diff --git a/JuceLibraryCode/AppConfig.h b/JuceLibraryCode/AppConfig.h new file mode 100644 index 0000000..5cb7b1d --- /dev/null +++ b/JuceLibraryCode/AppConfig.h @@ -0,0 +1,97 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + + There's a section below where you can add your own custom code safely, and the + Introjucer will preserve the contents of that block, but the best way to change + any of these definitions is by using the Introjucer's project settings. + + Any commented-out settings will assume their default values. + +*/ + +#ifndef __JUCE_APPCONFIG_AK6XQK__ +#define __JUCE_APPCONFIG_AK6XQK__ + +//============================================================================== +// [BEGIN_USER_CODE_SECTION] + +// (You can add your own code in this section, and the Introjucer will not overwrite it) + +// [END_USER_CODE_SECTION] + +//============================================================================== +#define JUCE_MODULE_AVAILABLE_juce_core 1 +#define JUCE_MODULE_AVAILABLE_juce_data_structures 1 +#define JUCE_MODULE_AVAILABLE_juce_events 1 +#define JUCE_MODULE_AVAILABLE_juce_graphics 1 +#define JUCE_MODULE_AVAILABLE_juce_gui_basics 1 +#define JUCE_MODULE_AVAILABLE_juce_gui_extra 1 + +//============================================================================== +// juce_core flags: + +#ifndef JUCE_FORCE_DEBUG + //#define JUCE_FORCE_DEBUG +#endif + +#ifndef JUCE_LOG_ASSERTIONS + //#define JUCE_LOG_ASSERTIONS +#endif + +#ifndef JUCE_CHECK_MEMORY_LEAKS + //#define JUCE_CHECK_MEMORY_LEAKS +#endif + +#ifndef JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + //#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES +#endif + +#ifndef JUCE_INCLUDE_ZLIB_CODE + //#define JUCE_INCLUDE_ZLIB_CODE +#endif + +//============================================================================== +// juce_graphics flags: + +#ifndef JUCE_USE_COREIMAGE_LOADER + //#define JUCE_USE_COREIMAGE_LOADER +#endif + +#ifndef JUCE_USE_DIRECTWRITE + //#define JUCE_USE_DIRECTWRITE +#endif + +//============================================================================== +// juce_gui_basics flags: + +#ifndef JUCE_ENABLE_REPAINT_DEBUGGING + //#define JUCE_ENABLE_REPAINT_DEBUGGING +#endif + +#ifndef JUCE_USE_XSHM + //#define JUCE_USE_XSHM +#endif + +#ifndef JUCE_USE_XRENDER + //#define JUCE_USE_XRENDER +#endif + +#ifndef JUCE_USE_XCURSOR + //#define JUCE_USE_XCURSOR +#endif + +//============================================================================== +// juce_gui_extra flags: + +#ifndef JUCE_WEB_BROWSER + //#define JUCE_WEB_BROWSER +#endif + +#ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR + //#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR +#endif + + +#endif // __JUCE_APPCONFIG_AK6XQK__ diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h new file mode 100644 index 0000000..00f4278 --- /dev/null +++ b/JuceLibraryCode/JuceHeader.h @@ -0,0 +1,37 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + + This is the header file that your files should include in order to get all the + JUCE library headers. You should avoid including the JUCE headers directly in + your own source files, because that wouldn't pick up the correct configuration + options for your app. + +*/ + +#ifndef __APPHEADERFILE_AK6XQK__ +#define __APPHEADERFILE_AK6XQK__ + +#include "AppConfig.h" +#include "modules/juce_core/juce_core.h" +#include "modules/juce_data_structures/juce_data_structures.h" +#include "modules/juce_events/juce_events.h" +#include "modules/juce_graphics/juce_graphics.h" +#include "modules/juce_gui_basics/juce_gui_basics.h" +#include "modules/juce_gui_extra/juce_gui_extra.h" + +#if ! DONT_SET_USING_JUCE_NAMESPACE + // If your code uses a lot of JUCE classes, then this will obviously save you + // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE. + using namespace juce; +#endif + +namespace ProjectInfo +{ + const char* const projectName = "FsTrack"; + const char* const versionString = "1.0.0"; + const int versionNumber = 0x10000; +} + +#endif // __APPHEADERFILE_AK6XQK__ diff --git a/JuceLibraryCode/ReadMe.txt b/JuceLibraryCode/ReadMe.txt new file mode 100644 index 0000000..f6c3564 --- /dev/null +++ b/JuceLibraryCode/ReadMe.txt @@ -0,0 +1,12 @@ + + Important Note!! + ================ + +The purpose of this folder is to contain files that are auto-generated by the Introjucer, +and ALL files in this folder will be mercilessly DELETED and completely re-written whenever +the Introjucer saves your project. + +Therefore, it's a bad idea to make any manual changes to the files in here, or to +put any of your own files in here if you don't want to lose them. (Of course you may choose +to add the folder's contents to your version-control system so that you can re-merge your own +modifications after the Introjucer has saved its changes). diff --git a/JuceLibraryCode/memdump.c b/JuceLibraryCode/memdump.c new file mode 100644 index 0000000..12c0b83 --- /dev/null +++ b/JuceLibraryCode/memdump.c @@ -0,0 +1,75 @@ +#include +#include +#include + + +static char temp[16]; + +#define PRINT_WORD(buf, w)\ + sprintf(temp, "%08X", w); \ + strcat(buf, temp); + +#define PRINT_BYTE(buf, b)\ + sprintf(temp, "%02X", b); \ + strcat(buf, temp) + +#define PRINT_STRING(buf, s)\ + sprintf(temp, "%s", s); \ + strcat(buf, temp) + +#define PRINT_CHAR(buf, c)\ + sprintf(temp, "%c", c); \ + strcat(buf, temp) + +void memdump(uint8_t *pBuf, uint32_t base, int num_bytes_per_row, int len) +{ + char buffer[1024] = {0}; + int i, j, cnt_hex, cnt_asc; + uint8_t c; + + i = j = 0; + cnt_hex = len; + cnt_asc = len; + + do + { + buffer[0] = 0; + PRINT_WORD(buffer, base + i); + PRINT_STRING(buffer, ": "); + for (j=0; j < num_bytes_per_row; j++) + { + if (cnt_hex) + { + PRINT_BYTE(buffer, pBuf[i+j]); + PRINT_STRING(buffer, " "); + cnt_hex--; + } + else + { + PRINT_STRING(buffer, " "); + } + + } + + PRINT_STRING(buffer, " "); + for (j=0; j < num_bytes_per_row; j++) + { + if (cnt_asc) + { + c = pBuf[i+j]; + if((c < 0x20) || (c > 0x7F)) + c = '.'; + + PRINT_CHAR(buffer, c); + cnt_asc--; + } + else + { + PRINT_STRING(buffer, " "); + } + } + printf("%s\n", buffer); + i += num_bytes_per_row; + + } while (cnt_hex); +} \ No newline at end of file diff --git a/JuceLibraryCode/memdump.h b/JuceLibraryCode/memdump.h new file mode 100644 index 0000000..379e68d --- /dev/null +++ b/JuceLibraryCode/memdump.h @@ -0,0 +1,22 @@ +/* + * File: memdump.h + * Author: jens + * + * Created on 1. Februar 2015, 09:09 + */ + +#ifndef MEMDUMP_H +#define MEMDUMP_H + +#ifdef __cplusplus +extern "C" { +#endif + +void memdump(uint8_t *pBuf, uint32_t base, int num_bytes_per_row, int len); + +#ifdef __cplusplus +} +#endif + +#endif /* MEMDUMP_H */ + diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.cpp new file mode 100644 index 0000000..65b1615 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.cpp @@ -0,0 +1,235 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +AbstractFifo::AbstractFifo (const int capacity) noexcept + : bufferSize (capacity) +{ + jassert (bufferSize > 0); +} + +AbstractFifo::~AbstractFifo() {} + +int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } +int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady() - 1; } + +int AbstractFifo::getNumReady() const noexcept +{ + const int vs = validStart.get(); + const int ve = validEnd.get(); + return ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); +} + +void AbstractFifo::reset() noexcept +{ + validEnd = 0; + validStart = 0; +} + +void AbstractFifo::setTotalSize (int newSize) noexcept +{ + jassert (newSize > 0); + reset(); + bufferSize = newSize; +} + +//============================================================================== +void AbstractFifo::prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept +{ + const int vs = validStart.get(); + const int ve = validEnd.value; + + const int freeSpace = ve >= vs ? (bufferSize - (ve - vs)) : (vs - ve); + numToWrite = jmin (numToWrite, freeSpace - 1); + + if (numToWrite <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = ve; + startIndex2 = 0; + blockSize1 = jmin (bufferSize - ve, numToWrite); + numToWrite -= blockSize1; + blockSize2 = numToWrite <= 0 ? 0 : jmin (numToWrite, vs); + } +} + +void AbstractFifo::finishedWrite (int numWritten) noexcept +{ + jassert (numWritten >= 0 && numWritten < bufferSize); + int newEnd = validEnd.value + numWritten; + if (newEnd >= bufferSize) + newEnd -= bufferSize; + + validEnd = newEnd; +} + +void AbstractFifo::prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept +{ + const int vs = validStart.value; + const int ve = validEnd.get(); + + const int numReady = ve >= vs ? (ve - vs) : (bufferSize - (vs - ve)); + numWanted = jmin (numWanted, numReady); + + if (numWanted <= 0) + { + startIndex1 = 0; + startIndex2 = 0; + blockSize1 = 0; + blockSize2 = 0; + } + else + { + startIndex1 = vs; + startIndex2 = 0; + blockSize1 = jmin (bufferSize - vs, numWanted); + numWanted -= blockSize1; + blockSize2 = numWanted <= 0 ? 0 : jmin (numWanted, ve); + } +} + +void AbstractFifo::finishedRead (int numRead) noexcept +{ + jassert (numRead >= 0 && numRead <= bufferSize); + + int newStart = validStart.value + numRead; + if (newStart >= bufferSize) + newStart -= bufferSize; + + validStart = newStart; +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class AbstractFifoTests : public UnitTest +{ +public: + AbstractFifoTests() : UnitTest ("Abstract Fifo") {} + + class WriteThread : public Thread + { + public: + WriteThread (AbstractFifo& f, int* b, Random rng) + : Thread ("fifo writer"), fifo (f), buffer (b), random (rng) + { + startThread(); + } + + ~WriteThread() + { + stopThread (5000); + } + + void run() + { + int n = 0; + + while (! threadShouldExit()) + { + int num = random.nextInt (2000) + 1; + + int start1, size1, start2, size2; + fifo.prepareToWrite (num, start1, size1, start2, size2); + + jassert (size1 >= 0 && size2 >= 0); + jassert (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())); + jassert (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize())); + + for (int i = 0; i < size1; ++i) + buffer [start1 + i] = n++; + + for (int i = 0; i < size2; ++i) + buffer [start2 + i] = n++; + + fifo.finishedWrite (size1 + size2); + } + } + + private: + AbstractFifo& fifo; + int* buffer; + Random random; + }; + + void runTest() + { + beginTest ("AbstractFifo"); + + int buffer [5000]; + AbstractFifo fifo (numElementsInArray (buffer)); + + WriteThread writer (fifo, buffer, getRandom()); + + int n = 0; + Random r = getRandom(); + r.combineSeed (12345); + + for (int count = 100000; --count >= 0;) + { + int num = r.nextInt (6000) + 1; + + int start1, size1, start2, size2; + fifo.prepareToRead (num, start1, size1, start2, size2); + + if (! (size1 >= 0 && size2 >= 0) + && (size1 == 0 || (start1 >= 0 && start1 < fifo.getTotalSize())) + && (size2 == 0 || (start2 >= 0 && start2 < fifo.getTotalSize()))) + { + expect (false, "prepareToRead returned -ve values"); + break; + } + + bool failed = false; + + for (int i = 0; i < size1; ++i) + failed = (buffer [start1 + i] != n++) || failed; + + for (int i = 0; i < size2; ++i) + failed = (buffer [start2 + i] != n++) || failed; + + if (failed) + { + expect (false, "read values were incorrect"); + break; + } + + fifo.finishedRead (size1 + size2); + } + } +}; + +static AbstractFifoTests fifoUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.h b/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.h new file mode 100644 index 0000000..02feac7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_AbstractFifo.h @@ -0,0 +1,220 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ABSTRACTFIFO_H_INCLUDED +#define JUCE_ABSTRACTFIFO_H_INCLUDED + + +//============================================================================== +/** + Encapsulates the logic required to implement a lock-free FIFO. + + This class handles the logic needed when building a single-reader, single-writer FIFO. + + It doesn't actually hold any data itself, but your FIFO class can use one of these to manage + its position and status when reading or writing to it. + + To use it, you can call prepareToWrite() to determine the position within your own buffer that + an incoming block of data should be stored, and prepareToRead() to find out when the next + outgoing block should be read from. + + e.g. + @code + class MyFifo + { + public: + MyFifo() : abstractFifo (1024) + { + } + + void addToFifo (const int* someData, int numItems) + { + int start1, size1, start2, size2; + abstractFifo.prepareToWrite (numItems, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (myBuffer + start1, someData, size1); + + if (size2 > 0) + copySomeData (myBuffer + start2, someData + size1, size2); + + abstractFifo.finishedWrite (size1 + size2); + } + + void readFromFifo (int* someData, int numItems) + { + int start1, size1, start2, size2; + abstractFifo.prepareToRead (numSamples, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (someData, myBuffer + start1, size1); + + if (size2 > 0) + copySomeData (someData + size1, myBuffer + start2, size2); + + abstractFifo.finishedRead (size1 + size2); + } + + private: + AbstractFifo abstractFifo; + int myBuffer [1024]; + }; + @endcode +*/ +class JUCE_API AbstractFifo +{ +public: + //============================================================================== + /** Creates a FIFO to manage a buffer with the specified capacity. */ + AbstractFifo (int capacity) noexcept; + + /** Destructor */ + ~AbstractFifo(); + + //============================================================================== + /** Returns the total size of the buffer being managed. */ + int getTotalSize() const noexcept; + + /** Returns the number of items that can currently be added to the buffer without it overflowing. */ + int getFreeSpace() const noexcept; + + /** Returns the number of items that can currently be read from the buffer. */ + int getNumReady() const noexcept; + + /** Clears the buffer positions, so that it appears empty. */ + void reset() noexcept; + + /** Changes the buffer's total size. + Note that this isn't thread-safe, so don't call it if there's any danger that it + might overlap with a call to any other method in this class! + */ + void setTotalSize (int newSize) noexcept; + + //============================================================================== + /** Returns the location within the buffer at which an incoming block of data should be written. + + Because the section of data that you want to add to the buffer may overlap the end + and wrap around to the start, two blocks within your buffer are returned, and you + should copy your data into the first one, with any remaining data spilling over into + the second. + + If the number of items you ask for is too large to fit within the buffer's free space, then + blockSize1 + blockSize2 may add up to a lower value than numToWrite. If this happens, you + may decide to keep waiting and re-trying the method until there's enough space available. + + After calling this method, if you choose to write your data into the blocks returned, you + must call finishedWrite() to tell the FIFO how much data you actually added. + + e.g. + @code + void addToFifo (const int* someData, int numItems) + { + int start1, size1, start2, size2; + prepareToWrite (numItems, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (myBuffer + start1, someData, size1); + + if (size2 > 0) + copySomeData (myBuffer + start2, someData + size1, size2); + + finishedWrite (size1 + size2); + } + @endcode + + @param numToWrite indicates how many items you'd like to add to the buffer + @param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written + @param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 + @param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into + the first block should be written + @param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 + @see finishedWrite + */ + void prepareToWrite (int numToWrite, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; + + /** Called after writing from the FIFO, to indicate that this many items have been added. + @see prepareToWrite + */ + void finishedWrite (int numWritten) noexcept; + + /** Returns the location within the buffer from which the next block of data should be read. + + Because the section of data that you want to read from the buffer may overlap the end + and wrap around to the start, two blocks within your buffer are returned, and you + should read from both of them. + + If the number of items you ask for is greater than the amount of data available, then + blockSize1 + blockSize2 may add up to a lower value than numWanted. If this happens, you + may decide to keep waiting and re-trying the method until there's enough data available. + + After calling this method, if you choose to read the data, you must call finishedRead() to + tell the FIFO how much data you have consumed. + + e.g. + @code + void readFromFifo (int* someData, int numItems) + { + int start1, size1, start2, size2; + prepareToRead (numSamples, start1, size1, start2, size2); + + if (size1 > 0) + copySomeData (someData, myBuffer + start1, size1); + + if (size2 > 0) + copySomeData (someData + size1, myBuffer + start2, size2); + + finishedRead (size1 + size2); + } + @endcode + + @param numWanted indicates how many items you'd like to add to the buffer + @param startIndex1 on exit, this will contain the start index in your buffer at which your data should be written + @param blockSize1 on exit, this indicates how many items can be written to the block starting at startIndex1 + @param startIndex2 on exit, this will contain the start index in your buffer at which any data that didn't fit into + the first block should be written + @param blockSize2 on exit, this indicates how many items can be written to the block starting at startIndex2 + @see finishedRead + */ + void prepareToRead (int numWanted, int& startIndex1, int& blockSize1, int& startIndex2, int& blockSize2) const noexcept; + + /** Called after reading from the FIFO, to indicate that this many items have now been consumed. + @see prepareToRead + */ + void finishedRead (int numRead) noexcept; + + +private: + //============================================================================== + int bufferSize; + Atomic validStart, validEnd; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) +}; + + +#endif // JUCE_ABSTRACTFIFO_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Array.h b/JuceLibraryCode/modules/juce_core/containers/juce_Array.h new file mode 100644 index 0000000..87a136e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Array.h @@ -0,0 +1,1116 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ARRAY_H_INCLUDED +#define JUCE_ARRAY_H_INCLUDED + + +//============================================================================== +/** + Holds a resizable array of primitive or copy-by-value objects. + + Examples of arrays are: Array, Array or Array + + The Array class can be used to hold simple, non-polymorphic objects as well as primitive types - to + do so, the class must fulfil these requirements: + - it must have a copy constructor and assignment operator + - it must be able to be relocated in memory by a memcpy without this causing any problems - so + objects whose functionality relies on external pointers or references to themselves can not be used. + + You can of course have an array of pointers to any kind of object, e.g. Array, but if + you do this, the array doesn't take any ownership of the objects - see the OwnedArray class or the + ReferenceCountedArray class for more powerful ways of holding lists of objects. + + For holding lists of strings, you can use Array\, but it's usually better to use the + specialised class StringArray, which provides more useful functions. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see OwnedArray, ReferenceCountedArray, StringArray, CriticalSection +*/ +template +class Array +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + //============================================================================== + /** Creates an empty array. */ + Array() noexcept : numUsed (0) + { + } + + /** Creates a copy of another array. + @param other the array to copy + */ + Array (const Array& other) + { + const ScopedLockType lock (other.getLock()); + numUsed = other.numUsed; + data.setAllocatedSize (other.numUsed); + + for (int i = 0; i < numUsed; ++i) + new (data.elements + i) ElementType (other.data.elements[i]); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array (Array&& other) noexcept + : data (static_cast&&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + #endif + + /** Initalises from a null-terminated C array of values. + + @param values the array to copy from + */ + template + explicit Array (const TypeToCreateFrom* values) + : numUsed (0) + { + while (*values != TypeToCreateFrom()) + add (*values++); + } + + /** Initalises from a C array of values. + + @param values the array to copy from + @param numValues the number of values in the array + */ + template + Array (const TypeToCreateFrom* values, int numValues) + : numUsed (numValues) + { + data.setAllocatedSize (numValues); + + for (int i = 0; i < numValues; ++i) + new (data.elements + i) ElementType (values[i]); + } + + /** Destructor. */ + ~Array() + { + deleteAllElements(); + } + + /** Copies another array. + @param other the array to copy + */ + Array& operator= (const Array& other) + { + if (this != &other) + { + Array otherCopy (other); + swapWith (otherCopy); + } + + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Array& operator= (Array&& other) noexcept + { + const ScopedLockType lock (getLock()); + deleteAllElements(); + data = static_cast&&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator== (const OtherArrayType& other) const + { + const ScopedLockType lock (getLock()); + const typename OtherArrayType::ScopedLockType lock2 (other.getLock()); + + if (numUsed != other.numUsed) + return false; + + for (int i = numUsed; --i >= 0;) + if (! (data.elements [i] == other.data.elements [i])) + return false; + + return true; + } + + /** Compares this array to another one. + Two arrays are considered equal if they both contain the same set of + elements, in the same order. + @param other the other array to compare with + */ + template + bool operator!= (const OtherArrayType& other) const + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the array. + This will remove all the elements, and free any storage that the array is + using. To clear the array without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() + { + const ScopedLockType lock (getLock()); + deleteAllElements(); + data.setAllocatedSize (0); + numUsed = 0; + } + + /** Removes all elements from the array without freeing the array's allocated storage. + @see clear + */ + void clearQuick() + { + const ScopedLockType lock (getLock()); + deleteAllElements(); + numUsed = 0; + } + + //============================================================================== + /** Returns the current number of elements in the array. + */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns one of the elements in the array. + If the index passed in is beyond the range of valid elements, this + will return a default value. + + If you're certain that the index will always be a valid element, you + can call getUnchecked() instead, which is faster. + + @param index the index of the element being requested (0 is the first element in the array) + @see getUnchecked, getFirst, getLast + */ + ElementType operator[] (const int index) const + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return ElementType(); + } + + /** Returns one of the elements in the array, without checking the index passed in. + + Unlike the operator[] method, this will try to return an element without + checking that the index is within the bounds of the array, so should only + be used when you're confident that it will always be a valid index. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType getUnchecked (const int index) const + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns a direct reference to one of the elements in the array, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + @see operator[], getFirst, getLast + */ + inline ElementType& getReference (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns the first element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getLast + */ + inline ElementType getFirst() const + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements[0]; + } + + return ElementType(); + } + + /** Returns the last element in the array, or a default value if the array is empty. + + @see operator[], getUnchecked, getFirst + */ + inline ElementType getLast() const + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements[numUsed - 1]; + } + + return ElementType(); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ElementType* getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* end() const noexcept + { + #if JUCE_DEBUG + if (data.elements == nullptr || numUsed <= 0) // (to keep static analysers happy) + return data.elements; + #endif + + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of the first element which matches the value passed in. + + This will search the array for the given object, and return the index + of its first occurrence. If the object isn't found, the method will return -1. + + @param elementToLookFor the value or object to look for + @returns the index of the object, or -1 if it's not found + */ + int indexOf (ParameterType elementToLookFor) const + { + const ScopedLockType lock (getLock()); + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains at least one occurrence of an object. + + @param elementToLookFor the value or object to look for + @returns true if the item is found + */ + bool contains (ParameterType elementToLookFor) const + { + const ScopedLockType lock (getLock()); + const ElementType* e = data.elements.getData(); + const ElementType* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (elementToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new element at the end of the array. + + @param newElement the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addUsingDefaultSort, addArray + */ + void add (const ElementType& newElement) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newElement); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Appends a new element at the end of the array. + + @param newElement the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addUsingDefaultSort, addArray + */ + void add (ElementType&& newElement) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (static_cast (newElement)); + } + #endif + + /** Inserts a new element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be + inserted (pass in -1 to add it to the end) + @param newElement the new object to add to the array + @see add, addSorted, addUsingDefaultSort, set + */ + void insert (int indexToInsertAt, ParameterType newElement) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + ElementType* const insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + + if (numberToMove > 0) + memmove (insertPos + 1, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + + new (insertPos) ElementType (newElement); + ++numUsed; + } + else + { + new (data.elements + numUsed++) ElementType (newElement); + } + } + + /** Inserts multiple copies of an element into the array at a given position. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the new element should be inserted + @param newElement the new object to add to the array + @param numberOfTimesToInsertIt how many copies of the value to insert + @see insert, add, addSorted, set + */ + void insertMultiple (int indexToInsertAt, ParameterType newElement, + int numberOfTimesToInsertIt) + { + if (numberOfTimesToInsertIt > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfTimesToInsertIt); + ElementType* insertPos; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos = data.elements + indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfTimesToInsertIt, insertPos, ((size_t) numberToMove) * sizeof (ElementType)); + } + else + { + insertPos = data.elements + numUsed; + } + + numUsed += numberOfTimesToInsertIt; + + while (--numberOfTimesToInsertIt >= 0) + { + new (insertPos) ElementType (newElement); + ++insertPos; // NB: this increment is done separately from the + // new statement to avoid a compiler bug in VS2014 + } + } + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newElements the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + const ElementType* newElements, + int numberOfElements) + { + if (numberOfElements > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfElements); + ElementType* insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const int numberToMove = numUsed - indexToInsertAt; + memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ElementType)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + new (insertPos++) ElementType (*newElements++); + } + } + + /** Appends a new element at the end of the array as long as the array doesn't + already contain it. + + If the array already contains an element that matches the one passed in, nothing + will be done. + + @param newElement the new object to add to the array + */ + void addIfNotAlreadyThere (ParameterType newElement) + { + const ScopedLockType lock (getLock()); + + if (! contains (newElement)) + add (newElement); + } + + /** Replaces an element with a new value. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the item is added to the end of the array. + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see add, insert + */ + void set (const int indexToChange, ParameterType newValue) + { + jassert (indexToChange >= 0); + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToChange, numUsed)) + { + jassert (data.elements != nullptr); + data.elements [indexToChange] = newValue; + } + else if (indexToChange >= 0) + { + data.ensureAllocatedSize (numUsed + 1); + new (data.elements + numUsed++) ElementType (newValue); + } + } + + /** Replaces an element with a new value without doing any bounds-checking. + + This just sets a value directly in the array's internal storage, so you'd + better make sure it's in range! + + @param indexToChange the index whose value you want to change + @param newValue the new value to set for this index. + @see set, getUnchecked + */ + void setUnchecked (const int indexToChange, ParameterType newValue) + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (indexToChange, numUsed)); + data.elements [indexToChange] = newValue; + } + + /** Adds elements from an array to the end of this array. + + @param elementsToAdd an array of some kind of object from which elements + can be constructed. + @param numElementsToAdd how many elements are in this other array + @see add + */ + template + void addArray (const Type* elementsToAdd, int numElementsToAdd) + { + const ScopedLockType lock (getLock()); + + if (numElementsToAdd > 0) + { + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + { + new (data.elements + numUsed) ElementType (*elementsToAdd++); + ++numUsed; + } + } + } + + /** Adds elements from a null-terminated array of pointers to the end of this array. + + @param elementsToAdd an array of pointers to some kind of object from which elements + can be constructed. This array must be terminated by a nullptr + @see addArray + */ + template + void addNullTerminatedArray (const Type* const* elementsToAdd) + { + int num = 0; + for (const Type* const* e = elementsToAdd; *e != nullptr; ++e) + ++num; + + addArray (elementsToAdd, num); + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock()); + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + while (--numElementsToAdd >= 0) + add (arrayToAddFrom.getUnchecked (startIndex++)); + } + } + + /** This will enlarge or shrink the array to the given number of elements, by adding + or removing items from its end. + + If the array is smaller than the given target size, empty elements will be appended + until its size is as specified. If its size is larger than the target, items will be + removed from its end to shorten it. + */ + void resize (const int targetNumItems) + { + jassert (targetNumItems >= 0); + + const int numToAdd = targetNumItems - numUsed; + if (numToAdd > 0) + insertMultiple (numUsed, ElementType(), numToAdd); + else if (numToAdd < 0) + removeRange (targetNumItems, -numToAdd); + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use a comparator to find the position at which the new element + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param newElement the new element to insert to the array + @returns the index at which the new item was added + @see addUsingDefaultSort, add, sort + */ + template + int addSorted (ElementComparator& comparator, ParameterType newElement) + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newElement, 0, numUsed); + insert (index, newElement); + return index; + } + + /** Inserts a new element into the array, assuming that the array is sorted. + + This will use the DefaultElementComparator class for sorting, so your ElementType + must be suitable for use with that class. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param newElement the new element to insert to the array + @see addSorted, sort + */ + void addUsingDefaultSort (ParameterType newElement) + { + DefaultElementComparator comparator; + addSorted (comparator, newElement); + } + + /** Finds the index of an element in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param elementToLookFor the element to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, TargetValueType elementToLookFor) const + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + + for (int s = 0, e = numUsed;;) + { + if (s >= e) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + return -1; + + if (comparator.compareElements (elementToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + } + + //============================================================================== + /** Removes an element from the array. + + This will remove the element at a given index, and move back + all the subsequent elements to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @returns the element that has been removed + @see removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + ElementType remove (const int indexToRemove) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + jassert (data.elements != nullptr); + ElementType removed (data.elements[indexToRemove]); + removeInternal (indexToRemove); + return removed; + } + + return ElementType(); + } + + /** Removes an item from the array. + + This will remove the first occurrence of the given element from the array. + If the item isn't found, no action is taken. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeFirstMatchingValue (ParameterType valueToRemove) + { + const ScopedLockType lock (getLock()); + ElementType* const e = data.elements; + + for (int i = 0; i < numUsed; ++i) + { + if (valueToRemove == e[i]) + { + removeInternal (i); + break; + } + } + } + + /** Removes an item from the array. + + This will remove the first occurrence of the given element from the array. + If the item isn't found, no action is taken. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeAllInstancesOf (ParameterType valueToRemove) + { + const ScopedLockType lock (getLock()); + + for (int i = numUsed; --i >= 0;) + if (valueToRemove == data.elements[i]) + removeInternal (i); + } + + /** Removes a range of elements from the array. + + This will remove a set of elements, starting from the given index, + and move subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first element to remove + @param numberToRemove how many elements should be removed + @see remove, removeFirstMatchingValue, removeAllInstancesOf + */ + void removeRange (int startIndex, int numberToRemove) + { + const ScopedLockType lock (getLock()); + const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + ElementType* const e = data.elements + startIndex; + + numberToRemove = endIndex - startIndex; + for (int i = 0; i < numberToRemove; ++i) + e[i].~ElementType(); + + const int numToShift = numUsed - endIndex; + if (numToShift > 0) + memmove (e, e + numberToRemove, ((size_t) numToShift) * sizeof (ElementType)); + + numUsed -= numberToRemove; + minimiseStorageAfterRemoval(); + } + } + + /** Removes the last n elements from the array. + + @param howManyToRemove how many elements to remove from the end of the array + @see remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + void removeLast (int howManyToRemove = 1) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove > numUsed) + howManyToRemove = numUsed; + + for (int i = 1; i <= howManyToRemove; ++i) + data.elements [numUsed - i].~ElementType(); + + numUsed -= howManyToRemove; + minimiseStorageAfterRemoval(); + } + + /** Removes any elements which are also in another array. + + @param otherArray the other array in which to look for elements to remove + @see removeValuesNotIn, remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + template + void removeValuesIn (const OtherArrayType& otherArray) + { + const typename OtherArrayType::ScopedLockType lock1 (otherArray.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this == &otherArray) + { + clear(); + } + else + { + if (otherArray.size() > 0) + { + for (int i = numUsed; --i >= 0;) + if (otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Removes any elements which are not found in another array. + + Only elements which occur in this other array will be retained. + + @param otherArray the array in which to look for elements NOT to remove + @see removeValuesIn, remove, removeFirstMatchingValue, removeAllInstancesOf, removeRange + */ + template + void removeValuesNotIn (const OtherArrayType& otherArray) + { + const typename OtherArrayType::ScopedLockType lock1 (otherArray.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this != &otherArray) + { + if (otherArray.size() <= 0) + { + clear(); + } + else + { + for (int i = numUsed; --i >= 0;) + if (! otherArray.contains (data.elements [i])) + removeInternal (i); + } + } + } + + /** Swaps over two elements in the array. + + This swaps over the elements found at the two indexes passed in. + If either index is out-of-range, this method will do nothing. + + @param index1 index of one of the elements to swap + @param index2 index of the other element to swap + */ + void swap (const int index1, + const int index2) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the values to a different position. + + This will move the value to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the value to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this value to end up. If this + is less than zero, the value will be moved to the end + of the array + */ + void move (const int currentIndex, int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + char tempCopy [sizeof (ElementType)]; + memcpy (tempCopy, data.elements + currentIndex, sizeof (ElementType)); + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ElementType) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ElementType) * (size_t) (currentIndex - newIndex)); + } + + memcpy (data.elements + newIndex, tempCopy, sizeof (ElementType)); + } + } + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see addSorted, indexOfSorted, sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const + { + const ScopedLockType lock (getLock()); + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + + //============================================================================== + #ifndef DOXYGEN + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (Array& other) noexcept, { swapWith (other); }) + #endif + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void removeInternal (const int indexToRemove) + { + --numUsed; + ElementType* const e = data.elements + indexToRemove; + e->~ElementType(); + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, ((size_t) numberToShift) * sizeof (ElementType)); + + minimiseStorageAfterRemoval(); + } + + inline void deleteAllElements() noexcept + { + for (int i = 0; i < numUsed; ++i) + data.elements[i].~ElementType(); + } + + void minimiseStorageAfterRemoval() + { + if (data.numAllocated > jmax (minimumAllocatedSize, numUsed * 2)) + data.shrinkToNoMoreThan (jmax (numUsed, jmax (minimumAllocatedSize, 64 / (int) sizeof (ElementType)))); + } +}; + + +#endif // JUCE_ARRAY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ArrayAllocationBase.h b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayAllocationBase.h new file mode 100644 index 0000000..cfbe464 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayAllocationBase.h @@ -0,0 +1,138 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ARRAYALLOCATIONBASE_H_INCLUDED +#define JUCE_ARRAYALLOCATIONBASE_H_INCLUDED + + +//============================================================================== +/** + Implements some basic array storage allocation functions. + + This class isn't really for public use - it's used by the other + array classes, but might come in handy for some purposes. + + It inherits from a critical section class to allow the arrays to use + the "empty base class optimisation" pattern to reduce their footprint. + + @see Array, OwnedArray, ReferenceCountedArray +*/ +template +class ArrayAllocationBase : public TypeOfCriticalSectionToUse +{ +public: + //============================================================================== + /** Creates an empty array. */ + ArrayAllocationBase() noexcept + : numAllocated (0) + { + } + + /** Destructor. */ + ~ArrayAllocationBase() noexcept + { + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + ArrayAllocationBase (ArrayAllocationBase&& other) noexcept + : elements (static_cast &&> (other.elements)), + numAllocated (other.numAllocated) + { + } + + ArrayAllocationBase& operator= (ArrayAllocationBase&& other) noexcept + { + elements = static_cast &&> (other.elements); + numAllocated = other.numAllocated; + return *this; + } + #endif + + //============================================================================== + /** Changes the amount of storage allocated. + + This will retain any data currently held in the array, and either add or + remove extra space at the end. + + @param numElements the number of elements that are needed + */ + void setAllocatedSize (const int numElements) + { + if (numAllocated != numElements) + { + if (numElements > 0) + elements.realloc ((size_t) numElements); + else + elements.free(); + + numAllocated = numElements; + } + } + + /** Increases the amount of storage allocated if it is less than a given amount. + + This will retain any data currently held in the array, but will add + extra space at the end to make sure there it's at least as big as the size + passed in. If it's already bigger, no action is taken. + + @param minNumElements the minimum number of elements that are needed + */ + void ensureAllocatedSize (const int minNumElements) + { + if (minNumElements > numAllocated) + setAllocatedSize ((minNumElements + minNumElements / 2 + 8) & ~7); + + jassert (numAllocated <= 0 || elements != nullptr); + } + + /** Minimises the amount of storage allocated so that it's no more than + the given number of elements. + */ + void shrinkToNoMoreThan (const int maxNumElements) + { + if (maxNumElements < numAllocated) + setAllocatedSize (maxNumElements); + } + + /** Swap the contents of two objects. */ + void swapWith (ArrayAllocationBase & other) noexcept + { + elements.swapWith (other.elements); + std::swap (numAllocated, other.numAllocated); + } + + //============================================================================== + HeapBlock elements; + int numAllocated; + +private: + JUCE_DECLARE_NON_COPYABLE (ArrayAllocationBase) +}; + + +#endif // JUCE_ARRAYALLOCATIONBASE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.cpp new file mode 100644 index 0000000..38f5c5f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.cpp @@ -0,0 +1,133 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +DynamicObject::DynamicObject() +{ +} + +DynamicObject::DynamicObject (const DynamicObject& other) + : ReferenceCountedObject(), properties (other.properties) +{ +} + +DynamicObject::~DynamicObject() +{ +} + +bool DynamicObject::hasProperty (const Identifier& propertyName) const +{ + const var* const v = properties.getVarPointer (propertyName); + return v != nullptr && ! v->isMethod(); +} + +var DynamicObject::getProperty (const Identifier& propertyName) const +{ + return properties [propertyName]; +} + +void DynamicObject::setProperty (const Identifier& propertyName, const var& newValue) +{ + properties.set (propertyName, newValue); +} + +void DynamicObject::removeProperty (const Identifier& propertyName) +{ + properties.remove (propertyName); +} + +bool DynamicObject::hasMethod (const Identifier& methodName) const +{ + return getProperty (methodName).isMethod(); +} + +var DynamicObject::invokeMethod (Identifier method, const var::NativeFunctionArgs& args) +{ + if (var::NativeFunction function = properties [method].getNativeFunction()) + return function (args); + + return var(); +} + +void DynamicObject::setMethod (Identifier name, var::NativeFunction function) +{ + properties.set (name, var (function)); +} + +void DynamicObject::clear() +{ + properties.clear(); +} + +void DynamicObject::cloneAllProperties() +{ + for (int i = properties.size(); --i >= 0;) + if (var* v = properties.getVarPointerAt (i)) + *v = v->clone(); +} + +DynamicObject::Ptr DynamicObject::clone() +{ + Ptr d (new DynamicObject (*this)); + d->cloneAllProperties(); + return d; +} + +void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const bool allOnOneLine) +{ + out << '{'; + if (! allOnOneLine) + out << newLine; + + const int numValues = properties.size(); + + for (int i = 0; i < numValues; ++i) + { + if (! allOnOneLine) + JSONFormatter::writeSpaces (out, indentLevel + JSONFormatter::indentSize); + + out << '"'; + JSONFormatter::writeString (out, properties.getName (i)); + out << "\": "; + JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine); + + if (i < numValues - 1) + { + if (allOnOneLine) + out << ", "; + else + out << ',' << newLine; + } + else if (! allOnOneLine) + out << newLine; + } + + if (! allOnOneLine) + JSONFormatter::writeSpaces (out, indentLevel); + + out << '}'; +} diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.h b/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.h new file mode 100644 index 0000000..5c624a4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_DynamicObject.h @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DYNAMICOBJECT_H_INCLUDED +#define JUCE_DYNAMICOBJECT_H_INCLUDED + + +//============================================================================== +/** + Represents a dynamically implemented object. + + This class is primarily intended for wrapping scripting language objects, + but could be used for other purposes. + + An instance of a DynamicObject can be used to store named properties, and + by subclassing hasMethod() and invokeMethod(), you can give your object + methods. +*/ +class JUCE_API DynamicObject : public ReferenceCountedObject +{ +public: + //============================================================================== + DynamicObject(); + DynamicObject (const DynamicObject&); + ~DynamicObject(); + + typedef ReferenceCountedObjectPtr Ptr; + + //============================================================================== + /** Returns true if the object has a property with this name. + Note that if the property is actually a method, this will return false. + */ + virtual bool hasProperty (const Identifier& propertyName) const; + + /** Returns a named property. + This returns var::null if no such property exists. + */ + virtual var getProperty (const Identifier& propertyName) const; + + /** Sets a named property. */ + virtual void setProperty (const Identifier& propertyName, const var& newValue); + + /** Removes a named property. */ + virtual void removeProperty (const Identifier& propertyName); + + //============================================================================== + /** Checks whether this object has the specified method. + + The default implementation of this just checks whether there's a property + with this name that's actually a method, but this can be overridden for + building objects with dynamic invocation. + */ + virtual bool hasMethod (const Identifier& methodName) const; + + /** Invokes a named method on this object. + + The default implementation looks up the named property, and if it's a method + call, then it invokes it. + + This method is virtual to allow more dynamic invocation to used for objects + where the methods may not already be set as properies. + */ + virtual var invokeMethod (Identifier methodName, + const var::NativeFunctionArgs& args); + + /** Adds a method to the class. + + This is basically the same as calling setProperty (methodName, (var::NativeFunction) myFunction), but + helps to avoid accidentally invoking the wrong type of var constructor. It also makes + the code easier to read, + */ + void setMethod (Identifier methodName, var::NativeFunction function); + + //============================================================================== + /** Removes all properties and methods from the object. */ + void clear(); + + /** Returns the NamedValueSet that holds the object's properties. */ + NamedValueSet& getProperties() noexcept { return properties; } + + /** Calls var::clone() on all the properties that this object contains. */ + void cloneAllProperties(); + + //============================================================================== + /** Returns a clone of this object. + The default implementation of this method just returns a new DynamicObject + with a (deep) copy of all of its properties. Subclasses can override this to + implement their own custom copy routines. + */ + virtual Ptr clone(); + + //============================================================================== + /** Writes this object to a text stream in JSON format. + This method is used by JSON::toString and JSON::writeToStream, and you should + never need to call it directly, but it's virtual so that custom object types + can stringify themselves appropriately. + */ + virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine); + +private: + //============================================================================== + NamedValueSet properties; + + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // These methods have been deprecated - use var::invoke instead + virtual void invokeMethod (const Identifier&, const var*, int) {} + #endif + + JUCE_LEAK_DETECTOR (DynamicObject) +}; + + + +#endif // JUCE_DYNAMICOBJECT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ElementComparator.h b/JuceLibraryCode/modules/juce_core/containers/juce_ElementComparator.h new file mode 100644 index 0000000..01dc5b7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ElementComparator.h @@ -0,0 +1,195 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ELEMENTCOMPARATOR_H_INCLUDED +#define JUCE_ELEMENTCOMPARATOR_H_INCLUDED + +#ifndef DOXYGEN + +/** This is an internal helper class which converts a juce ElementComparator style + class (using a "compareElements" method) into a class that's compatible with + std::sort (i.e. using an operator() to compare the elements) +*/ +template +struct SortFunctionConverter +{ + SortFunctionConverter (ElementComparator& e) : comparator (e) {} + + template + bool operator() (Type a, Type b) { return comparator.compareElements (a, b) < 0; } + +private: + ElementComparator& comparator; + SortFunctionConverter& operator= (const SortFunctionConverter&) JUCE_DELETED_FUNCTION; +}; + +#endif + + +//============================================================================== +/** + Sorts a range of elements in an array. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to sort + @param firstElement the index of the first element of the range to be sorted + @param lastElement the index of the last element in the range that needs + sorting (this is inclusive) + @param retainOrderOfEquivalentItems if true, the order of items that the + comparator deems the same will be maintained - this will be + a slower algorithm than if they are allowed to be moved around. + + @see sortArrayRetainingOrder +*/ +template +static void sortArray (ElementComparator& comparator, + ElementType* const array, + int firstElement, + int lastElement, + const bool retainOrderOfEquivalentItems) +{ + SortFunctionConverter converter (comparator); + + if (retainOrderOfEquivalentItems) + std::stable_sort (array + firstElement, array + lastElement + 1, converter); + else + std::sort (array + firstElement, array + lastElement + 1, converter); +} + + +//============================================================================== +/** + Searches a sorted array of elements, looking for the index at which a specified value + should be inserted for it to be in the correct order. + + The comparator object that is passed-in must define a public method with the following + signature: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator an object which defines a compareElements() method + @param array the array to search + @param newElement the value that is going to be inserted + @param firstElement the index of the first element to search + @param lastElement the index of the last element in the range (this is non-inclusive) +*/ +template +static int findInsertIndexInSortedArray (ElementComparator& comparator, + ElementType* const array, + const ElementType newElement, + int firstElement, + int lastElement) +{ + jassert (firstElement <= lastElement); + + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + while (firstElement < lastElement) + { + if (comparator.compareElements (newElement, array [firstElement]) == 0) + { + ++firstElement; + break; + } + else + { + const int halfway = (firstElement + lastElement) >> 1; + + if (halfway == firstElement) + { + if (comparator.compareElements (newElement, array [halfway]) >= 0) + ++firstElement; + + break; + } + else if (comparator.compareElements (newElement, array [halfway]) >= 0) + { + firstElement = halfway; + } + else + { + lastElement = halfway; + } + } + } + + return firstElement; +} + +//============================================================================== +/** + A simple ElementComparator class that can be used to sort an array of + objects that support the '<' operator. + + This will work for primitive types and objects that implement operator<(). + + Example: @code + Array myArray; + DefaultElementComparator sorter; + myArray.sort (sorter); + @endcode + + @see ElementComparator +*/ +template +class DefaultElementComparator +{ +private: + typedef PARAMETER_TYPE (ElementType) ParameterType; + +public: + static int compareElements (ParameterType first, ParameterType second) + { + return (first < second) ? -1 : ((second < first) ? 1 : 0); + } +}; + + +#endif // JUCE_ELEMENTCOMPARATOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_HashMap.h b/JuceLibraryCode/modules/juce_core/containers/juce_HashMap.h new file mode 100644 index 0000000..07d02b4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_HashMap.h @@ -0,0 +1,455 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HASHMAP_H_INCLUDED +#define JUCE_HASHMAP_H_INCLUDED + + +//============================================================================== +/** + A simple class to generate hash functions for some primitive types, intended for + use with the HashMap class. + @see HashMap +*/ +struct DefaultHashFunctions +{ + /** Generates a simple hash from an integer. */ + int generateHash (const int key, const int upperLimit) const noexcept { return std::abs (key) % upperLimit; } + /** Generates a simple hash from an int64. */ + int generateHash (const int64 key, const int upperLimit) const noexcept { return std::abs ((int) key) % upperLimit; } + /** Generates a simple hash from a string. */ + int generateHash (const String& key, const int upperLimit) const noexcept { return (int) (((uint32) key.hashCode()) % (uint32) upperLimit); } + /** Generates a simple hash from a variant. */ + int generateHash (const var& key, const int upperLimit) const noexcept { return generateHash (key.toString(), upperLimit); } +}; + + +//============================================================================== +/** + Holds a set of mappings between some key/value pairs. + + The types of the key and value objects are set as template parameters. + You can also specify a class to supply a hash function that converts a key value + into an hashed integer. This class must have the form: + + @code + struct MyHashGenerator + { + int generateHash (MyKeyType key, int upperLimit) const + { + // The function must return a value 0 <= x < upperLimit + return someFunctionOfMyKeyType (key) % upperLimit; + } + }; + @endcode + + Like the Array class, the key and value types are expected to be copy-by-value + types, so if you define them to be pointer types, this class won't delete the + objects that they point to. + + If you don't supply a class for the HashFunctionType template parameter, the + default one provides some simple mappings for strings and ints. + + @code + HashMap hash; + hash.set (1, "item1"); + hash.set (2, "item2"); + + DBG (hash [1]); // prints "item1" + DBG (hash [2]); // prints "item2" + + // This iterates the map, printing all of its key -> value pairs.. + for (HashMap::Iterator i (hash); i.next();) + DBG (i.getKey() << " -> " << i.getValue()); + @endcode + + @tparam HashFunctionType The class of hash function, which must be copy-constructible. + @see CriticalSection, DefaultHashFunctions, NamedValueSet, SortedSet +*/ +template +class HashMap +{ +private: + typedef PARAMETER_TYPE (KeyType) KeyTypeParameter; + typedef PARAMETER_TYPE (ValueType) ValueTypeParameter; + +public: + //============================================================================== + /** Creates an empty hash-map. + + @param numberOfSlots Specifies the number of hash entries the map will use. This will be + the "upperLimit" parameter that is passed to your generateHash() + function. The number of hash slots will grow automatically if necessary, + or it can be remapped manually using remapTable(). + @param hashFunction An instance of HashFunctionType, which will be copied and + stored to use with the HashMap. This parameter can be omitted + if HashFunctionType has a default constructor. + */ + explicit HashMap (int numberOfSlots = defaultHashTableSize, + HashFunctionType hashFunction = HashFunctionType()) + : hashFunctionToUse (hashFunction), totalNumItems (0) + { + hashSlots.insertMultiple (0, nullptr, numberOfSlots); + } + + /** Destructor. */ + ~HashMap() + { + clear(); + } + + //============================================================================== + /** Removes all values from the map. + Note that this will clear the content, but won't affect the number of slots (see + remapTable and getNumSlots). + */ + void clear() + { + const ScopedLockType sl (getLock()); + + for (int i = hashSlots.size(); --i >= 0;) + { + HashEntry* h = hashSlots.getUnchecked(i); + + while (h != nullptr) + { + const ScopedPointer deleter (h); + h = h->nextEntry; + } + + hashSlots.set (i, nullptr); + } + + totalNumItems = 0; + } + + //============================================================================== + /** Returns the current number of items in the map. */ + inline int size() const noexcept + { + return totalNumItems; + } + + /** Returns the value corresponding to a given key. + If the map doesn't contain the key, a default instance of the value type is returned. + @param keyToLookFor the key of the item being requested + */ + inline ValueType operator[] (KeyTypeParameter keyToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (const HashEntry* entry = hashSlots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry) + if (entry->key == keyToLookFor) + return entry->value; + + return ValueType(); + } + + //============================================================================== + /** Returns true if the map contains an item with the specied key. */ + bool contains (KeyTypeParameter keyToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (const HashEntry* entry = hashSlots.getUnchecked (generateHashFor (keyToLookFor)); entry != nullptr; entry = entry->nextEntry) + if (entry->key == keyToLookFor) + return true; + + return false; + } + + /** Returns true if the hash contains at least one occurrence of a given value. */ + bool containsValue (ValueTypeParameter valueToLookFor) const + { + const ScopedLockType sl (getLock()); + + for (int i = getNumSlots(); --i >= 0;) + for (const HashEntry* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry) + if (entry->value == valueToLookFor) + return true; + + return false; + } + + //============================================================================== + /** Adds or replaces an element in the hash-map. + If there's already an item with the given key, this will replace its value. Otherwise, a new item + will be added to the map. + */ + void set (KeyTypeParameter newKey, ValueTypeParameter newValue) + { + const ScopedLockType sl (getLock()); + const int hashIndex = generateHashFor (newKey); + + HashEntry* const firstEntry = hashSlots.getUnchecked (hashIndex); + + for (HashEntry* entry = firstEntry; entry != nullptr; entry = entry->nextEntry) + { + if (entry->key == newKey) + { + entry->value = newValue; + return; + } + } + + hashSlots.set (hashIndex, new HashEntry (newKey, newValue, firstEntry)); + ++totalNumItems; + + if (totalNumItems > (getNumSlots() * 3) / 2) + remapTable (getNumSlots() * 2); + } + + /** Removes an item with the given key. */ + void remove (KeyTypeParameter keyToRemove) + { + const ScopedLockType sl (getLock()); + const int hashIndex = generateHashFor (keyToRemove); + HashEntry* entry = hashSlots.getUnchecked (hashIndex); + HashEntry* previous = nullptr; + + while (entry != nullptr) + { + if (entry->key == keyToRemove) + { + const ScopedPointer deleter (entry); + + entry = entry->nextEntry; + + if (previous != nullptr) + previous->nextEntry = entry; + else + hashSlots.set (hashIndex, entry); + + --totalNumItems; + } + else + { + previous = entry; + entry = entry->nextEntry; + } + } + } + + /** Removes all items with the given value. */ + void removeValue (ValueTypeParameter valueToRemove) + { + const ScopedLockType sl (getLock()); + + for (int i = getNumSlots(); --i >= 0;) + { + HashEntry* entry = hashSlots.getUnchecked(i); + HashEntry* previous = nullptr; + + while (entry != nullptr) + { + if (entry->value == valueToRemove) + { + const ScopedPointer deleter (entry); + + entry = entry->nextEntry; + + if (previous != nullptr) + previous->nextEntry = entry; + else + hashSlots.set (i, entry); + + --totalNumItems; + } + else + { + previous = entry; + entry = entry->nextEntry; + } + } + } + } + + /** Remaps the hash-map to use a different number of slots for its hash function. + Each slot corresponds to a single hash-code, and each one can contain multiple items. + @see getNumSlots() + */ + void remapTable (int newNumberOfSlots) + { + HashMap newTable (newNumberOfSlots); + + for (int i = getNumSlots(); --i >= 0;) + for (const HashEntry* entry = hashSlots.getUnchecked(i); entry != nullptr; entry = entry->nextEntry) + newTable.set (entry->key, entry->value); + + swapWith (newTable); + } + + /** Returns the number of slots which are available for hashing. + Each slot corresponds to a single hash-code, and each one can contain multiple items. + @see getNumSlots() + */ + inline int getNumSlots() const noexcept + { + return hashSlots.size(); + } + + //============================================================================== + /** Efficiently swaps the contents of two hash-maps. */ + template + void swapWith (OtherHashMapType& otherHashMap) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherHashMapType::ScopedLockType lock2 (otherHashMap.getLock()); + + hashSlots.swapWith (otherHashMap.hashSlots); + std::swap (totalNumItems, otherHashMap.totalNumItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this structure. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return lock; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + +private: + //============================================================================== + class HashEntry + { + public: + HashEntry (KeyTypeParameter k, ValueTypeParameter val, HashEntry* const next) + : key (k), value (val), nextEntry (next) + {} + + const KeyType key; + ValueType value; + HashEntry* nextEntry; + + JUCE_DECLARE_NON_COPYABLE (HashEntry) + }; + +public: + //============================================================================== + /** Iterates over the items in a HashMap. + + To use it, repeatedly call next() until it returns false, e.g. + @code + HashMap myMap; + + HashMap::Iterator i (myMap); + + while (i.next()) + { + DBG (i.getKey() << " -> " << i.getValue()); + } + @endcode + + The order in which items are iterated bears no resemblence to the order in which + they were originally added! + + Obviously as soon as you call any non-const methods on the original hash-map, any + iterators that were created beforehand will cease to be valid, and should not be used. + + @see HashMap + */ + class Iterator + { + public: + //============================================================================== + Iterator (const HashMap& hashMapToIterate) + : hashMap (hashMapToIterate), entry (nullptr), index (0) + {} + + /** Moves to the next item, if one is available. + When this returns true, you can get the item's key and value using getKey() and + getValue(). If it returns false, the iteration has finished and you should stop. + */ + bool next() + { + if (entry != nullptr) + entry = entry->nextEntry; + + while (entry == nullptr) + { + if (index >= hashMap.getNumSlots()) + return false; + + entry = hashMap.hashSlots.getUnchecked (index++); + } + + return true; + } + + /** Returns the current item's key. + This should only be called when a call to next() has just returned true. + */ + KeyType getKey() const + { + return entry != nullptr ? entry->key : KeyType(); + } + + /** Returns the current item's value. + This should only be called when a call to next() has just returned true. + */ + ValueType getValue() const + { + return entry != nullptr ? entry->value : ValueType(); + } + + private: + //============================================================================== + const HashMap& hashMap; + HashEntry* entry; + int index; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Iterator) + }; + +private: + //============================================================================== + enum { defaultHashTableSize = 101 }; + friend class Iterator; + + HashFunctionType hashFunctionToUse; + Array hashSlots; + int totalNumItems; + TypeOfCriticalSectionToUse lock; + + int generateHashFor (KeyTypeParameter key) const + { + const int hash = hashFunctionToUse.generateHash (key, getNumSlots()); + jassert (isPositiveAndBelow (hash, getNumSlots())); // your hash function is generating out-of-range numbers! + return hash; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashMap) +}; + + +#endif // JUCE_HASHMAP_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_LinkedListPointer.h b/JuceLibraryCode/modules/juce_core/containers/juce_LinkedListPointer.h new file mode 100644 index 0000000..a27e521 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_LinkedListPointer.h @@ -0,0 +1,371 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LINKEDLISTPOINTER_H_INCLUDED +#define JUCE_LINKEDLISTPOINTER_H_INCLUDED + + +//============================================================================== +/** + Helps to manipulate singly-linked lists of objects. + + For objects that are designed to contain a pointer to the subsequent item in the + list, this class contains methods to deal with the list. To use it, the ObjectType + class that it points to must contain a LinkedListPointer called nextListItem, e.g. + + @code + struct MyObject + { + int x, y, z; + + // A linkable object must contain a member with this name and type, which must be + // accessible by the LinkedListPointer class. (This doesn't mean it has to be public - + // you could make your class a friend of a LinkedListPointer instead). + LinkedListPointer nextListItem; + }; + + LinkedListPointer myList; + myList.append (new MyObject()); + myList.append (new MyObject()); + + int numItems = myList.size(); // returns 2 + MyObject* lastInList = myList.getLast(); + @endcode +*/ +template +class LinkedListPointer +{ +public: + //============================================================================== + /** Creates a null pointer to an empty list. */ + LinkedListPointer() noexcept + : item (nullptr) + { + } + + /** Creates a pointer to a list whose head is the item provided. */ + explicit LinkedListPointer (ObjectType* const headItem) noexcept + : item (headItem) + { + } + + /** Sets this pointer to point to a new list. */ + LinkedListPointer& operator= (ObjectType* const newItem) noexcept + { + item = newItem; + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + LinkedListPointer (LinkedListPointer&& other) noexcept + : item (other.item) + { + other.item = nullptr; + } + + LinkedListPointer& operator= (LinkedListPointer&& other) noexcept + { + jassert (this != &other); // hopefully the compiler should make this situation impossible! + + item = other.item; + other.item = nullptr; + return *this; + } + #endif + + //============================================================================== + /** Returns the item which this pointer points to. */ + inline operator ObjectType*() const noexcept + { + return item; + } + + /** Returns the item which this pointer points to. */ + inline ObjectType* get() const noexcept + { + return item; + } + + /** Returns the last item in the list which this pointer points to. + This will iterate the list and return the last item found. Obviously the speed + of this operation will be proportional to the size of the list. If the list is + empty the return value will be this object. + If you're planning on appending a number of items to your list, it's much more + efficient to use the Appender class than to repeatedly call getLast() to find the end. + */ + LinkedListPointer& getLast() noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the number of items in the list. + Obviously with a simple linked list, getting the size involves iterating the list, so + this can be a lengthy operation - be careful when using this method in your code. + */ + int size() const noexcept + { + int total = 0; + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + ++total; + + return total; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + LinkedListPointer& operator[] (int index) noexcept + { + LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns the item at a given index in the list. + Since the only way to find an item is to iterate the list, this operation can obviously + be slow, depending on its size, so you should be careful when using this in algorithms. + */ + const LinkedListPointer& operator[] (int index) const noexcept + { + const LinkedListPointer* l = this; + + while (--index >= 0 && l->item != nullptr) + l = &(l->item->nextListItem); + + return *l; + } + + /** Returns true if the list contains the given item. */ + bool contains (const ObjectType* const itemToLookFor) const noexcept + { + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + if (itemToLookFor == i) + return true; + + return false; + } + + //============================================================================== + /** Inserts an item into the list, placing it before the item that this pointer + currently points to. + */ + void insertNext (ObjectType* const newItem) + { + jassert (newItem != nullptr); + jassert (newItem->nextListItem == nullptr); + newItem->nextListItem = item; + item = newItem; + } + + /** Inserts an item at a numeric index in the list. + Obviously this will involve iterating the list to find the item at the given index, + so be careful about the impact this may have on execution time. + */ + void insertAtIndex (int index, ObjectType* newItem) + { + jassert (newItem != nullptr); + LinkedListPointer* l = this; + + while (index != 0 && l->item != nullptr) + { + l = &(l->item->nextListItem); + --index; + } + + l->insertNext (newItem); + } + + /** Replaces the object that this pointer points to, appending the rest of the list to + the new object, and returning the old one. + */ + ObjectType* replaceNext (ObjectType* const newItem) noexcept + { + jassert (newItem != nullptr); + jassert (newItem->nextListItem == nullptr); + + ObjectType* const oldItem = item; + item = newItem; + item->nextListItem = oldItem->nextListItem.item; + oldItem->nextListItem.item = nullptr; + return oldItem; + } + + /** Adds an item to the end of the list. + + This operation involves iterating the whole list, so can be slow - if you need to + append a number of items to your list, it's much more efficient to use the Appender + class than to repeatedly call append(). + */ + void append (ObjectType* const newItem) + { + getLast().item = newItem; + } + + /** Creates copies of all the items in another list and adds them to this one. + This will use the ObjectType's copy constructor to try to create copies of each + item in the other list, and appends them to this list. + */ + void addCopyOfList (const LinkedListPointer& other) + { + LinkedListPointer* insertPoint = this; + + for (ObjectType* i = other.item; i != nullptr; i = i->nextListItem) + { + insertPoint->insertNext (new ObjectType (*i)); + insertPoint = &(insertPoint->item->nextListItem); + } + } + + /** Removes the head item from the list. + This won't delete the object that is removed, but returns it, so the caller can + delete it if necessary. + */ + ObjectType* removeNext() noexcept + { + ObjectType* const oldItem = item; + + if (oldItem != nullptr) + { + item = oldItem->nextListItem; + oldItem->nextListItem.item = nullptr; + } + + return oldItem; + } + + /** Removes a specific item from the list. + Note that this will not delete the item, it simply unlinks it from the list. + */ + void remove (ObjectType* const itemToRemove) + { + if (LinkedListPointer* const l = findPointerTo (itemToRemove)) + l->removeNext(); + } + + /** Iterates the list, calling the delete operator on all of its elements and + leaving this pointer empty. + */ + void deleteAll() + { + while (item != nullptr) + { + ObjectType* const oldItem = item; + item = oldItem->nextListItem; + delete oldItem; + } + } + + /** Finds a pointer to a given item. + If the item is found in the list, this returns the pointer that points to it. If + the item isn't found, this returns null. + */ + LinkedListPointer* findPointerTo (ObjectType* const itemToLookFor) noexcept + { + LinkedListPointer* l = this; + + while (l->item != nullptr) + { + if (l->item == itemToLookFor) + return l; + + l = &(l->item->nextListItem); + } + + return nullptr; + } + + /** Copies the items in the list to an array. + The destArray must contain enough elements to hold the entire list - no checks are + made for this! + */ + void copyToArray (ObjectType** destArray) const noexcept + { + jassert (destArray != nullptr); + + for (ObjectType* i = item; i != nullptr; i = i->nextListItem) + *destArray++ = i; + } + + /** Swaps this pointer with another one */ + void swapWith (LinkedListPointer& other) noexcept + { + std::swap (item, other.item); + } + + //============================================================================== + /** + Allows efficient repeated insertions into a list. + + You can create an Appender object which points to the last element in your + list, and then repeatedly call Appender::append() to add items to the end + of the list in O(1) time. + */ + class Appender + { + public: + /** Creates an appender which will add items to the given list. + */ + Appender (LinkedListPointer& endOfListPointer) noexcept + : endOfList (&endOfListPointer) + { + // This can only be used to add to the end of a list. + jassert (endOfListPointer.item == nullptr); + } + + /** Appends an item to the list. */ + void append (ObjectType* const newItem) noexcept + { + *endOfList = newItem; + endOfList = &(newItem->nextListItem); + } + + private: + LinkedListPointer* endOfList; + + JUCE_DECLARE_NON_COPYABLE (Appender) + }; + +private: + //============================================================================== + ObjectType* item; + + JUCE_DECLARE_NON_COPYABLE (LinkedListPointer) +}; + + +#endif // JUCE_LINKEDLISTPOINTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.cpp new file mode 100644 index 0000000..504ab78 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.cpp @@ -0,0 +1,270 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +struct NamedValueSet::NamedValue +{ + NamedValue() noexcept {} + NamedValue (Identifier n, const var& v) : name (n), value (v) {} + NamedValue (const NamedValue& other) : name (other.name), value (other.value) {} + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValue (NamedValue&& other) noexcept + : name (static_cast (other.name)), + value (static_cast (other.value)) + { + } + + NamedValue (Identifier n, var&& v) : name (n), value (static_cast (v)) + { + } + + NamedValue& operator= (NamedValue&& other) noexcept + { + name = static_cast (other.name); + value = static_cast (other.value); + return *this; + } + #endif + + bool operator== (const NamedValue& other) const noexcept { return name == other.name && value == other.value; } + bool operator!= (const NamedValue& other) const noexcept { return ! operator== (other); } + + Identifier name; + var value; +}; + +//============================================================================== +NamedValueSet::NamedValueSet() noexcept +{ +} + +NamedValueSet::NamedValueSet (const NamedValueSet& other) + : values (other.values) +{ +} + +NamedValueSet& NamedValueSet::operator= (const NamedValueSet& other) +{ + clear(); + values = other.values; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +NamedValueSet::NamedValueSet (NamedValueSet&& other) noexcept + : values (static_cast &&> (other.values)) +{ +} + +NamedValueSet& NamedValueSet::operator= (NamedValueSet&& other) noexcept +{ + other.values.swapWith (values); + return *this; +} +#endif + +NamedValueSet::~NamedValueSet() noexcept +{ +} + +void NamedValueSet::clear() +{ + values.clear(); +} + +bool NamedValueSet::operator== (const NamedValueSet& other) const +{ + return values == other.values; +} + +bool NamedValueSet::operator!= (const NamedValueSet& other) const +{ + return ! operator== (other); +} + +int NamedValueSet::size() const noexcept +{ + return values.size(); +} + +const var& NamedValueSet::operator[] (const Identifier& name) const noexcept +{ + if (const var* v = getVarPointer (name)) + return *v; + + return var::null; +} + +var NamedValueSet::getWithDefault (const Identifier& name, const var& defaultReturnValue) const +{ + if (const var* const v = getVarPointer (name)) + return *v; + + return defaultReturnValue; +} + +var* NamedValueSet::getVarPointer (const Identifier& name) const noexcept +{ + for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) + if (i->name == name) + return &(i->value); + + return nullptr; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +bool NamedValueSet::set (Identifier name, var&& newValue) +{ + if (var* const v = getVarPointer (name)) + { + if (v->equalsWithSameType (newValue)) + return false; + + *v = static_cast (newValue); + return true; + } + + values.add (NamedValue (name, static_cast (newValue))); + return true; +} +#endif + +bool NamedValueSet::set (Identifier name, const var& newValue) +{ + if (var* const v = getVarPointer (name)) + { + if (v->equalsWithSameType (newValue)) + return false; + + *v = newValue; + return true; + } + + values.add (NamedValue (name, newValue)); + return true; +} + +bool NamedValueSet::contains (const Identifier& name) const noexcept +{ + return getVarPointer (name) != nullptr; +} + +int NamedValueSet::indexOf (const Identifier& name) const noexcept +{ + const int numValues = values.size(); + + for (int i = 0; i < numValues; ++i) + if (values.getReference(i).name == name) + return i; + + return -1; +} + +bool NamedValueSet::remove (const Identifier& name) +{ + const int numValues = values.size(); + + for (int i = 0; i < numValues; ++i) + { + if (values.getReference(i).name == name) + { + values.remove (i); + return true; + } + } + + return false; +} + +Identifier NamedValueSet::getName (const int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return values.getReference (index).name; + + jassertfalse; + return Identifier(); +} + +const var& NamedValueSet::getValueAt (const int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return values.getReference (index).value; + + jassertfalse; + return var::null; +} + +var* NamedValueSet::getVarPointerAt (int index) const noexcept +{ + if (isPositiveAndBelow (index, values.size())) + return &(values.getReference (index).value); + + return nullptr; +} + +void NamedValueSet::setFromXmlAttributes (const XmlElement& xml) +{ + values.clearQuick(); + + for (const XmlElement::XmlAttributeNode* att = xml.attributes; att != nullptr; att = att->nextListItem) + { + if (att->name.toString().startsWith ("base64:")) + { + MemoryBlock mb; + + if (mb.fromBase64Encoding (att->value)) + { + values.add (NamedValue (att->name.toString().substring (7), var (mb))); + continue; + } + } + + values.add (NamedValue (att->name, var (att->value))); + } +} + +void NamedValueSet::copyToXmlAttributes (XmlElement& xml) const +{ + for (NamedValue* e = values.end(), *i = values.begin(); i != e; ++i) + { + if (const MemoryBlock* mb = i->value.getBinaryData()) + { + xml.setAttribute ("base64:" + i->name.toString(), mb->toBase64Encoding()); + } + else + { + // These types can't be stored as XML! + jassert (! i->value.isObject()); + jassert (! i->value.isMethod()); + jassert (! i->value.isArray()); + + xml.setAttribute (i->name.toString(), + i->value.toString()); + } + } +} diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.h b/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.h new file mode 100644 index 0000000..e4a98ed --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_NamedValueSet.h @@ -0,0 +1,145 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NAMEDVALUESET_H_INCLUDED +#define JUCE_NAMEDVALUESET_H_INCLUDED + + +//============================================================================== +/** Holds a set of named var objects. + + This can be used as a basic structure to hold a set of var object, which can + be retrieved by using their identifier. +*/ +class JUCE_API NamedValueSet +{ +public: + /** Creates an empty set. */ + NamedValueSet() noexcept; + + /** Creates a copy of another set. */ + NamedValueSet (const NamedValueSet&); + + /** Replaces this set with a copy of another set. */ + NamedValueSet& operator= (const NamedValueSet&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + NamedValueSet (NamedValueSet&&) noexcept; + NamedValueSet& operator= (NamedValueSet&&) noexcept; + #endif + + /** Destructor. */ + ~NamedValueSet() noexcept; + + bool operator== (const NamedValueSet&) const; + bool operator!= (const NamedValueSet&) const; + + //============================================================================== + /** Returns the total number of values that the set contains. */ + int size() const noexcept; + + /** Returns the value of a named item. + If the name isn't found, this will return a void variant. + @see getProperty + */ + const var& operator[] (const Identifier& name) const noexcept; + + /** Tries to return the named value, but if no such value is found, this will + instead return the supplied default value. + */ + var getWithDefault (const Identifier& name, const var& defaultReturnValue) const; + + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the value passed-in. + */ + bool set (Identifier name, const var& newValue); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Changes or adds a named value. + @returns true if a value was changed or added; false if the + value was already set the value passed-in. + */ + bool set (Identifier name, var&& newValue); + #endif + + /** Returns true if the set contains an item with the specified name. */ + bool contains (const Identifier& name) const noexcept; + + /** Removes a value from the set. + @returns true if a value was removed; false if there was no value + with the name that was given. + */ + bool remove (const Identifier& name); + + /** Returns the name of the value at a given index. + The index must be between 0 and size() - 1. + */ + Identifier getName (int index) const noexcept; + + /** Returns a pointer to the var that holds a named value, or null if there is + no value with this name. + + Do not use this method unless you really need access to the internal var object + for some reason - for normal reading and writing always prefer operator[]() and set(). + */ + var* getVarPointer (const Identifier& name) const noexcept; + + /** Returns the value of the item at a given index. + The index must be between 0 and size() - 1. + */ + const var& getValueAt (int index) const noexcept; + + /** Returns the value of the item at a given index. + The index must be between 0 and size() - 1, or this will return a nullptr + */ + var* getVarPointerAt (int index) const noexcept; + + /** Returns the index of the given name, or -1 if it's not found. */ + int indexOf (const Identifier& name) const noexcept; + + /** Removes all values. */ + void clear(); + + //============================================================================== + /** Sets properties to the values of all of an XML element's attributes. */ + void setFromXmlAttributes (const XmlElement& xml); + + /** Sets attributes in an XML element corresponding to each of this object's + properties. + */ + void copyToXmlAttributes (XmlElement& xml) const; + +private: + //============================================================================== + struct NamedValue; + Array values; +}; + + +#endif // JUCE_NAMEDVALUESET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h b/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h new file mode 100644 index 0000000..be7448c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_OwnedArray.h @@ -0,0 +1,897 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OWNEDARRAY_H_INCLUDED +#define JUCE_OWNEDARRAY_H_INCLUDED + + +//============================================================================== +/** An array designed for holding objects. + + This holds a list of pointers to objects, and will automatically + delete the objects when they are removed from the array, or when the + array is itself deleted. + + Declare it in the form: OwnedArray + + ..and then add new objects, e.g. myOwnedArray.add (new MyObjectClass()); + + After adding objects, they are 'owned' by the array and will be deleted when + removed or replaced. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, ReferenceCountedArray, StringArray, CriticalSection +*/ +template + +class OwnedArray +{ +public: + //============================================================================== + /** Creates an empty array. */ + OwnedArray() noexcept + : numUsed (0) + { + } + + /** Deletes the array and also deletes any objects inside it. + + To get rid of the array without deleting its objects, use its + clear (false) method before deleting it. + */ + ~OwnedArray() + { + deleteAllObjects(); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + OwnedArray (OwnedArray&& other) noexcept + : data (static_cast &&> (other.data)), + numUsed (other.numUsed) + { + other.numUsed = 0; + } + + OwnedArray& operator= (OwnedArray&& other) noexcept + { + const ScopedLockType lock (getLock()); + deleteAllObjects(); + + data = static_cast &&> (other.data); + numUsed = other.numUsed; + other.numUsed = 0; + return *this; + } + #endif + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clear (bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + + if (deleteObjects) + deleteAllObjects(); + + data.setAllocatedSize (0); + numUsed = 0; + } + + //============================================================================== + /** Clears the array, optionally deleting the objects inside it first. */ + void clearQuick (bool deleteObjects) + { + const ScopedLockType lock (getLock()); + + if (deleteObjects) + deleteAllObjects(); + + numUsed = 0; + } + + //============================================================================== + /** Returns the number of items currently in the array. + @see operator[] + */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns a pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClass* operator[] (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return nullptr; + } + + /** Returns a pointer to the object at this index in the array, without checking whether the index is in-range. + + This is a faster and less safe version of operator[] which doesn't check the index passed in, so + it can be used when you're sure the index is always going to be legal. + */ + inline ObjectClass* getUnchecked (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns a pointer to the first object in the array. + + This will return a null pointer if the array's empty. + @see getLast + */ + inline ObjectClass* getFirst() const noexcept + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [0]; + } + + return nullptr; + } + + /** Returns a pointer to the last object in the array. + + This will return a null pointer if the array's empty. + @see getFirst + */ + inline ObjectClass* getLast() const noexcept + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [numUsed - 1]; + } + + return nullptr; + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ObjectClass** getRawDataPointer() noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** end() const noexcept + { + #if JUCE_DEBUG + if (data.elements == nullptr || numUsed <= 0) // (to keep static analysers happy) + return data.elements; + #endif + + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of an object which might be in the array. + + @param objectToLookFor the object to look for + @returns the index at which the object was found, or -1 if it's not found + */ + int indexOf (const ObjectClass* objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return static_cast (e - data.elements.getData()); + + return -1; + } + + /** Returns true if the array contains a specified object. + + @param objectToLookFor the object to look for + @returns true if the object is in the array + */ + bool contains (const ObjectClass* objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass* const* e = data.elements.getData(); + ObjectClass* const* const end_ = e + numUsed; + + for (; e != end_; ++e) + if (objectToLookFor == *e) + return true; + + return false; + } + + //============================================================================== + /** Appends a new object to the end of the array. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + Also be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param newObject the new object to add to the array + @returns the new object that was added + @see set, insert, addIfNotAlreadyThere, addSorted + */ + ObjectClass* add (ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = newObject; + return newObject; + } + + /** Inserts a new object into the array at the given index. + + Note that the this object will be deleted by the OwnedArray when it + is removed, so be careful not to delete it somewhere else. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToInsertAt the index at which the new element should be inserted + @param newObject the new object to add to the array + @returns the new object that was added + @see add, addSorted, addIfNotAlreadyThere, set + */ + ObjectClass* insert (int indexToInsertAt, ObjectClass* newObject) noexcept + { + if (indexToInsertAt < 0) + return add (newObject); + + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + ObjectClass** const e = data.elements + indexToInsertAt; + const int numToMove = numUsed - indexToInsertAt; + + if (numToMove > 0) + memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); + + *e = newObject; + ++numUsed; + return newObject; + } + + /** Inserts an array of values into this array at a given position. + + If the index is less than 0 or greater than the size of the array, the + new elements will be added to the end of the array. + Otherwise, they will be inserted into the array, moving all the later elements + along to make room. + + @param indexToInsertAt the index at which the first new element should be inserted + @param newObjects the new values to add to the array + @param numberOfElements how many items are in the array + @see insert, add, addSorted, set + */ + void insertArray (int indexToInsertAt, + ObjectClass* const* newObjects, + int numberOfElements) + { + if (numberOfElements > 0) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + numberOfElements); + ObjectClass** insertPos = data.elements; + + if (isPositiveAndBelow (indexToInsertAt, numUsed)) + { + insertPos += indexToInsertAt; + const size_t numberToMove = (size_t) (numUsed - indexToInsertAt); + memmove (insertPos + numberOfElements, insertPos, numberToMove * sizeof (ObjectClass*)); + } + else + { + insertPos += numUsed; + } + + numUsed += numberOfElements; + + while (--numberOfElements >= 0) + *insertPos++ = *newObjects++; + } + } + + /** Appends a new object at the end of the array as long as the array doesn't + already contain it. + + If the array already contains a matching object, nothing will be done. + + @param newObject the new object to add to the array + @returns the new object that was added + */ + ObjectClass* addIfNotAlreadyThere (ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + + if (! contains (newObject)) + add (newObject); + + return newObject; + } + + /** Replaces an object in the array with a different one. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the new object is added to the end of the array. + + Be careful not to add the same object to the array more than once, + as this will obviously cause deletion of dangling pointers. + + @param indexToChange the index whose value you want to change + @param newObject the new value to set for this index. + @param deleteOldElement whether to delete the object that's being replaced with the new one + @see add, insert, remove + */ + ObjectClass* set (int indexToChange, ObjectClass* newObject, bool deleteOldElement = true) + { + if (indexToChange >= 0) + { + ScopedPointer toDelete; + + { + const ScopedLockType lock (getLock()); + + if (indexToChange < numUsed) + { + if (deleteOldElement) + { + toDelete = data.elements [indexToChange]; + + if (toDelete == newObject) + toDelete.release(); + } + + data.elements [indexToChange] = newObject; + } + else + { + data.ensureAllocatedSize (numUsed + 1); + data.elements [numUsed++] = newObject; + } + } + } + else + { + jassertfalse; // you're trying to set an object at a negative index, which doesn't have + // any effect - but since the object is not being added, it may be leaking.. + } + + return newObject; + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addArray (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + while (--numElementsToAdd >= 0) + { + data.elements [numUsed] = arrayToAddFrom.getUnchecked (startIndex++); + ++numUsed; + } + } + + /** Adds copies of the elements in another array to the end of this array. + + The other array must be either an OwnedArray of a compatible type of object, or an Array + containing pointers to the same kind of object. The objects involved must provide + a copy constructor, and this will be used to create new copies of each element, and + add them to this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addCopiesOf (const OtherArrayType& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) + { + const typename OtherArrayType::ScopedLockType lock1 (arrayToAddFrom.getLock()); + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + data.ensureAllocatedSize (numUsed + numElementsToAdd); + jassert (numElementsToAdd <= 0 || data.elements != nullptr); + + while (--numElementsToAdd >= 0) + data.elements [numUsed++] = createCopyIfNotNull (arrayToAddFrom.getUnchecked (startIndex++)); + } + + /** Inserts a new object into the array assuming that the array is sorted. + + This will use a comparator to find the position at which the new object + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort method + for details about this object's structure + @param newObject the new object to insert to the array + @returns the index at which the new object was added + @see add, sort, indexOfSorted + */ + template + int addSorted (ElementComparator& comparator, ObjectClass* const newObject) noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + insert (index, newObject); + return index; + } + + /** Finds the index of an object in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param objectToLookFor the object to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, const ObjectClass* const objectToLookFor) const noexcept + { + (void) comparator; + const ScopedLockType lock (getLock()); + int s = 0, e = numUsed; + + while (s < e) + { + if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + break; + + if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + + return -1; + } + + //============================================================================== + /** Removes an object from the array. + + This will remove the object at a given index (optionally also + deleting it) and move back all the subsequent objects to close the gap. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @param deleteObject whether to delete the object that is removed + @see removeObject, removeRange + */ + void remove (int indexToRemove, bool deleteObject = true) + { + ScopedPointer toDelete; + + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (deleteObject) + toDelete = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + } + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + /** Removes and returns an object from the array without deleting it. + + This will remove the object at a given index and return it, moving back all + the subsequent objects to close the gap. If the index passed in is out-of-range, + nothing will happen. + + @param indexToRemove the index of the element to remove + @see remove, removeObject, removeRange + */ + ObjectClass* removeAndReturn (int indexToRemove) + { + ObjectClass* removedItem = nullptr; + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + removedItem = *e; + + --numUsed; + const int numToShift = numUsed - indexToRemove; + + if (numToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + return removedItem; + } + + /** Removes a specified object from the array. + + If the item isn't found, no action is taken. + + @param objectToRemove the object to try to remove + @param deleteObject whether to delete the object (if it's found) + @see remove, removeRange + */ + void removeObject (const ObjectClass* objectToRemove, bool deleteObject = true) + { + const ScopedLockType lock (getLock()); + ObjectClass** const e = data.elements.getData(); + + for (int i = 0; i < numUsed; ++i) + { + if (objectToRemove == e[i]) + { + remove (i, deleteObject); + break; + } + } + } + + /** Removes a range of objects from the array. + + This will remove a set of objects, starting from the given index, + and move any subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first object to remove + @param numberToRemove how many objects should be removed + @param deleteObjects whether to delete the objects that get removed + @see remove, removeObject + */ + void removeRange (int startIndex, int numberToRemove, bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); + startIndex = jlimit (0, numUsed, startIndex); + + if (endIndex > startIndex) + { + if (deleteObjects) + { + for (int i = startIndex; i < endIndex; ++i) + { + ContainerDeletePolicy::destroy (data.elements [i]); + data.elements [i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) + } + } + + const int rangeSize = endIndex - startIndex; + ObjectClass** e = data.elements + startIndex; + int numToShift = numUsed - endIndex; + numUsed -= rangeSize; + + while (--numToShift >= 0) + { + *e = e [rangeSize]; + ++e; + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes the last n objects from the array. + + @param howManyToRemove how many objects to remove from the end of the array + @param deleteObjects whether to also delete the objects that are removed + @see remove, removeObject, removeRange + */ + void removeLast (int howManyToRemove = 1, + bool deleteObjects = true) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove >= numUsed) + clear (deleteObjects); + else + removeRange (numUsed - howManyToRemove, howManyToRemove, deleteObjects); + } + + /** Swaps a pair of objects in the array. + + If either of the indexes passed in is out-of-range, nothing will happen, + otherwise the two objects at these positions will be exchanged. + */ + void swap (int index1, + int index2) noexcept + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the objects to a different position. + + This will move the object to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the object to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this object to end up. If this + is less than zero, it will be moved to the end of the array + */ + void move (int currentIndex, int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + ObjectClass* const value = data.elements [currentIndex]; + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); + } + + data.elements [newIndex] = value; + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock()); + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType* first, ElementType* second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + @see sortArray, indexOfSorted + */ + template + void sort (ElementComparator& comparator, + bool retainOrderOfEquivalentItems = false) const noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + + //============================================================================== + #ifndef DOXYGEN + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (OwnedArray& other) noexcept, { swapWith (other); }) + #endif + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + void deleteAllObjects() + { + while (numUsed > 0) + ContainerDeletePolicy::destroy (data.elements [--numUsed]); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OwnedArray) +}; + + +#endif // JUCE_OWNEDARRAY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp new file mode 100644 index 0000000..6b02baf --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.cpp @@ -0,0 +1,219 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +PropertySet::PropertySet (const bool ignoreCaseOfKeyNames) + : properties (ignoreCaseOfKeyNames), + fallbackProperties (nullptr), + ignoreCaseOfKeys (ignoreCaseOfKeyNames) +{ +} + +PropertySet::PropertySet (const PropertySet& other) + : properties (other.properties), + fallbackProperties (other.fallbackProperties), + ignoreCaseOfKeys (other.ignoreCaseOfKeys) +{ +} + +PropertySet& PropertySet::operator= (const PropertySet& other) +{ + properties = other.properties; + fallbackProperties = other.fallbackProperties; + ignoreCaseOfKeys = other.ignoreCaseOfKeys; + + propertyChanged(); + return *this; +} + +PropertySet::~PropertySet() +{ +} + +void PropertySet::clear() +{ + const ScopedLock sl (lock); + + if (properties.size() > 0) + { + properties.clear(); + propertyChanged(); + } +} + +String PropertySet::getValue (StringRef keyName, const String& defaultValue) const noexcept +{ + const ScopedLock sl (lock); + + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index]; + + return fallbackProperties != nullptr ? fallbackProperties->getValue (keyName, defaultValue) + : defaultValue; +} + +int PropertySet::getIntValue (StringRef keyName, const int defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index].getIntValue(); + + return fallbackProperties != nullptr ? fallbackProperties->getIntValue (keyName, defaultValue) + : defaultValue; +} + +double PropertySet::getDoubleValue (StringRef keyName, const double defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues()[index].getDoubleValue(); + + return fallbackProperties != nullptr ? fallbackProperties->getDoubleValue (keyName, defaultValue) + : defaultValue; +} + +bool PropertySet::getBoolValue (StringRef keyName, const bool defaultValue) const noexcept +{ + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + return properties.getAllValues() [index].getIntValue() != 0; + + return fallbackProperties != nullptr ? fallbackProperties->getBoolValue (keyName, defaultValue) + : defaultValue; +} + +XmlElement* PropertySet::getXmlValue (StringRef keyName) const +{ + return XmlDocument::parse (getValue (keyName)); +} + +void PropertySet::setValue (const String& keyName, const var& v) +{ + jassert (keyName.isNotEmpty()); // shouldn't use an empty key name! + + if (keyName.isNotEmpty()) + { + const String value (v.toString()); + const ScopedLock sl (lock); + + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index < 0 || properties.getAllValues() [index] != value) + { + properties.set (keyName, value); + propertyChanged(); + } + } +} + +void PropertySet::removeValue (StringRef keyName) +{ + if (keyName.isNotEmpty()) + { + const ScopedLock sl (lock); + const int index = properties.getAllKeys().indexOf (keyName, ignoreCaseOfKeys); + + if (index >= 0) + { + properties.remove (keyName); + propertyChanged(); + } + } +} + +void PropertySet::setValue (const String& keyName, const XmlElement* const xml) +{ + setValue (keyName, xml == nullptr ? var() + : var (xml->createDocument ("", true))); +} + +bool PropertySet::containsKey (StringRef keyName) const noexcept +{ + const ScopedLock sl (lock); + return properties.getAllKeys().contains (keyName, ignoreCaseOfKeys); +} + +void PropertySet::addAllPropertiesFrom (const PropertySet& source) +{ + const ScopedLock sl (source.getLock()); + + for (int i = 0; i < source.properties.size(); ++i) + setValue (source.properties.getAllKeys() [i], + source.properties.getAllValues() [i]); +} + +void PropertySet::setFallbackPropertySet (PropertySet* fallbackProperties_) noexcept +{ + const ScopedLock sl (lock); + fallbackProperties = fallbackProperties_; +} + +XmlElement* PropertySet::createXml (const String& nodeName) const +{ + const ScopedLock sl (lock); + XmlElement* const xml = new XmlElement (nodeName); + + for (int i = 0; i < properties.getAllKeys().size(); ++i) + { + XmlElement* const e = xml->createNewChildElement ("VALUE"); + e->setAttribute ("name", properties.getAllKeys()[i]); + e->setAttribute ("val", properties.getAllValues()[i]); + } + + return xml; +} + +void PropertySet::restoreFromXml (const XmlElement& xml) +{ + const ScopedLock sl (lock); + clear(); + + forEachXmlChildElementWithTagName (xml, e, "VALUE") + { + if (e->hasAttribute ("name") + && e->hasAttribute ("val")) + { + properties.set (e->getStringAttribute ("name"), + e->getStringAttribute ("val")); + } + } + + if (properties.size() > 0) + propertyChanged(); +} + +void PropertySet::propertyChanged() +{ +} diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h new file mode 100644 index 0000000..2a4ecb1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_PropertySet.h @@ -0,0 +1,211 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PROPERTYSET_H_INCLUDED +#define JUCE_PROPERTYSET_H_INCLUDED + + +//============================================================================== +/** + A set of named property values, which can be strings, integers, floating point, etc. + + Effectively, this just wraps a StringPairArray in an interface that makes it easier + to load and save types other than strings. + + See the PropertiesFile class for a subclass of this, which automatically broadcasts change + messages and saves/loads the list from a file. +*/ +class JUCE_API PropertySet +{ +public: + //============================================================================== + /** Creates an empty PropertySet. + @param ignoreCaseOfKeyNames if true, the names of properties are compared in a + case-insensitive way + */ + PropertySet (bool ignoreCaseOfKeyNames = false); + + /** Creates a copy of another PropertySet. */ + PropertySet (const PropertySet& other); + + /** Copies another PropertySet over this one. */ + PropertySet& operator= (const PropertySet& other); + + /** Destructor. */ + virtual ~PropertySet(); + + //============================================================================== + /** Returns one of the properties as a string. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + String getValue (StringRef keyName, const String& defaultReturnValue = String()) const noexcept; + + /** Returns one of the properties as an integer. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + int getIntValue (StringRef keyName, int defaultReturnValue = 0) const noexcept; + + /** Returns one of the properties as an double. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + double getDoubleValue (StringRef keyName, double defaultReturnValue = 0.0) const noexcept; + + /** Returns one of the properties as an boolean. + + The result will be true if the string found for this key name can be parsed as a non-zero + integer. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + @param defaultReturnValue a value to return if the named property doesn't actually exist + */ + bool getBoolValue (StringRef keyName, bool defaultReturnValue = false) const noexcept; + + /** Returns one of the properties as an XML element. + + The result will a new XMLElement object that the caller must delete. If may return nullptr + if the key isn't found, or if the entry contains an string that isn't valid XML. + + If the value isn't found in this set, then this will look for it in a fallback + property set (if you've specified one with the setFallbackPropertySet() method), + and if it can't find one there, it'll return the default value passed-in. + + @param keyName the name of the property to retrieve + */ + XmlElement* getXmlValue (StringRef keyName) const; + + //============================================================================== + /** Sets a named property. + + @param keyName the name of the property to set. (This mustn't be an empty string) + @param value the new value to set it to + */ + void setValue (const String& keyName, const var& value); + + /** Sets a named property to an XML element. + + @param keyName the name of the property to set. (This mustn't be an empty string) + @param xml the new element to set it to. If this is zero, the value will be set to + an empty string + @see getXmlValue + */ + void setValue (const String& keyName, const XmlElement* xml); + + /** This copies all the values from a source PropertySet to this one. + This won't remove any existing settings, it just adds any that it finds in the source set. + */ + void addAllPropertiesFrom (const PropertySet& source); + + //============================================================================== + /** Deletes a property. + @param keyName the name of the property to delete. (This mustn't be an empty string) + */ + void removeValue (StringRef keyName); + + /** Returns true if the properies include the given key. */ + bool containsKey (StringRef keyName) const noexcept; + + /** Removes all values. */ + void clear(); + + //============================================================================== + /** Returns the keys/value pair array containing all the properties. */ + StringPairArray& getAllProperties() noexcept { return properties; } + + /** Returns the lock used when reading or writing to this set */ + const CriticalSection& getLock() const noexcept { return lock; } + + //============================================================================== + /** Returns an XML element which encapsulates all the items in this property set. + The string parameter is the tag name that should be used for the node. + @see restoreFromXml + */ + XmlElement* createXml (const String& nodeName) const; + + /** Reloads a set of properties that were previously stored as XML. + The node passed in must have been created by the createXml() method. + @see createXml + */ + void restoreFromXml (const XmlElement& xml); + + //============================================================================== + /** Sets up a second PopertySet that will be used to look up any values that aren't + set in this one. + + If you set this up to be a pointer to a second property set, then whenever one + of the getValue() methods fails to find an entry in this set, it will look up that + value in the fallback set, and if it finds it, it will return that. + + Make sure that you don't delete the fallback set while it's still being used by + another set! To remove the fallback set, just call this method with a null pointer. + + @see getFallbackPropertySet + */ + void setFallbackPropertySet (PropertySet* fallbackProperties) noexcept; + + /** Returns the fallback property set. + @see setFallbackPropertySet + */ + PropertySet* getFallbackPropertySet() const noexcept { return fallbackProperties; } + +protected: + /** Subclasses can override this to be told when one of the properies has been changed. */ + virtual void propertyChanged(); + +private: + StringPairArray properties; + PropertySet* fallbackProperties; + CriticalSection lock; + bool ignoreCaseOfKeys; + + JUCE_LEAK_DETECTOR (PropertySet) +}; + + +#endif // JUCE_PROPERTYSET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ReferenceCountedArray.h b/JuceLibraryCode/modules/juce_core/containers/juce_ReferenceCountedArray.h new file mode 100644 index 0000000..6b02c3e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ReferenceCountedArray.h @@ -0,0 +1,897 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED +#define JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED + + +//============================================================================== +/** + Holds a list of objects derived from ReferenceCountedObject, or which implement basic + reference-count handling methods. + + The template parameter specifies the class of the object you want to point to - the easiest + way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject + or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable + class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and + decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods + should behave. + + A ReferenceCountedArray holds objects derived from ReferenceCountedObject, + and takes care of incrementing and decrementing their ref counts when they + are added and removed from the array. + + To make all the array's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, OwnedArray, StringArray +*/ +template +class ReferenceCountedArray +{ +public: + typedef ReferenceCountedObjectPtr ObjectClassPtr; + + //============================================================================== + /** Creates an empty array. + @see ReferenceCountedObject, Array, OwnedArray + */ + ReferenceCountedArray() noexcept + : numUsed (0) + { + } + + /** Creates a copy of another array */ + ReferenceCountedArray (const ReferenceCountedArray& other) noexcept + { + const ScopedLockType lock (other.getLock()); + numUsed = other.size(); + data.setAllocatedSize (numUsed); + memcpy (data.elements, other.getRawDataPointer(), (size_t) numUsed * sizeof (ObjectClass*)); + + for (int i = numUsed; --i >= 0;) + if (ObjectClass* o = data.elements[i]) + o->incReferenceCount(); + } + + /** Creates a copy of another array */ + template + ReferenceCountedArray (const ReferenceCountedArray& other) noexcept + { + const typename ReferenceCountedArray::ScopedLockType lock (other.getLock()); + numUsed = other.size(); + data.setAllocatedSize (numUsed); + memcpy (data.elements, other.getRawDataPointer(), numUsed * sizeof (ObjectClass*)); + + for (int i = numUsed; --i >= 0;) + if (ObjectClass* o = data.elements[i]) + o->incReferenceCount(); + } + + /** Copies another array into this one. + Any existing objects in this array will first be released. + */ + ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept + { + ReferenceCountedArray otherCopy (other); + swapWith (otherCopy); + return *this; + } + + /** Copies another array into this one. + Any existing objects in this array will first be released. + */ + template + ReferenceCountedArray& operator= (const ReferenceCountedArray& other) noexcept + { + ReferenceCountedArray otherCopy (other); + swapWith (otherCopy); + return *this; + } + + /** Destructor. + Any objects in the array will be released, and may be deleted if not referenced from elsewhere. + */ + ~ReferenceCountedArray() + { + clear(); + } + + //============================================================================== + /** Removes all objects from the array. + + Any objects in the array that are not referenced from elsewhere will be deleted. + */ + void clear() + { + const ScopedLockType lock (getLock()); + + while (numUsed > 0) + if (ObjectClass* o = data.elements [--numUsed]) + releaseObject (o); + + jassert (numUsed == 0); + data.setAllocatedSize (0); + } + + /** Returns the current number of objects in the array. */ + inline int size() const noexcept + { + return numUsed; + } + + /** Returns a pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClassPtr operator[] (const int index) const noexcept + { + return getObjectPointer (index); + } + + /** Returns a pointer to the object at this index in the array, without checking + whether the index is in-range. + + This is a faster and less safe version of operator[] which doesn't check the index passed in, so + it can be used when you're sure the index is always going to be legal. + */ + inline ObjectClassPtr getUnchecked (const int index) const noexcept + { + return getObjectPointerUnchecked (index); + } + + /** Returns a raw pointer to the object at this index in the array. + + If the index is out-of-range, this will return a null pointer, (and + it could be null anyway, because it's ok for the array to hold null + pointers as well as objects). + + @see getUnchecked + */ + inline ObjectClass* getObjectPointer (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index, numUsed)) + { + jassert (data.elements != nullptr); + return data.elements [index]; + } + + return ObjectClassPtr(); + } + + /** Returns a raw pointer to the object at this index in the array, without checking + whether the index is in-range. + */ + inline ObjectClass* getObjectPointerUnchecked (const int index) const noexcept + { + const ScopedLockType lock (getLock()); + jassert (isPositiveAndBelow (index, numUsed) && data.elements != nullptr); + return data.elements [index]; + } + + /** Returns a pointer to the first object in the array. + + This will return a null pointer if the array's empty. + @see getLast + */ + inline ObjectClassPtr getFirst() const noexcept + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [0]; + } + + return ObjectClassPtr(); + } + + /** Returns a pointer to the last object in the array. + + This will return a null pointer if the array's empty. + @see getFirst + */ + inline ObjectClassPtr getLast() const noexcept + { + const ScopedLockType lock (getLock()); + + if (numUsed > 0) + { + jassert (data.elements != nullptr); + return data.elements [numUsed - 1]; + } + + return ObjectClassPtr(); + } + + /** Returns a pointer to the actual array data. + This pointer will only be valid until the next time a non-const method + is called on the array. + */ + inline ObjectClass** getRawDataPointer() const noexcept + { + return data.elements; + } + + //============================================================================== + /** Returns a pointer to the first element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** begin() const noexcept + { + return data.elements; + } + + /** Returns a pointer to the element which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ObjectClass** end() const noexcept + { + return data.elements + numUsed; + } + + //============================================================================== + /** Finds the index of the first occurrence of an object in the array. + + @param objectToLookFor the object to look for + @returns the index at which the object was found, or -1 if it's not found + */ + int indexOf (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass** e = data.elements.getData(); + ObjectClass** const endPointer = e + numUsed; + + while (e != endPointer) + { + if (objectToLookFor == *e) + return static_cast (e - data.elements.getData()); + + ++e; + } + + return -1; + } + + /** Returns true if the array contains a specified object. + + @param objectToLookFor the object to look for + @returns true if the object is in the array + */ + bool contains (const ObjectClass* const objectToLookFor) const noexcept + { + const ScopedLockType lock (getLock()); + ObjectClass** e = data.elements.getData(); + ObjectClass** const endPointer = e + numUsed; + + while (e != endPointer) + { + if (objectToLookFor == *e) + return true; + + ++e; + } + + return false; + } + + /** Appends a new object to the end of the array. + + This will increase the new object's reference count. + + @param newObject the new object to add to the array + @see set, insert, addIfNotAlreadyThere, addSorted, addArray + */ + ObjectClass* add (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = newObject; + + if (newObject != nullptr) + newObject->incReferenceCount(); + + return newObject; + } + + /** Inserts a new object into the array at the given index. + + If the index is less than 0 or greater than the size of the array, the + element will be added to the end of the array. + Otherwise, it will be inserted into the array, moving all the later elements + along to make room. + + This will increase the new object's reference count. + + @param indexToInsertAt the index at which the new element should be inserted + @param newObject the new object to add to the array + @see add, addSorted, addIfNotAlreadyThere, set + */ + ObjectClass* insert (int indexToInsertAt, + ObjectClass* const newObject) noexcept + { + if (indexToInsertAt < 0) + return add (newObject); + + const ScopedLockType lock (getLock()); + + if (indexToInsertAt > numUsed) + indexToInsertAt = numUsed; + + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + + ObjectClass** const e = data.elements + indexToInsertAt; + const int numToMove = numUsed - indexToInsertAt; + + if (numToMove > 0) + memmove (e + 1, e, sizeof (ObjectClass*) * (size_t) numToMove); + + *e = newObject; + + if (newObject != nullptr) + newObject->incReferenceCount(); + + ++numUsed; + + return newObject; + } + + /** Appends a new object at the end of the array as long as the array doesn't + already contain it. + + If the array already contains a matching object, nothing will be done. + + @param newObject the new object to add to the array + */ + void addIfNotAlreadyThere (ObjectClass* const newObject) noexcept + { + const ScopedLockType lock (getLock()); + if (! contains (newObject)) + add (newObject); + } + + /** Replaces an object in the array with a different one. + + If the index is less than zero, this method does nothing. + If the index is beyond the end of the array, the new object is added to the end of the array. + + The object being added has its reference count increased, and if it's replacing + another object, then that one has its reference count decreased, and may be deleted. + + @param indexToChange the index whose value you want to change + @param newObject the new value to set for this index. + @see add, insert, remove + */ + void set (const int indexToChange, + ObjectClass* const newObject) + { + if (indexToChange >= 0) + { + const ScopedLockType lock (getLock()); + + if (newObject != nullptr) + newObject->incReferenceCount(); + + if (indexToChange < numUsed) + { + if (ObjectClass* o = data.elements [indexToChange]) + releaseObject (o); + + data.elements [indexToChange] = newObject; + } + else + { + data.ensureAllocatedSize (numUsed + 1); + jassert (data.elements != nullptr); + data.elements [numUsed++] = newObject; + } + } + } + + /** Adds elements from another array to the end of this array. + + @param arrayToAddFrom the array from which to copy the elements + @param startIndex the first element of the other array to start copying from + @param numElementsToAdd how many elements to add from the other array. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + void addArray (const ReferenceCountedArray& arrayToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) noexcept + { + const ScopedLockType lock1 (arrayToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > arrayToAddFrom.size()) + numElementsToAdd = arrayToAddFrom.size() - startIndex; + + if (numElementsToAdd > 0) + { + data.ensureAllocatedSize (numUsed + numElementsToAdd); + + while (--numElementsToAdd >= 0) + add (arrayToAddFrom.getUnchecked (startIndex++)); + } + } + } + + /** Inserts a new object into the array assuming that the array is sorted. + + This will use a comparator to find the position at which the new object + should go. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator object to use to compare the elements - see the + sort() method for details about this object's form + @param newObject the new object to insert to the array + @returns the index at which the new object was added + @see add, sort + */ + template + int addSorted (ElementComparator& comparator, ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + insert (index, newObject); + return index; + } + + /** Inserts or replaces an object in the array, assuming it is sorted. + + This is similar to addSorted, but if a matching element already exists, then it will be + replaced by the new one, rather than the new one being added as well. + */ + template + void addOrReplaceSorted (ElementComparator& comparator, + ObjectClass* newObject) noexcept + { + const ScopedLockType lock (getLock()); + const int index = findInsertIndexInSortedArray (comparator, data.elements.getData(), newObject, 0, numUsed); + + if (index > 0 && comparator.compareElements (newObject, data.elements [index - 1]) == 0) + set (index - 1, newObject); // replace an existing object that matches + else + insert (index, newObject); // no match, so insert the new one + } + + /** Finds the index of an object in the array, assuming that the array is sorted. + + This will use a comparator to do a binary-chop to find the index of the given + element, if it exists. If the array isn't sorted, the behaviour of this + method will be unpredictable. + + @param comparator the comparator to use to compare the elements - see the sort() + method for details about the form this object should take + @param objectToLookFor the object to search for + @returns the index of the element, or -1 if it's not found + @see addSorted, sort + */ + template + int indexOfSorted (ElementComparator& comparator, + const ObjectClass* const objectToLookFor) const noexcept + { + (void) comparator; + const ScopedLockType lock (getLock()); + int s = 0, e = numUsed; + + while (s < e) + { + if (comparator.compareElements (objectToLookFor, data.elements [s]) == 0) + return s; + + const int halfway = (s + e) / 2; + if (halfway == s) + break; + + if (comparator.compareElements (objectToLookFor, data.elements [halfway]) >= 0) + s = halfway; + else + e = halfway; + } + + return -1; + } + + //============================================================================== + /** Removes an object from the array. + + This will remove the object at a given index and move back all the + subsequent objects to close the gap. + + If the index passed in is out-of-range, nothing will happen. + + The object that is removed will have its reference count decreased, + and may be deleted if not referenced from elsewhere. + + @param indexToRemove the index of the element to remove + @see removeObject, removeRange + */ + void remove (const int indexToRemove) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (ObjectClass* o = *e) + releaseObject (o); + + --numUsed; + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes and returns an object from the array. + + This will remove the object at a given index and return it, moving back all + the subsequent objects to close the gap. If the index passed in is out-of-range, + nothing will happen and a null pointer will be returned. + + @param indexToRemove the index of the element to remove + @see remove, removeObject, removeRange + */ + ObjectClassPtr removeAndReturn (const int indexToRemove) + { + ObjectClassPtr removedItem; + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (indexToRemove, numUsed)) + { + ObjectClass** const e = data.elements + indexToRemove; + + if (ObjectClass* o = *e) + { + removedItem = o; + releaseObject (o); + } + + --numUsed; + const int numberToShift = numUsed - indexToRemove; + + if (numberToShift > 0) + memmove (e, e + 1, sizeof (ObjectClass*) * (size_t) numberToShift); + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + + return removedItem; + } + + /** Removes the first occurrence of a specified object from the array. + + If the item isn't found, no action is taken. If it is found, it is + removed and has its reference count decreased. + + @param objectToRemove the object to try to remove + @see remove, removeRange + */ + void removeObject (ObjectClass* const objectToRemove) + { + const ScopedLockType lock (getLock()); + remove (indexOf (objectToRemove)); + } + + /** Removes a range of objects from the array. + + This will remove a set of objects, starting from the given index, + and move any subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + The objects that are removed will have their reference counts decreased, + and may be deleted if not referenced from elsewhere. + + @param startIndex the index of the first object to remove + @param numberToRemove how many objects should be removed + @see remove, removeObject + */ + void removeRange (const int startIndex, + const int numberToRemove) + { + const ScopedLockType lock (getLock()); + + const int start = jlimit (0, numUsed, startIndex); + const int endIndex = jlimit (0, numUsed, startIndex + numberToRemove); + + if (endIndex > start) + { + int i; + for (i = start; i < endIndex; ++i) + { + if (ObjectClass* o = data.elements[i]) + { + releaseObject (o); + data.elements[i] = nullptr; // (in case one of the destructors accesses this array and hits a dangling pointer) + } + } + + const int rangeSize = endIndex - start; + ObjectClass** e = data.elements + start; + i = numUsed - endIndex; + numUsed -= rangeSize; + + while (--i >= 0) + { + *e = e [rangeSize]; + ++e; + } + + if ((numUsed << 1) < data.numAllocated) + minimiseStorageOverheads(); + } + } + + /** Removes the last n objects from the array. + + The objects that are removed will have their reference counts decreased, + and may be deleted if not referenced from elsewhere. + + @param howManyToRemove how many objects to remove from the end of the array + @see remove, removeObject, removeRange + */ + void removeLast (int howManyToRemove = 1) + { + const ScopedLockType lock (getLock()); + + if (howManyToRemove > numUsed) + howManyToRemove = numUsed; + + while (--howManyToRemove >= 0) + remove (numUsed - 1); + } + + /** Swaps a pair of objects in the array. + + If either of the indexes passed in is out-of-range, nothing will happen, + otherwise the two objects at these positions will be exchanged. + */ + void swap (const int index1, + const int index2) noexcept + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (index1, numUsed) + && isPositiveAndBelow (index2, numUsed)) + { + std::swap (data.elements [index1], + data.elements [index2]); + } + } + + /** Moves one of the objects to a different position. + + This will move the object to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the object to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this object to end up. If this + is less than zero, it will be moved to the end of the array + */ + void move (const int currentIndex, + int newIndex) noexcept + { + if (currentIndex != newIndex) + { + const ScopedLockType lock (getLock()); + + if (isPositiveAndBelow (currentIndex, numUsed)) + { + if (! isPositiveAndBelow (newIndex, numUsed)) + newIndex = numUsed - 1; + + ObjectClass* const value = data.elements [currentIndex]; + + if (newIndex > currentIndex) + { + memmove (data.elements + currentIndex, + data.elements + currentIndex + 1, + sizeof (ObjectClass*) * (size_t) (newIndex - currentIndex)); + } + else + { + memmove (data.elements + newIndex + 1, + data.elements + newIndex, + sizeof (ObjectClass*) * (size_t) (currentIndex - newIndex)); + } + + data.elements [newIndex] = value; + } + } + } + + //============================================================================== + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherArrayType& otherArray) noexcept + { + const ScopedLockType lock1 (getLock()); + const typename OtherArrayType::ScopedLockType lock2 (otherArray.getLock()); + data.swapWith (otherArray.data); + std::swap (numUsed, otherArray.numUsed); + } + + //============================================================================== + /** Compares this array to another one. + + @returns true only if the other array contains the same objects in the same order + */ + bool operator== (const ReferenceCountedArray& other) const noexcept + { + const ScopedLockType lock2 (other.getLock()); + const ScopedLockType lock1 (getLock()); + + if (numUsed != other.numUsed) + return false; + + for (int i = numUsed; --i >= 0;) + if (data.elements [i] != other.data.elements [i]) + return false; + + return true; + } + + /** Compares this array to another one. + + @see operator== + */ + bool operator!= (const ReferenceCountedArray& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Sorts the elements in the array. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (ElementType first, ElementType second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items + which the comparator says are equivalent will be + kept in the order in which they currently appear + in the array. This is slower to perform, but may + be important in some cases. If it's false, a faster + algorithm is used, but equivalent elements may be + rearranged. + + @see sortArray + */ + template + void sort (ElementComparator& comparator, + const bool retainOrderOfEquivalentItems = false) const noexcept + { + (void) comparator; // if you pass in an object with a static compareElements() method, this + // avoids getting warning messages about the parameter being unused + + const ScopedLockType lock (getLock()); + sortArray (comparator, data.elements.getData(), 0, size() - 1, retainOrderOfEquivalentItems); + } + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + const ScopedLockType lock (getLock()); + data.shrinkToNoMoreThan (numUsed); + } + + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + const ScopedLockType lock (getLock()); + data.ensureAllocatedSize (minNumElements); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data; } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + + //============================================================================== + #ifndef DOXYGEN + // Note that the swapWithArray method has been replaced by a more flexible templated version, + // and renamed "swapWith" to be more consistent with the names used in other classes. + JUCE_DEPRECATED_WITH_BODY (void swapWithArray (ReferenceCountedArray& other) noexcept, { swapWith (other); }) + #endif + +private: + //============================================================================== + ArrayAllocationBase data; + int numUsed; + + static void releaseObject (ObjectClass* o) + { + if (o->decReferenceCountWithoutDeleting()) + ContainerDeletePolicy::destroy (o); + } +}; + + +#endif // JUCE_REFERENCECOUNTEDARRAY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ScopedValueSetter.h b/JuceLibraryCode/modules/juce_core/containers/juce_ScopedValueSetter.h new file mode 100644 index 0000000..13b871e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ScopedValueSetter.h @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDVALUESETTER_H_INCLUDED +#define JUCE_SCOPEDVALUESETTER_H_INCLUDED + + +//============================================================================== +/** + Helper class providing an RAII-based mechanism for temporarily setting and + then re-setting a value. + + E.g. @code + int x = 1; + + { + ScopedValueSetter setter (x, 2); + + // x is now 2 + } + + // x is now 1 again + + { + ScopedValueSetter setter (x, 3, 4); + + // x is now 3 + } + + // x is now 4 + @endcode + +*/ +template +class ScopedValueSetter +{ +public: + /** Creates a ScopedValueSetter that will immediately change the specified value to the + given new value, and will then reset it to its original value when this object is deleted. + */ + ScopedValueSetter (ValueType& valueToSet, + ValueType newValue) + : value (valueToSet), + originalValue (valueToSet) + { + valueToSet = newValue; + } + + /** Creates a ScopedValueSetter that will immediately change the specified value to the + given new value, and will then reset it to be valueWhenDeleted when this object is deleted. + */ + ScopedValueSetter (ValueType& valueToSet, + ValueType newValue, + ValueType valueWhenDeleted) + : value (valueToSet), + originalValue (valueWhenDeleted) + { + valueToSet = newValue; + } + + ~ScopedValueSetter() + { + value = originalValue; + } + +private: + //============================================================================== + ValueType& value; + const ValueType originalValue; + + JUCE_DECLARE_NON_COPYABLE (ScopedValueSetter) +}; + + +#endif // JUCE_SCOPEDVALUESETTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_SortedSet.h b/JuceLibraryCode/modules/juce_core/containers/juce_SortedSet.h new file mode 100644 index 0000000..62fd923 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_SortedSet.h @@ -0,0 +1,494 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SORTEDSET_H_INCLUDED +#define JUCE_SORTEDSET_H_INCLUDED + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4512) +#endif + +//============================================================================== +/** + Holds a set of unique primitive objects, such as ints or doubles. + + A set can only hold one item with a given value, so if for example it's a + set of integers, attempting to add the same integer twice will do nothing + the second time. + + Internally, the list of items is kept sorted (which means that whatever + kind of primitive type is used must support the ==, <, >, <= and >= operators + to determine the order), and searching the set for known values is very fast + because it uses a binary-chop method. + + Note that if you're using a class or struct as the element type, it must be + capable of being copied or moved with a straightforward memcpy, rather than + needing construction and destruction code. + + To make all the set's methods thread-safe, pass in "CriticalSection" as the templated + TypeOfCriticalSectionToUse parameter, instead of the default DummyCriticalSection. + + @see Array, OwnedArray, ReferenceCountedArray, StringArray, CriticalSection +*/ +template +class SortedSet +{ +public: + //============================================================================== + /** Creates an empty set. */ + SortedSet() noexcept + { + } + + /** Creates a copy of another set. + @param other the set to copy + */ + SortedSet (const SortedSet& other) + : data (other.data) + { + } + + /** Destructor. */ + ~SortedSet() noexcept + { + } + + /** Copies another set over this one. + @param other the set to copy + */ + SortedSet& operator= (const SortedSet& other) noexcept + { + data = other.data; + return *this; + } + + //============================================================================== + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator== (const SortedSet& other) const noexcept + { + return data == other.data; + } + + /** Compares this set to another one. + Two sets are considered equal if they both contain the same set of elements. + @param other the other set to compare with + */ + bool operator!= (const SortedSet& other) const noexcept + { + return ! operator== (other); + } + + //============================================================================== + /** Removes all elements from the set. + + This will remove all the elements, and free any storage that the set is + using. To clear it without freeing the storage, use the clearQuick() + method instead. + + @see clearQuick + */ + void clear() noexcept + { + data.clear(); + } + + /** Removes all elements from the set without freeing the array's allocated storage. + @see clear + */ + void clearQuick() noexcept + { + data.clearQuick(); + } + + //============================================================================== + /** Returns the current number of elements in the set. */ + inline int size() const noexcept + { + return data.size(); + } + + /** Returns one of the elements in the set. + + If the index passed in is beyond the range of valid elements, this + will return zero. + + If you're certain that the index will always be a valid element, you + can call getUnchecked() instead, which is faster. + + @param index the index of the element being requested (0 is the first element in the set) + @see getUnchecked, getFirst, getLast + */ + inline ElementType operator[] (const int index) const noexcept + { + return data [index]; + } + + /** Returns one of the elements in the set, without checking the index passed in. + Unlike the operator[] method, this will try to return an element without + checking that the index is within the bounds of the set, so should only + be used when you're confident that it will always be a valid index. + + @param index the index of the element being requested (0 is the first element in the set) + @see operator[], getFirst, getLast + */ + inline ElementType getUnchecked (const int index) const noexcept + { + return data.getUnchecked (index); + } + + /** Returns a direct reference to one of the elements in the set, without checking the index passed in. + + This is like getUnchecked, but returns a direct reference to the element, so that + you can alter it directly. Obviously this can be dangerous, so only use it when + absolutely necessary. + + @param index the index of the element being requested (0 is the first element in the array) + */ + inline ElementType& getReference (const int index) const noexcept + { + return data.getReference (index); + } + + /** Returns the first element in the set, or 0 if the set is empty. + @see operator[], getUnchecked, getLast + */ + inline ElementType getFirst() const noexcept + { + return data.getFirst(); + } + + /** Returns the last element in the set, or 0 if the set is empty. + @see operator[], getUnchecked, getFirst + */ + inline ElementType getLast() const noexcept + { + return data.getLast(); + } + + //============================================================================== + /** Returns a pointer to the first element in the set. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* begin() const noexcept + { + return data.begin(); + } + + /** Returns a pointer to the element which follows the last element in the set. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline ElementType* end() const noexcept + { + return data.end(); + } + + //============================================================================== + /** Finds the index of the first element which matches the value passed in. + + This will search the set for the given object, and return the index + of its first occurrence. If the object isn't found, the method will return -1. + + @param elementToLookFor the value or object to look for + @returns the index of the object, or -1 if it's not found + */ + int indexOf (const ElementType& elementToLookFor) const noexcept + { + const ScopedLockType lock (data.getLock()); + + int s = 0; + int e = data.size(); + + for (;;) + { + if (s >= e) + return -1; + + if (elementToLookFor == data.getReference (s)) + return s; + + const int halfway = (s + e) / 2; + + if (halfway == s) + return -1; + + if (elementToLookFor < data.getReference (halfway)) + e = halfway; + else + s = halfway; + } + } + + /** Returns true if the set contains at least one occurrence of an object. + + @param elementToLookFor the value or object to look for + @returns true if the item is found + */ + bool contains (const ElementType& elementToLookFor) const noexcept + { + return indexOf (elementToLookFor) >= 0; + } + + //============================================================================== + /** Adds a new element to the set, (as long as it's not already in there). + + Note that if a matching element already exists, the new value will be assigned + to the existing one using operator=, so that if there are any differences between + the objects which were not recognised by the object's operator==, then the + set will always contain a copy of the most recently added one. + + @param newElement the new object to add to the set + @returns true if the value was added, or false if it already existed + @see set, insert, addIfNotAlreadyThere, addSorted, addSet, addArray + */ + bool add (const ElementType& newElement) noexcept + { + const ScopedLockType lock (getLock()); + + int s = 0; + int e = data.size(); + + while (s < e) + { + ElementType& elem = data.getReference (s); + if (newElement == elem) + { + elem = newElement; // force an update in case operator== permits differences. + return false; + } + + const int halfway = (s + e) / 2; + const bool isBeforeHalfway = (newElement < data.getReference (halfway)); + + if (halfway == s) + { + if (! isBeforeHalfway) + ++s; + + break; + } + + if (isBeforeHalfway) + e = halfway; + else + s = halfway; + } + + data.insert (s, newElement); + return true; + } + + /** Adds elements from an array to this set. + + @param elementsToAdd the array of elements to add + @param numElementsToAdd how many elements are in this other array + @see add + */ + void addArray (const ElementType* elementsToAdd, + int numElementsToAdd) noexcept + { + const ScopedLockType lock (getLock()); + + while (--numElementsToAdd >= 0) + add (*elementsToAdd++); + } + + /** Adds elements from another set to this one. + + @param setToAddFrom the set from which to copy the elements + @param startIndex the first element of the other set to start copying from + @param numElementsToAdd how many elements to add from the other set. If this + value is negative or greater than the number of available elements, + all available elements will be copied. + @see add + */ + template + void addSet (const OtherSetType& setToAddFrom, + int startIndex = 0, + int numElementsToAdd = -1) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock()); + + { + const ScopedLockType lock2 (getLock()); + jassert (this != &setToAddFrom); + + if (this != &setToAddFrom) + { + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) + numElementsToAdd = setToAddFrom.size() - startIndex; + + if (numElementsToAdd > 0) + addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); + } + } + } + + //============================================================================== + /** Removes an element from the set. + + This will remove the element at a given index. + If the index passed in is out-of-range, nothing will happen. + + @param indexToRemove the index of the element to remove + @returns the element that has been removed + @see removeValue, removeRange + */ + ElementType remove (const int indexToRemove) noexcept + { + return data.remove (indexToRemove); + } + + /** Removes an item from the set. + + This will remove the given element from the set, if it's there. + + @param valueToRemove the object to try to remove + @see remove, removeRange + */ + void removeValue (const ElementType valueToRemove) noexcept + { + const ScopedLockType lock (getLock()); + data.remove (indexOf (valueToRemove)); + } + + /** Removes any elements which are also in another set. + + @param otherSet the other set in which to look for elements to remove + @see removeValuesNotIn, remove, removeValue, removeRange + */ + template + void removeValuesIn (const OtherSetType& otherSet) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this == &otherSet) + { + clear(); + } + else if (otherSet.size() > 0) + { + for (int i = data.size(); --i >= 0;) + if (otherSet.contains (data.getReference (i))) + remove (i); + } + } + + /** Removes any elements which are not found in another set. + + Only elements which occur in this other set will be retained. + + @param otherSet the set in which to look for elements NOT to remove + @see removeValuesIn, remove, removeValue, removeRange + */ + template + void removeValuesNotIn (const OtherSetType& otherSet) noexcept + { + const typename OtherSetType::ScopedLockType lock1 (otherSet.getLock()); + const ScopedLockType lock2 (getLock()); + + if (this != &otherSet) + { + if (otherSet.size() <= 0) + { + clear(); + } + else + { + for (int i = data.size(); --i >= 0;) + if (! otherSet.contains (data.getReference (i))) + remove (i); + } + } + } + + /** This swaps the contents of this array with those of another array. + + If you need to exchange two arrays, this is vastly quicker than using copy-by-value + because it just swaps their internal pointers. + */ + template + void swapWith (OtherSetType& otherSet) noexcept + { + data.swapWith (otherSet.data); + } + + //============================================================================== + /** Reduces the amount of storage being used by the set. + + Sets typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads() noexcept + { + data.minimiseStorageOverheads(); + } + + /** Increases the set's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the set won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (const int minNumElements) + { + data.ensureStorageAllocated (minNumElements); + } + + //============================================================================== + /** Returns the CriticalSection that locks this array. + To lock, you can call getLock().enter() and getLock().exit(), or preferably use + an object of ScopedLockType as an RAII lock for it. + */ + inline const TypeOfCriticalSectionToUse& getLock() const noexcept { return data.getLock(); } + + /** Returns the type of scoped lock to use for locking this array */ + typedef typename TypeOfCriticalSectionToUse::ScopedLockType ScopedLockType; + + +private: + //============================================================================== + Array data; +}; + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_SORTEDSET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_SparseSet.h b/JuceLibraryCode/modules/juce_core/containers/juce_SparseSet.h new file mode 100644 index 0000000..86366fc --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_SparseSet.h @@ -0,0 +1,298 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SPARSESET_H_INCLUDED +#define JUCE_SPARSESET_H_INCLUDED + + +//============================================================================== +/** + Holds a set of primitive values, storing them as a set of ranges. + + This container acts like an array, but can efficiently hold large contiguous + ranges of values. It's quite a specialised class, mostly useful for things + like keeping the set of selected rows in a listbox. + + The type used as a template parameter must be an integer type, such as int, short, + int64, etc. +*/ +template +class SparseSet +{ +public: + //============================================================================== + /** Creates a new empty set. */ + SparseSet() + { + } + + /** Creates a copy of another SparseSet. */ + SparseSet (const SparseSet& other) + : values (other.values) + { + } + + //============================================================================== + /** Clears the set. */ + void clear() + { + values.clear(); + } + + /** Checks whether the set is empty. + + This is much quicker than using (size() == 0). + */ + bool isEmpty() const noexcept + { + return values.size() == 0; + } + + /** Returns the number of values in the set. + + Because of the way the data is stored, this method can take longer if there + are a lot of items in the set. Use isEmpty() for a quick test of whether there + are any items. + */ + Type size() const + { + Type total (0); + + for (int i = 0; i < values.size(); i += 2) + total += values.getUnchecked (i + 1) - values.getUnchecked (i); + + return total; + } + + /** Returns one of the values in the set. + + @param index the index of the value to retrieve, in the range 0 to (size() - 1). + @returns the value at this index, or 0 if it's out-of-range + */ + Type operator[] (Type index) const + { + for (int i = 0; i < values.size(); i += 2) + { + const Type start (values.getUnchecked (i)); + const Type len (values.getUnchecked (i + 1) - start); + + if (index < len) + return start + index; + + index -= len; + } + + return Type(); + } + + /** Checks whether a particular value is in the set. */ + bool contains (const Type valueToLookFor) const + { + for (int i = 0; i < values.size(); ++i) + if (valueToLookFor < values.getUnchecked(i)) + return (i & 1) != 0; + + return false; + } + + //============================================================================== + /** Returns the number of contiguous blocks of values. + @see getRange + */ + int getNumRanges() const noexcept + { + return values.size() >> 1; + } + + /** Returns one of the contiguous ranges of values stored. + @param rangeIndex the index of the range to look up, between 0 + and (getNumRanges() - 1) + @see getTotalRange + */ + const Range getRange (const int rangeIndex) const + { + if (isPositiveAndBelow (rangeIndex, getNumRanges())) + return Range (values.getUnchecked (rangeIndex << 1), + values.getUnchecked ((rangeIndex << 1) + 1)); + + return Range(); + } + + /** Returns the range between the lowest and highest values in the set. + @see getRange + */ + Range getTotalRange() const + { + if (values.size() > 0) + { + jassert ((values.size() & 1) == 0); + return Range (values.getUnchecked (0), + values.getUnchecked (values.size() - 1)); + } + + return Range(); + } + + //============================================================================== + /** Adds a range of contiguous values to the set. + e.g. addRange (Range \ (10, 14)) will add (10, 11, 12, 13) to the set. + */ + void addRange (const Range range) + { + jassert (range.getLength() >= 0); + if (range.getLength() > 0) + { + removeRange (range); + + values.addUsingDefaultSort (range.getStart()); + values.addUsingDefaultSort (range.getEnd()); + + simplify(); + } + } + + /** Removes a range of values from the set. + e.g. removeRange (Range\ (10, 14)) will remove (10, 11, 12, 13) from the set. + */ + void removeRange (const Range rangeToRemove) + { + jassert (rangeToRemove.getLength() >= 0); + + if (rangeToRemove.getLength() > 0 + && values.size() > 0 + && rangeToRemove.getStart() < values.getUnchecked (values.size() - 1) + && values.getUnchecked(0) < rangeToRemove.getEnd()) + { + const bool onAtStart = contains (rangeToRemove.getStart() - 1); + const Type lastValue (jmin (rangeToRemove.getEnd(), values.getLast())); + const bool onAtEnd = contains (lastValue); + + for (int i = values.size(); --i >= 0;) + { + if (values.getUnchecked(i) <= lastValue) + { + while (values.getUnchecked(i) >= rangeToRemove.getStart()) + { + values.remove (i); + + if (--i < 0) + break; + } + + break; + } + } + + if (onAtStart) values.addUsingDefaultSort (rangeToRemove.getStart()); + if (onAtEnd) values.addUsingDefaultSort (lastValue); + + simplify(); + } + } + + /** Does an XOR of the values in a given range. */ + void invertRange (const Range range) + { + SparseSet newItems; + newItems.addRange (range); + + for (int i = getNumRanges(); --i >= 0;) + newItems.removeRange (getRange (i)); + + removeRange (range); + + for (int i = newItems.getNumRanges(); --i >= 0;) + addRange (newItems.getRange(i)); + } + + /** Checks whether any part of a given range overlaps any part of this set. */ + bool overlapsRange (const Range range) + { + if (range.getLength() > 0) + { + for (int i = getNumRanges(); --i >= 0;) + { + if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) + return false; + + if (values.getUnchecked (i << 1) < range.getEnd()) + return true; + } + } + + return false; + } + + /** Checks whether the whole of a given range is contained within this one. */ + bool containsRange (const Range range) + { + if (range.getLength() > 0) + { + for (int i = getNumRanges(); --i >= 0;) + { + if (values.getUnchecked ((i << 1) + 1) <= range.getStart()) + return false; + + if (values.getUnchecked (i << 1) <= range.getStart() + && range.getEnd() <= values.getUnchecked ((i << 1) + 1)) + return true; + } + } + + return false; + } + + //============================================================================== + bool operator== (const SparseSet& other) noexcept + { + return values == other.values; + } + + bool operator!= (const SparseSet& other) noexcept + { + return values != other.values; + } + +private: + //============================================================================== + // alternating start/end values of ranges of values that are present. + Array values; + + void simplify() + { + jassert ((values.size() & 1) == 0); + + for (int i = values.size(); --i > 0;) + if (values.getUnchecked(i) == values.getUnchecked (i - 1)) + values.removeRange (--i, 2); + } +}; + + + +#endif // JUCE_SPARSESET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp new file mode 100644 index 0000000..db3f2f5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp @@ -0,0 +1,780 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +enum VariantStreamMarkers +{ + varMarker_Int = 1, + varMarker_BoolTrue = 2, + varMarker_BoolFalse = 3, + varMarker_Double = 4, + varMarker_String = 5, + varMarker_Int64 = 6, + varMarker_Array = 7, + varMarker_Binary = 8, + varMarker_Undefined = 9 +}; + +//============================================================================== +class var::VariantType +{ +public: + VariantType() noexcept {} + virtual ~VariantType() noexcept {} + + virtual int toInt (const ValueUnion&) const noexcept { return 0; } + virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } + virtual double toDouble (const ValueUnion&) const noexcept { return 0; } + virtual String toString (const ValueUnion&) const { return String::empty; } + virtual bool toBool (const ValueUnion&) const noexcept { return false; } + virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } + virtual Array* toArray (const ValueUnion&) const noexcept { return nullptr; } + virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } + virtual var clone (const var& original) const { return original; } + + virtual bool isVoid() const noexcept { return false; } + virtual bool isUndefined() const noexcept { return false; } + virtual bool isInt() const noexcept { return false; } + virtual bool isInt64() const noexcept { return false; } + virtual bool isBool() const noexcept { return false; } + virtual bool isDouble() const noexcept { return false; } + virtual bool isString() const noexcept { return false; } + virtual bool isObject() const noexcept { return false; } + virtual bool isArray() const noexcept { return false; } + virtual bool isBinary() const noexcept { return false; } + virtual bool isMethod() const noexcept { return false; } + + virtual void cleanUp (ValueUnion&) const noexcept {} + virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } + virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; + virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; +}; + +//============================================================================== +class var::VariantType_Void : public var::VariantType +{ +public: + VariantType_Void() noexcept {} + static const VariantType_Void instance; + + bool isVoid() const noexcept override { return true; } + bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } + void writeToStream (const ValueUnion&, OutputStream& output) const override { output.writeCompressedInt (0); } +}; + +//============================================================================== +class var::VariantType_Undefined : public var::VariantType +{ +public: + VariantType_Undefined() noexcept {} + static const VariantType_Undefined instance; + + bool isUndefined() const noexcept override { return true; } + String toString (const ValueUnion&) const override { return "undefined"; } + bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + output.writeCompressedInt (1); + output.writeByte (varMarker_Undefined); + } +}; + +//============================================================================== +class var::VariantType_Int : public var::VariantType +{ +public: + VariantType_Int() noexcept {} + static const VariantType_Int instance; + + int toInt (const ValueUnion& data) const noexcept override { return data.intValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.intValue; }; + double toDouble (const ValueUnion& data) const noexcept override { return (double) data.intValue; } + String toString (const ValueUnion& data) const override { return String (data.intValue); } + bool toBool (const ValueUnion& data) const noexcept override { return data.intValue != 0; } + bool isInt() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + if (otherType.isDouble() || otherType.isInt64() || otherType.isString()) + return otherType.equals (otherData, data, *this); + + return otherType.toInt (otherData) == data.intValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (5); + output.writeByte (varMarker_Int); + output.writeInt (data.intValue); + } +}; + +//============================================================================== +class var::VariantType_Int64 : public var::VariantType +{ +public: + VariantType_Int64() noexcept {} + static const VariantType_Int64 instance; + + int toInt (const ValueUnion& data) const noexcept override { return (int) data.int64Value; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return data.int64Value; }; + double toDouble (const ValueUnion& data) const noexcept override { return (double) data.int64Value; } + String toString (const ValueUnion& data) const override { return String (data.int64Value); } + bool toBool (const ValueUnion& data) const noexcept override { return data.int64Value != 0; } + bool isInt64() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + if (otherType.isDouble() || otherType.isString()) + return otherType.equals (otherData, data, *this); + + return otherType.toInt64 (otherData) == data.int64Value; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Int64); + output.writeInt64 (data.int64Value); + } +}; + +//============================================================================== +class var::VariantType_Double : public var::VariantType +{ +public: + VariantType_Double() noexcept {} + static const VariantType_Double instance; + + int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.doubleValue; }; + double toDouble (const ValueUnion& data) const noexcept override { return data.doubleValue; } + String toString (const ValueUnion& data) const override { return String (data.doubleValue); } + bool toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0; } + bool isDouble() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits::epsilon(); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (9); + output.writeByte (varMarker_Double); + output.writeDouble (data.doubleValue); + } +}; + +//============================================================================== +class var::VariantType_Bool : public var::VariantType +{ +public: + VariantType_Bool() noexcept {} + static const VariantType_Bool instance; + + int toInt (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; }; + double toDouble (const ValueUnion& data) const noexcept override { return data.boolValue ? 1.0 : 0.0; } + String toString (const ValueUnion& data) const override { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); } + bool toBool (const ValueUnion& data) const noexcept override { return data.boolValue; } + bool isBool() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toBool (otherData) == data.boolValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (1); + output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); + } +}; + +//============================================================================== +class var::VariantType_String : public var::VariantType +{ +public: + VariantType_String() noexcept {} + static const VariantType_String instance; + + void cleanUp (ValueUnion& data) const noexcept override { getString (data)-> ~String(); } + void createCopy (ValueUnion& dest, const ValueUnion& source) const override { new (dest.stringValue) String (*getString (source)); } + + bool isString() const noexcept override { return true; } + int toInt (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue(); }; + int64 toInt64 (const ValueUnion& data) const noexcept override { return getString (data)->getLargeIntValue(); }; + double toDouble (const ValueUnion& data) const noexcept override { return getString (data)->getDoubleValue(); } + String toString (const ValueUnion& data) const override { return *getString (data); } + bool toBool (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue() != 0 + || getString (data)->trim().equalsIgnoreCase ("true") + || getString (data)->trim().equalsIgnoreCase ("yes"); } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toString (otherData) == *getString (data); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + const String* const s = getString (data); + const size_t len = s->getNumBytesAsUTF8() + 1; + HeapBlock temp (len); + s->copyToUTF8 (temp, len); + output.writeCompressedInt ((int) (len + 1)); + output.writeByte (varMarker_String); + output.write (temp, len); + } + +private: + static inline const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } + static inline String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } +}; + +//============================================================================== +class var::VariantType_Object : public var::VariantType +{ +public: + VariantType_Object() noexcept {} + static const VariantType_Object instance; + + void cleanUp (ValueUnion& data) const noexcept override { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } + + void createCopy (ValueUnion& dest, const ValueUnion& source) const override + { + dest.objectValue = source.objectValue; + if (dest.objectValue != nullptr) + dest.objectValue->incReferenceCount(); + } + + String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } + bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != 0; } + ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; } + bool isObject() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.toObject (otherData) == data.objectValue; + } + + var clone (const var& original) const override + { + if (DynamicObject* d = original.getDynamicObject()) + return d->clone().get(); + + jassertfalse; // can only clone DynamicObjects! + return var(); + } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + jassertfalse; // Can't write an object to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +class var::VariantType_Array : public var::VariantType_Object +{ +public: + VariantType_Array() noexcept {} + static const VariantType_Array instance; + + String toString (const ValueUnion&) const override { return "[Array]"; } + ReferenceCountedObject* toObject (const ValueUnion&) const noexcept override { return nullptr; } + bool isArray() const noexcept override { return true; } + + Array* toArray (const ValueUnion& data) const noexcept override + { + if (RefCountedArray* a = dynamic_cast (data.objectValue)) + return &(a->array); + + return nullptr; + } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + const Array* const thisArray = toArray (data); + const Array* const otherArray = otherType.toArray (otherData); + return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray); + } + + var clone (const var& original) const override + { + Array arrayCopy; + + if (const Array* array = toArray (original.value)) + for (int i = 0; i < array->size(); ++i) + arrayCopy.add (array->getReference(i).clone()); + + return var (arrayCopy); + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + if (const Array* array = toArray (data)) + { + MemoryOutputStream buffer (512); + const int numItems = array->size(); + buffer.writeCompressedInt (numItems); + + for (int i = 0; i < numItems; ++i) + array->getReference(i).writeToStream (buffer); + + output.writeCompressedInt (1 + (int) buffer.getDataSize()); + output.writeByte (varMarker_Array); + output << buffer; + } + } + + struct RefCountedArray : public ReferenceCountedObject + { + RefCountedArray (const Array& a) : array (a) { incReferenceCount(); } + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + RefCountedArray (Array&& a) : array (static_cast&&> (a)) { incReferenceCount(); } + #endif + Array array; + }; +}; + +//============================================================================== +class var::VariantType_Binary : public var::VariantType +{ +public: + VariantType_Binary() noexcept {} + + static const VariantType_Binary instance; + + void cleanUp (ValueUnion& data) const noexcept override { delete data.binaryValue; } + void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.binaryValue = new MemoryBlock (*source.binaryValue); } + + String toString (const ValueUnion& data) const override { return data.binaryValue->toBase64Encoding(); } + bool isBinary() const noexcept override { return true; } + MemoryBlock* toBinary (const ValueUnion& data) const noexcept override { return data.binaryValue; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + const MemoryBlock* const otherBlock = otherType.toBinary (otherData); + return otherBlock != nullptr && *otherBlock == *data.binaryValue; + } + + void writeToStream (const ValueUnion& data, OutputStream& output) const override + { + output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); + output.writeByte (varMarker_Binary); + output << *data.binaryValue; + } +}; + +//============================================================================== +class var::VariantType_Method : public var::VariantType +{ +public: + VariantType_Method() noexcept {} + static const VariantType_Method instance; + + String toString (const ValueUnion&) const override { return "Method"; } + bool toBool (const ValueUnion& data) const noexcept override { return data.methodValue != nullptr; } + bool isMethod() const noexcept override { return true; } + + bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + { + return otherType.isMethod() && otherData.methodValue == data.methodValue; + } + + void writeToStream (const ValueUnion&, OutputStream& output) const override + { + jassertfalse; // Can't write a method to a stream! + output.writeCompressedInt (0); + } +}; + +//============================================================================== +const var::VariantType_Void var::VariantType_Void::instance; +const var::VariantType_Undefined var::VariantType_Undefined::instance; +const var::VariantType_Int var::VariantType_Int::instance; +const var::VariantType_Int64 var::VariantType_Int64::instance; +const var::VariantType_Bool var::VariantType_Bool::instance; +const var::VariantType_Double var::VariantType_Double::instance; +const var::VariantType_String var::VariantType_String::instance; +const var::VariantType_Object var::VariantType_Object::instance; +const var::VariantType_Array var::VariantType_Array::instance; +const var::VariantType_Binary var::VariantType_Binary::instance; +const var::VariantType_Method var::VariantType_Method::instance; + + +//============================================================================== +var::var() noexcept : type (&VariantType_Void::instance) {} +var::var (const VariantType& t) noexcept : type (&t) {} +var::~var() noexcept { type->cleanUp (value); } + +const var var::null; + +//============================================================================== +var::var (const var& valueToCopy) : type (valueToCopy.type) +{ + type->createCopy (value, valueToCopy.value); +} + +var::var (const int v) noexcept : type (&VariantType_Int::instance) { value.intValue = v; } +var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } +var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } +var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } +var::var (NativeFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = m; } +var::var (const Array& v) : type (&VariantType_Array::instance) { value.objectValue = new VariantType_Array::RefCountedArray(v); } +var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } +var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } +var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } + +var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) +{ + value.objectValue = object; + + if (object != nullptr) + object->incReferenceCount(); +} + +var var::undefined() noexcept { return var (VariantType_Undefined::instance); } + +//============================================================================== +bool var::isVoid() const noexcept { return type->isVoid(); } +bool var::isUndefined() const noexcept { return type->isUndefined(); } +bool var::isInt() const noexcept { return type->isInt(); } +bool var::isInt64() const noexcept { return type->isInt64(); } +bool var::isBool() const noexcept { return type->isBool(); } +bool var::isDouble() const noexcept { return type->isDouble(); } +bool var::isString() const noexcept { return type->isString(); } +bool var::isObject() const noexcept { return type->isObject(); } +bool var::isArray() const noexcept { return type->isArray(); } +bool var::isBinaryData() const noexcept { return type->isBinary(); } +bool var::isMethod() const noexcept { return type->isMethod(); } + +var::operator int() const noexcept { return type->toInt (value); } +var::operator int64() const noexcept { return type->toInt64 (value); } +var::operator bool() const noexcept { return type->toBool (value); } +var::operator float() const noexcept { return (float) type->toDouble (value); } +var::operator double() const noexcept { return type->toDouble (value); } +String var::toString() const { return type->toString (value); } +var::operator String() const { return type->toString (value); } +ReferenceCountedObject* var::getObject() const noexcept { return type->toObject (value); } +Array* var::getArray() const noexcept { return type->toArray (value); } +MemoryBlock* var::getBinaryData() const noexcept { return type->toBinary (value); } +DynamicObject* var::getDynamicObject() const noexcept { return dynamic_cast (getObject()); } + +//============================================================================== +void var::swapWith (var& other) noexcept +{ + std::swap (type, other.type); + std::swap (value, other.value); +} + +var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; } +var& var::operator= (const int v) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = v; return *this; } +var& var::operator= (const int64 v) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = v; return *this; } +var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } +var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } +var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } +var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } +var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +var::var (var&& other) noexcept + : type (other.type), + value (other.value) +{ + other.type = &VariantType_Void::instance; +} + +var& var::operator= (var&& other) noexcept +{ + swapWith (other); + return *this; +} + +var::var (String&& v) : type (&VariantType_String::instance) +{ + new (value.stringValue) String (static_cast (v)); +} + +var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) +{ + value.binaryValue = new MemoryBlock (static_cast (v)); +} + +var::var (Array&& v) : type (&VariantType_Array::instance) +{ + value.objectValue = new VariantType_Array::RefCountedArray (static_cast&&> (v)); +} + +var& var::operator= (String&& v) +{ + type->cleanUp (value); + type = &VariantType_String::instance; + new (value.stringValue) String (static_cast (v)); + return *this; +} +#endif + +//============================================================================== +bool var::equals (const var& other) const noexcept +{ + return type->equals (value, other.value, *other.type); +} + +bool var::equalsWithSameType (const var& other) const noexcept +{ + return type == other.type && equals (other); +} + +bool var::hasSameTypeAs (const var& other) const noexcept +{ + return type == other.type; +} + +bool operator== (const var& v1, const var& v2) noexcept { return v1.equals (v2); } +bool operator!= (const var& v1, const var& v2) noexcept { return ! v1.equals (v2); } +bool operator== (const var& v1, const String& v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const String& v2) { return v1.toString() != v2; } +bool operator== (const var& v1, const char* const v2) { return v1.toString() == v2; } +bool operator!= (const var& v1, const char* const v2) { return v1.toString() != v2; } + +//============================================================================== +var var::clone() const noexcept +{ + return type->clone (*this); +} + +//============================================================================== +var var::operator[] (const Identifier propertyName) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperty (propertyName); + + return var(); +} + +var var::operator[] (const char* const propertyName) const +{ + return operator[] (Identifier (propertyName)); +} + +var var::getProperty (const Identifier propertyName, const var& defaultReturnValue) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->getProperties().getWithDefault (propertyName, defaultReturnValue); + + return defaultReturnValue; +} + +var::NativeFunction var::getNativeFunction() const +{ + return isMethod() ? value.methodValue : nullptr; +} + +var var::invoke (Identifier method, const var* arguments, int numArguments) const +{ + if (DynamicObject* const o = getDynamicObject()) + return o->invokeMethod (method, var::NativeFunctionArgs (*this, arguments, numArguments)); + + return var(); +} + +var var::call (const Identifier method) const +{ + return invoke (method, nullptr, 0); +} + +var var::call (const Identifier method, const var& arg1) const +{ + return invoke (method, &arg1, 1); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2) const +{ + var args[] = { arg1, arg2 }; + return invoke (method, args, 2); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3) +{ + var args[] = { arg1, arg2, arg3 }; + return invoke (method, args, 3); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const +{ + var args[] = { arg1, arg2, arg3, arg4 }; + return invoke (method, args, 4); +} + +var var::call (const Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const +{ + var args[] = { arg1, arg2, arg3, arg4, arg5 }; + return invoke (method, args, 5); +} + +//============================================================================== +int var::size() const +{ + if (const Array* const array = getArray()) + return array->size(); + + return 0; +} + +const var& var::operator[] (int arrayIndex) const +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +var& var::operator[] (int arrayIndex) +{ + const Array* const array = getArray(); + + // When using this method, the var must actually be an array, and the index + // must be in-range! + jassert (array != nullptr && isPositiveAndBelow (arrayIndex, array->size())); + + return array->getReference (arrayIndex); +} + +Array* var::convertToArray() +{ + if (Array* array = getArray()) + return array; + + Array tempVar; + if (! isVoid()) + tempVar.add (*this); + + *this = tempVar; + return getArray(); +} + +void var::append (const var& n) +{ + convertToArray()->add (n); +} + +void var::remove (const int index) +{ + if (Array* const array = getArray()) + array->remove (index); +} + +void var::insert (const int index, const var& n) +{ + convertToArray()->insert (index, n); +} + +void var::resize (const int numArrayElementsWanted) +{ + convertToArray()->resize (numArrayElementsWanted); +} + +int var::indexOf (const var& n) const +{ + if (const Array* const array = getArray()) + return array->indexOf (n); + + return -1; +} + +//============================================================================== +void var::writeToStream (OutputStream& output) const +{ + type->writeToStream (value, output); +} + +var var::readFromStream (InputStream& input) +{ + const int numBytes = input.readCompressedInt(); + + if (numBytes > 0) + { + switch (input.readByte()) + { + case varMarker_Int: return var (input.readInt()); + case varMarker_Int64: return var (input.readInt64()); + case varMarker_BoolTrue: return var (true); + case varMarker_BoolFalse: return var (false); + case varMarker_Double: return var (input.readDouble()); + case varMarker_String: + { + MemoryOutputStream mo; + mo.writeFromInputStream (input, numBytes - 1); + return var (mo.toUTF8()); + } + + case varMarker_Binary: + { + MemoryBlock mb ((size_t) numBytes - 1); + + if (numBytes > 1) + { + const int numRead = input.read (mb.getData(), numBytes - 1); + mb.setSize ((size_t) numRead); + } + + return var (mb); + } + + case varMarker_Array: + { + var v; + Array* const destArray = v.convertToArray(); + + for (int i = input.readCompressedInt(); --i >= 0;) + destArray->add (readFromStream (input)); + + return v; + } + + default: + input.skipNextBytes (numBytes - 1); break; + } + } + + return var(); +} + +var::NativeFunctionArgs::NativeFunctionArgs (const var& t, const var* args, int numArgs) noexcept + : thisObject (t), arguments (args), numArguments (numArgs) +{} diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.h b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.h new file mode 100644 index 0000000..41a4884 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.h @@ -0,0 +1,327 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_VARIANT_H_INCLUDED +#define JUCE_VARIANT_H_INCLUDED + + +//============================================================================== +/** + A variant class, that can be used to hold a range of primitive values. + + A var object can hold a range of simple primitive values, strings, or + any kind of ReferenceCountedObject. The var class is intended to act like + the kind of values used in dynamic scripting languages. + + You can save/load var objects either in a small, proprietary binary format + using writeToStream()/readFromStream(), or as JSON by using the JSON class. + + @see JSON, DynamicObject +*/ +class JUCE_API var +{ +public: + //============================================================================== + /** This structure is passed to a NativeFunction callback, and contains invocation + details about the function's arguments and context. + */ + struct NativeFunctionArgs + { + NativeFunctionArgs (const var& thisObject, const var* args, int numArgs) noexcept; + + const var& thisObject; + const var* arguments; + int numArguments; + + JUCE_DECLARE_NON_COPYABLE (NativeFunctionArgs) + }; + + typedef var (*NativeFunction) (const NativeFunctionArgs&); + typedef Identifier identifier; + + //============================================================================== + /** Creates a void variant. */ + var() noexcept; + + /** Destructor. */ + ~var() noexcept; + + /** A static var object that can be used where you need an empty variant object. */ + static const var null; + + var (const var& valueToCopy); + var (int value) noexcept; + var (int64 value) noexcept; + var (bool value) noexcept; + var (double value) noexcept; + var (const char* value); + var (const wchar_t* value); + var (const String& value); + var (const Array& value); + var (ReferenceCountedObject* object); + var (NativeFunction method) noexcept; + var (const void* binaryData, size_t dataSize); + var (const MemoryBlock& binaryData); + + var& operator= (const var& valueToCopy); + var& operator= (int value); + var& operator= (int64 value); + var& operator= (bool value); + var& operator= (double value); + var& operator= (const char* value); + var& operator= (const wchar_t* value); + var& operator= (const String& value); + var& operator= (const Array& value); + var& operator= (ReferenceCountedObject* object); + var& operator= (NativeFunction method); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + var (var&& other) noexcept; + var (String&& value); + var (MemoryBlock&& binaryData); + var (Array&& value); + var& operator= (var&& other) noexcept; + var& operator= (String&& value); + #endif + + void swapWith (var& other) noexcept; + + /** Returns a var object that can be used where you need the javascript "undefined" value. */ + static var undefined() noexcept; + + //============================================================================== + operator int() const noexcept; + operator int64() const noexcept; + operator bool() const noexcept; + operator float() const noexcept; + operator double() const noexcept; + operator String() const; + String toString() const; + + /** If this variant holds an array, this provides access to it. + NOTE: Beware when you use this - the array pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the array! + */ + Array* getArray() const noexcept; + + /** If this variant holds a memory block, this provides access to it. + NOTE: Beware when you use this - the MemoryBlock pointer is only valid for the lifetime + of the variant that returned it, so be very careful not to call this method on temporary + var objects that are the return-value of a function, and which may go out of scope before + you use the MemoryBlock! + */ + MemoryBlock* getBinaryData() const noexcept; + + ReferenceCountedObject* getObject() const noexcept; + DynamicObject* getDynamicObject() const noexcept; + + //============================================================================== + bool isVoid() const noexcept; + bool isUndefined() const noexcept; + bool isInt() const noexcept; + bool isInt64() const noexcept; + bool isBool() const noexcept; + bool isDouble() const noexcept; + bool isString() const noexcept; + bool isObject() const noexcept; + bool isArray() const noexcept; + bool isBinaryData() const noexcept; + bool isMethod() const noexcept; + + /** Returns true if this var has the same value as the one supplied. + Note that this ignores the type, so a string var "123" and an integer var with the + value 123 are considered to be equal. + @see equalsWithSameType + */ + bool equals (const var& other) const noexcept; + + /** Returns true if this var has the same value and type as the one supplied. + This differs from equals() because e.g. "123" and 123 will be considered different. + @see equals + */ + bool equalsWithSameType (const var& other) const noexcept; + + /** Returns true if this var has the same type as the one supplied. */ + bool hasSameTypeAs (const var& other) const noexcept; + + /** Returns a deep copy of this object. + For simple types this just returns a copy, but if the object contains any arrays + or DynamicObjects, they will be cloned (recursively). + */ + var clone() const noexcept; + + //============================================================================== + /** If the var is an array, this returns the number of elements. + If the var isn't actually an array, this will return 0. + */ + int size() const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + const var& operator[] (int arrayIndex) const; + + /** If the var is an array, this can be used to return one of its elements. + To call this method, you must make sure that the var is actually an array, and + that the index is a valid number. If these conditions aren't met, behaviour is + undefined. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + var& operator[] (int arrayIndex); + + /** Appends an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be appended to it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void append (const var& valueToAppend); + + /** Inserts an element to the var, converting it to an array if it isn't already one. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array. The parameter value + will then be inserted into it. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void insert (int index, const var& value); + + /** If the var is an array, this removes one of its elements. + If the index is out-of-range or the var isn't an array, nothing will be done. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void remove (int index); + + /** Treating the var as an array, this resizes it to contain the specified number of elements. + If the var isn't an array, it will be converted to one, and if its value was non-void, + this value will be kept as the first element of the new array before resizing. + For more control over the array's contents, you can call getArray() and manipulate + it directly as an Array\. + */ + void resize (int numArrayElementsWanted); + + /** If the var is an array, this searches it for the first occurrence of the specified value, + and returns its index. + If the var isn't an array, or if the value isn't found, this returns -1. + */ + int indexOf (const var& value) const; + + //============================================================================== + /** If this variant is an object, this returns one of its properties. */ + var operator[] (Identifier propertyName) const; + /** If this variant is an object, this returns one of its properties. */ + var operator[] (const char* propertyName) const; + /** If this variant is an object, this returns one of its properties, or a default + fallback value if the property is not set. */ + var getProperty (Identifier propertyName, const var& defaultReturnValue) const; + + /** Invokes a named method call with no arguments. */ + var call (Identifier method) const; + /** Invokes a named method call with one argument. */ + var call (Identifier method, const var& arg1) const; + /** Invokes a named method call with 2 arguments. */ + var call (Identifier method, const var& arg1, const var& arg2) const; + /** Invokes a named method call with 3 arguments. */ + var call (Identifier method, const var& arg1, const var& arg2, const var& arg3); + /** Invokes a named method call with 4 arguments. */ + var call (Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const; + /** Invokes a named method call with 5 arguments. */ + var call (Identifier method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const; + /** Invokes a named method call with a list of arguments. */ + var invoke (Identifier method, const var* arguments, int numArguments) const; + /** If this object is a method, this returns the function pointer. */ + NativeFunction getNativeFunction() const; + + //============================================================================== + /** Writes a binary representation of this value to a stream. + The data can be read back later using readFromStream(). + @see JSON + */ + void writeToStream (OutputStream& output) const; + + /** Reads back a stored binary representation of a value. + The data in the stream must have been written using writeToStream(), or this + will have unpredictable results. + @see JSON + */ + static var readFromStream (InputStream& input); + +private: + //============================================================================== + class VariantType; friend class VariantType; + class VariantType_Void; friend class VariantType_Void; + class VariantType_Undefined; friend class VariantType_Undefined; + class VariantType_Int; friend class VariantType_Int; + class VariantType_Int64; friend class VariantType_Int64; + class VariantType_Double; friend class VariantType_Double; + class VariantType_Bool; friend class VariantType_Bool; + class VariantType_String; friend class VariantType_String; + class VariantType_Object; friend class VariantType_Object; + class VariantType_Array; friend class VariantType_Array; + class VariantType_Binary; friend class VariantType_Binary; + class VariantType_Method; friend class VariantType_Method; + + union ValueUnion + { + int intValue; + int64 int64Value; + bool boolValue; + double doubleValue; + char stringValue [sizeof (String)]; + ReferenceCountedObject* objectValue; + MemoryBlock* binaryValue; + NativeFunction methodValue; + }; + + const VariantType* type; + ValueUnion value; + + Array* convertToArray(); + var (const VariantType&) noexcept; +}; + +/** Compares the values of two var objects, using the var::equals() comparison. */ +bool operator== (const var& v1, const var& v2) noexcept; +/** Compares the values of two var objects, using the var::equals() comparison. */ +bool operator!= (const var& v1, const var& v2) noexcept; +bool operator== (const var& v1, const String& v2); +bool operator!= (const var& v1, const String& v2); +bool operator== (const var& v1, const char* v2); +bool operator!= (const var& v1, const char* v2); + + +#endif // JUCE_VARIANT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.cpp b/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.cpp new file mode 100644 index 0000000..f2e09a6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.cpp @@ -0,0 +1,166 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +DirectoryIterator::DirectoryIterator (const File& directory, bool recursive, + const String& pattern, const int type) + : wildCards (parseWildcards (pattern)), + fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), + wildCard (pattern), + path (File::addTrailingSeparator (directory.getFullPathName())), + index (-1), + totalNumFiles (-1), + whatToLookFor (type), + isRecursive (recursive), + hasBeenAdvanced (false) +{ + // you have to specify the type of files you're looking for! + jassert ((type & (File::findFiles | File::findDirectories)) != 0); + jassert (type > 0 && type <= 7); +} + +DirectoryIterator::~DirectoryIterator() +{ +} + +StringArray DirectoryIterator::parseWildcards (const String& pattern) +{ + StringArray s; + s.addTokens (pattern, ";,", "\"'"); + s.trim(); + s.removeEmptyStrings(); + return s; +} + +bool DirectoryIterator::fileMatches (const StringArray& wildCards, const String& filename) +{ + for (int i = 0; i < wildCards.size(); ++i) + if (filename.matchesWildcard (wildCards[i], ! File::areFileNamesCaseSensitive())) + return true; + + return false; +} + +bool DirectoryIterator::next() +{ + return next (nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); +} + +bool DirectoryIterator::next (bool* const isDirResult, bool* const isHiddenResult, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + for (;;) + { + hasBeenAdvanced = true; + + if (subIterator != nullptr) + { + if (subIterator->next (isDirResult, isHiddenResult, fileSize, modTime, creationTime, isReadOnly)) + return true; + + subIterator = nullptr; + } + + String filename; + bool isDirectory, isHidden = false, shouldContinue = false; + + while (fileFinder.next (filename, &isDirectory, + (isHiddenResult != nullptr || (whatToLookFor & File::ignoreHiddenFiles) != 0) ? &isHidden : nullptr, + fileSize, modTime, creationTime, isReadOnly)) + { + ++index; + + if (! filename.containsOnly (".")) + { + bool matches = false; + + if (isDirectory) + { + if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) + subIterator = new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename), + true, wildCard, whatToLookFor); + + matches = (whatToLookFor & File::findDirectories) != 0; + } + else + { + matches = (whatToLookFor & File::findFiles) != 0; + } + + // if we're not relying on the OS iterator to do the wildcard match, do it now.. + if (matches && (isRecursive || wildCards.size() > 1)) + matches = fileMatches (wildCards, filename); + + if (matches && (whatToLookFor & File::ignoreHiddenFiles) != 0) + matches = ! isHidden; + + if (matches) + { + currentFile = File::createFileWithoutCheckingPath (path + filename); + if (isHiddenResult != nullptr) *isHiddenResult = isHidden; + if (isDirResult != nullptr) *isDirResult = isDirectory; + + return true; + } + + if (subIterator != nullptr) + { + shouldContinue = true; + break; + } + } + } + + if (! shouldContinue) + return false; + } +} + +const File& DirectoryIterator::getFile() const +{ + if (subIterator != nullptr && subIterator->hasBeenAdvanced) + return subIterator->getFile(); + + // You need to call DirectoryIterator::next() before asking it for the file that it found! + jassert (hasBeenAdvanced); + + return currentFile; +} + +float DirectoryIterator::getEstimatedProgress() const +{ + if (totalNumFiles < 0) + totalNumFiles = File (path).getNumberOfChildFiles (File::findFilesAndDirectories); + + if (totalNumFiles <= 0) + return 0.0f; + + const float detailedIndex = (subIterator != nullptr) ? index + subIterator->getEstimatedProgress() + : (float) index; + + return detailedIndex / totalNumFiles; +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.h b/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.h new file mode 100644 index 0000000..3ca2265 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_DirectoryIterator.h @@ -0,0 +1,159 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DIRECTORYITERATOR_H_INCLUDED +#define JUCE_DIRECTORYITERATOR_H_INCLUDED + + +//============================================================================== +/** + Searches through the files in a directory, returning each file that is found. + + A DirectoryIterator will search through a directory and its subdirectories using + a wildcard filepattern match. + + If you may be scanning a large number of files, it's usually smarter to use this + class than File::findChildFiles() because it allows you to stop at any time, rather + than having to wait for the entire scan to finish before getting the results. + + It also provides an estimate of its progress, using a (highly inaccurate!) algorithm. +*/ +class JUCE_API DirectoryIterator +{ +public: + //============================================================================== + /** Creates a DirectoryIterator for a given directory. + + After creating one of these, call its next() method to get the + first file - e.g. @code + + DirectoryIterator iter (File ("/animals/mooses"), true, "*.moose"); + + while (iter.next()) + { + File theFileItFound (iter.getFile()); + + ... etc + } + @endcode + + @param directory the directory to search in + @param isRecursive whether all the subdirectories should also be searched + @param wildCard the file pattern to match. This may contain multiple patterns + separated by a semi-colon or comma, e.g. "*.jpg;*.png" + @param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying + whether to look for files, directories, or both. + */ + DirectoryIterator (const File& directory, + bool isRecursive, + const String& wildCard = "*", + int whatToLookFor = File::findFiles); + + /** Destructor. */ + ~DirectoryIterator(); + + /** Moves the iterator along to the next file. + + @returns true if a file was found (you can then use getFile() to see what it was) - or + false if there are no more matching files. + */ + bool next(); + + /** Moves the iterator along to the next file, and returns various properties of that file. + + If you need to find out details about the file, it's more efficient to call this method than + to call the normal next() method and then find out the details afterwards. + + All the parameters are optional, so pass null pointers for any items that you're not + interested in. + + @returns true if a file was found (you can then use getFile() to see what it was) - or + false if there are no more matching files. If it returns false, then none of the + parameters will be filled-in. + */ + bool next (bool* isDirectory, + bool* isHidden, + int64* fileSize, + Time* modTime, + Time* creationTime, + bool* isReadOnly); + + /** Returns the file that the iterator is currently pointing at. + + The result of this call is only valid after a call to next() has returned true. + */ + const File& getFile() const; + + /** Returns a guess of how far through the search the iterator has got. + + @returns a value 0.0 to 1.0 to show the progress, although this won't be + very accurate. + */ + float getEstimatedProgress() const; + +private: + //============================================================================== + class NativeIterator + { + public: + NativeIterator (const File& directory, const String& wildCard); + ~NativeIterator(); + + bool next (String& filenameFound, + bool* isDirectory, bool* isHidden, int64* fileSize, + Time* modTime, Time* creationTime, bool* isReadOnly); + + class Pimpl; + + private: + friend class DirectoryIterator; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeIterator) + }; + + friend struct ContainerDeletePolicy; + StringArray wildCards; + NativeIterator fileFinder; + String wildCard, path; + int index; + mutable int totalNumFiles; + const int whatToLookFor; + const bool isRecursive; + bool hasBeenAdvanced; + ScopedPointer subIterator; + File currentFile; + + static StringArray parseWildcards (const String& pattern); + static bool fileMatches (const StringArray& wildCards, const String& filename); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryIterator) +}; + +#endif // JUCE_DIRECTORYITERATOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_File.cpp b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp new file mode 100644 index 0000000..19d724b --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp @@ -0,0 +1,1091 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +File::File (const String& fullPathName) + : fullPath (parseAbsolutePath (fullPathName)) +{ +} + +File File::createFileWithoutCheckingPath (const String& path) noexcept +{ + File f; + f.fullPath = path; + return f; +} + +File::File (const File& other) + : fullPath (other.fullPath) +{ +} + +File& File::operator= (const String& newPath) +{ + fullPath = parseAbsolutePath (newPath); + return *this; +} + +File& File::operator= (const File& other) +{ + fullPath = other.fullPath; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +File::File (File&& other) noexcept + : fullPath (static_cast (other.fullPath)) +{ +} + +File& File::operator= (File&& other) noexcept +{ + fullPath = static_cast (other.fullPath); + return *this; +} +#endif + +const File File::nonexistent; + + +//============================================================================== +String File::parseAbsolutePath (const String& p) +{ + if (p.isEmpty()) + return String(); + +#if JUCE_WINDOWS + // Windows.. + String path (p.replaceCharacter ('/', '\\')); + + if (path.startsWithChar (separator)) + { + if (path[1] != separator) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + path = File::getCurrentWorkingDirectory().getFullPathName().substring (0, 2) + path; + } + } + else if (! path.containsChar (':')) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#else + // Mac or Linux.. + + // Yes, I know it's legal for a unix pathname to contain a backslash, but this assertion is here + // to catch anyone who's trying to run code that was written on Windows with hard-coded path names. + // If that's why you've ended up here, use File::getChildFile() to build your paths instead. + jassert ((! p.containsChar ('\\')) || (p.indexOfChar ('/') >= 0 && p.indexOfChar ('/') < p.indexOfChar ('\\'))); + + String path (p); + + if (path.startsWithChar ('~')) + { + if (path[1] == separator || path[1] == 0) + { + // expand a name of the form "~/abc" + path = File::getSpecialLocation (File::userHomeDirectory).getFullPathName() + + path.substring (1); + } + else + { + // expand a name of type "~dave/abc" + const String userName (path.substring (1).upToFirstOccurrenceOf ("/", false, false)); + + if (struct passwd* const pw = getpwnam (userName.toUTF8())) + path = addTrailingSeparator (pw->pw_dir) + path.fromFirstOccurrenceOf ("/", false, false); + } + } + else if (! path.startsWithChar (separator)) + { + #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS + if (! (path.startsWith ("./") || path.startsWith ("../"))) + { + /* When you supply a raw string to the File object constructor, it must be an absolute path. + If you're trying to parse a string that may be either a relative path or an absolute path, + you MUST provide a context against which the partial path can be evaluated - you can do + this by simply using File::getChildFile() instead of the File constructor. E.g. saying + "File::getCurrentWorkingDirectory().getChildFile (myUnknownPath)" would return an absolute + path if that's what was supplied, or would evaluate a partial path relative to the CWD. + */ + jassertfalse; + + #if JUCE_LOG_ASSERTIONS + Logger::writeToLog ("Illegal absolute path: " + path); + #endif + } + #endif + + return File::getCurrentWorkingDirectory().getChildFile (path).getFullPathName(); + } +#endif + + while (path.endsWithChar (separator) && path != separatorString) // careful not to turn a single "/" into an empty string. + path = path.dropLastCharacters (1); + + return path; +} + +String File::addTrailingSeparator (const String& path) +{ + return path.endsWithChar (separator) ? path + : path + separator; +} + +//============================================================================== +#if JUCE_LINUX + #define NAMES_ARE_CASE_SENSITIVE 1 +#endif + +bool File::areFileNamesCaseSensitive() +{ + #if NAMES_ARE_CASE_SENSITIVE + return true; + #else + return false; + #endif +} + +static int compareFilenames (const String& name1, const String& name2) noexcept +{ + #if NAMES_ARE_CASE_SENSITIVE + return name1.compare (name2); + #else + return name1.compareIgnoreCase (name2); + #endif +} + +bool File::operator== (const File& other) const { return compareFilenames (fullPath, other.fullPath) == 0; } +bool File::operator!= (const File& other) const { return compareFilenames (fullPath, other.fullPath) != 0; } +bool File::operator< (const File& other) const { return compareFilenames (fullPath, other.fullPath) < 0; } +bool File::operator> (const File& other) const { return compareFilenames (fullPath, other.fullPath) > 0; } + +//============================================================================== +bool File::setReadOnly (const bool shouldBeReadOnly, + const bool applyRecursively) const +{ + bool worked = true; + + if (applyRecursively && isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).setReadOnly (shouldBeReadOnly, true) && worked; + } + + return setFileReadOnlyInternal (shouldBeReadOnly) && worked; +} + +bool File::setExecutePermission (bool shouldBeExecutable) const +{ + return setFileExecutableInternal (shouldBeExecutable); +} + +bool File::deleteRecursively() const +{ + bool worked = true; + + if (isDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFilesAndDirectories, false); + + for (int i = subFiles.size(); --i >= 0;) + worked = subFiles.getReference(i).deleteRecursively() && worked; + } + + return deleteFile() && worked; +} + +bool File::moveFileTo (const File& newFile) const +{ + if (newFile.fullPath == fullPath) + return true; + + if (! exists()) + return false; + + #if ! NAMES_ARE_CASE_SENSITIVE + if (*this != newFile) + #endif + if (! newFile.deleteFile()) + return false; + + return moveInternal (newFile); +} + +bool File::copyFileTo (const File& newFile) const +{ + return (*this == newFile) + || (exists() && newFile.deleteFile() && copyInternal (newFile)); +} + +bool File::copyDirectoryTo (const File& newDirectory) const +{ + if (isDirectory() && newDirectory.createDirectory()) + { + Array subFiles; + findChildFiles (subFiles, File::findFiles, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyFileTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + subFiles.clear(); + findChildFiles (subFiles, File::findDirectories, false); + + for (int i = 0; i < subFiles.size(); ++i) + if (! subFiles.getReference(i).copyDirectoryTo (newDirectory.getChildFile (subFiles.getReference(i).getFileName()))) + return false; + + return true; + } + + return false; +} + +//============================================================================== +String File::getPathUpToLastSlash() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator); + + if (lastSlash > 0) + return fullPath.substring (0, lastSlash); + + if (lastSlash == 0) + return separatorString; + + return fullPath; +} + +File File::getParentDirectory() const +{ + File f; + f.fullPath = getPathUpToLastSlash(); + return f; +} + +//============================================================================== +String File::getFileName() const +{ + return fullPath.substring (fullPath.lastIndexOfChar (separator) + 1); +} + +String File::getFileNameWithoutExtension() const +{ + const int lastSlash = fullPath.lastIndexOfChar (separator) + 1; + const int lastDot = fullPath.lastIndexOfChar ('.'); + + if (lastDot > lastSlash) + return fullPath.substring (lastSlash, lastDot); + + return fullPath.substring (lastSlash); +} + +bool File::isAChildOf (const File& potentialParent) const +{ + if (potentialParent.fullPath.isEmpty()) + return false; + + const String ourPath (getPathUpToLastSlash()); + + if (compareFilenames (potentialParent.fullPath, ourPath) == 0) + return true; + + if (potentialParent.fullPath.length() >= ourPath.length()) + return false; + + return getParentDirectory().isAChildOf (potentialParent); +} + +int File::hashCode() const { return fullPath.hashCode(); } +int64 File::hashCode64() const { return fullPath.hashCode64(); } + +//============================================================================== +bool File::isAbsolutePath (StringRef path) +{ + return path.text[0] == separator + #if JUCE_WINDOWS + || (path.isNotEmpty() && path.text[1] == ':'); + #else + || path.text[0] == '~'; + #endif +} + +File File::getChildFile (StringRef relativePath) const +{ + if (isAbsolutePath (relativePath)) + return File (String (relativePath.text)); + + if (relativePath[0] != '.') + return File (addTrailingSeparator (fullPath) + relativePath); + + String path (fullPath); + + // It's relative, so remove any ../ or ./ bits at the start.. + #if JUCE_WINDOWS + if (relativePath.text.indexOf ((juce_wchar) '/') >= 0) + return getChildFile (String (relativePath.text).replaceCharacter ('/', '\\')); + #endif + + while (relativePath[0] == '.') + { + const juce_wchar secondChar = relativePath[1]; + + if (secondChar == '.') + { + const juce_wchar thirdChar = relativePath[2]; + + if (thirdChar == 0 || thirdChar == separator) + { + const int lastSlash = path.lastIndexOfChar (separator); + if (lastSlash >= 0) + path = path.substring (0, lastSlash); + + relativePath = relativePath.text + (thirdChar == 0 ? 2 : 3); + } + else + { + break; + } + } + else if (secondChar == separator) + { + relativePath = relativePath.text + 2; + } + else + { + break; + } + } + + return File (addTrailingSeparator (path) + relativePath); +} + +File File::getSiblingFile (StringRef fileName) const +{ + return getParentDirectory().getChildFile (fileName); +} + +//============================================================================== +String File::descriptionOfSizeInBytes (const int64 bytes) +{ + const char* suffix; + double divisor = 0; + + if (bytes == 1) { suffix = " byte"; } + else if (bytes < 1024) { suffix = " bytes"; } + else if (bytes < 1024 * 1024) { suffix = " KB"; divisor = 1024.0; } + else if (bytes < 1024 * 1024 * 1024) { suffix = " MB"; divisor = 1024.0 * 1024.0; } + else { suffix = " GB"; divisor = 1024.0 * 1024.0 * 1024.0; } + + return (divisor > 0 ? String (bytes / divisor, 1) : String (bytes)) + suffix; +} + +//============================================================================== +Result File::create() const +{ + if (exists()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + { + FileOutputStream fo (*this, 8); + r = fo.getStatus(); + } + + return r; +} + +Result File::createDirectory() const +{ + if (isDirectory()) + return Result::ok(); + + const File parentDir (getParentDirectory()); + + if (parentDir == *this) + return Result::fail ("Cannot create parent directory"); + + Result r (parentDir.createDirectory()); + + if (r.wasOk()) + r = createDirectoryInternal (fullPath.trimCharactersAtEnd (separatorString)); + + return r; +} + +//============================================================================== +Time File::getLastModificationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (m); } +Time File::getLastAccessTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (a); } +Time File::getCreationTime() const { int64 m, a, c; getFileTimesInternal (m, a, c); return Time (c); } + +bool File::setLastModificationTime (Time t) const { return setFileTimesInternal (t.toMilliseconds(), 0, 0); } +bool File::setLastAccessTime (Time t) const { return setFileTimesInternal (0, t.toMilliseconds(), 0); } +bool File::setCreationTime (Time t) const { return setFileTimesInternal (0, 0, t.toMilliseconds()); } + +//============================================================================== +bool File::loadFileAsData (MemoryBlock& destBlock) const +{ + if (! existsAsFile()) + return false; + + FileInputStream in (*this); + return in.openedOk() && getSize() == (int64) in.readIntoMemoryBlock (destBlock); +} + +String File::loadFileAsString() const +{ + if (! existsAsFile()) + return String(); + + FileInputStream in (*this); + return in.openedOk() ? in.readEntireStreamAsString() + : String(); +} + +void File::readLines (StringArray& destLines) const +{ + destLines.addLines (loadFileAsString()); +} + +//============================================================================== +int File::findChildFiles (Array& results, + const int whatToLookFor, + const bool searchRecursively, + const String& wildCardPattern) const +{ + int total = 0; + + for (DirectoryIterator di (*this, searchRecursively, wildCardPattern, whatToLookFor); di.next();) + { + results.add (di.getFile()); + ++total; + } + + return total; +} + +int File::getNumberOfChildFiles (const int whatToLookFor, const String& wildCardPattern) const +{ + int total = 0; + + for (DirectoryIterator di (*this, false, wildCardPattern, whatToLookFor); di.next();) + ++total; + + return total; +} + +bool File::containsSubDirectories() const +{ + if (! isDirectory()) + return false; + + DirectoryIterator di (*this, false, "*", findDirectories); + return di.next(); +} + +//============================================================================== +File File::getNonexistentChildFile (const String& suggestedPrefix, + const String& suffix, + bool putNumbersInBrackets) const +{ + File f (getChildFile (suggestedPrefix + suffix)); + + if (f.exists()) + { + int number = 1; + String prefix (suggestedPrefix); + + // remove any bracketed numbers that may already be on the end.. + if (prefix.trim().endsWithChar (')')) + { + putNumbersInBrackets = true; + + const int openBracks = prefix.lastIndexOfChar ('('); + const int closeBracks = prefix.lastIndexOfChar (')'); + + if (openBracks > 0 + && closeBracks > openBracks + && prefix.substring (openBracks + 1, closeBracks).containsOnly ("0123456789")) + { + number = prefix.substring (openBracks + 1, closeBracks).getIntValue(); + prefix = prefix.substring (0, openBracks); + } + } + + // also use brackets if it ends in a digit. + putNumbersInBrackets = putNumbersInBrackets + || CharacterFunctions::isDigit (prefix.getLastCharacter()); + + do + { + String newName (prefix); + + if (putNumbersInBrackets) + newName << '(' << ++number << ')'; + else + newName << ++number; + + f = getChildFile (newName + suffix); + + } while (f.exists()); + } + + return f; +} + +File File::getNonexistentSibling (const bool putNumbersInBrackets) const +{ + if (! exists()) + return *this; + + return getParentDirectory().getNonexistentChildFile (getFileNameWithoutExtension(), + getFileExtension(), + putNumbersInBrackets); +} + +//============================================================================== +String File::getFileExtension() const +{ + const int indexOfDot = fullPath.lastIndexOfChar ('.'); + + if (indexOfDot > fullPath.lastIndexOfChar (separator)) + return fullPath.substring (indexOfDot); + + return String(); +} + +bool File::hasFileExtension (StringRef possibleSuffix) const +{ + if (possibleSuffix.isEmpty()) + return fullPath.lastIndexOfChar ('.') <= fullPath.lastIndexOfChar (separator); + + const int semicolon = possibleSuffix.text.indexOf ((juce_wchar) ';'); + + if (semicolon >= 0) + return hasFileExtension (String (possibleSuffix.text).substring (0, semicolon).trimEnd()) + || hasFileExtension ((possibleSuffix.text + (semicolon + 1)).findEndOfWhitespace()); + + if (fullPath.endsWithIgnoreCase (possibleSuffix)) + { + if (possibleSuffix.text[0] == '.') + return true; + + const int dotPos = fullPath.length() - possibleSuffix.length() - 1; + + if (dotPos >= 0) + return fullPath [dotPos] == '.'; + } + + return false; +} + +File File::withFileExtension (StringRef newExtension) const +{ + if (fullPath.isEmpty()) + return File(); + + String filePart (getFileName()); + + const int i = filePart.lastIndexOfChar ('.'); + if (i >= 0) + filePart = filePart.substring (0, i); + + if (newExtension.isNotEmpty() && newExtension.text[0] != '.') + filePart << '.'; + + return getSiblingFile (filePart + newExtension); +} + +//============================================================================== +bool File::startAsProcess (const String& parameters) const +{ + return exists() && Process::openDocument (fullPath, parameters); +} + +//============================================================================== +FileInputStream* File::createInputStream() const +{ + ScopedPointer fin (new FileInputStream (*this)); + + if (fin->openedOk()) + return fin.release(); + + return nullptr; +} + +FileOutputStream* File::createOutputStream (const size_t bufferSize) const +{ + ScopedPointer out (new FileOutputStream (*this, bufferSize)); + + return out->failedToOpen() ? nullptr + : out.release(); +} + +//============================================================================== +bool File::appendData (const void* const dataToAppend, + const size_t numberOfBytes) const +{ + jassert (((ssize_t) numberOfBytes) >= 0); + + if (numberOfBytes == 0) + return true; + + FileOutputStream out (*this, 8192); + return out.openedOk() && out.write (dataToAppend, numberOfBytes); +} + +bool File::replaceWithData (const void* const dataToWrite, + const size_t numberOfBytes) const +{ + if (numberOfBytes == 0) + return deleteFile(); + + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendData (dataToWrite, numberOfBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::appendText (const String& text, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + FileOutputStream out (*this); + + if (out.failedToOpen()) + return false; + + out.writeText (text, asUnicode, writeUnicodeHeaderBytes); + return true; +} + +bool File::replaceWithText (const String& textToWrite, + const bool asUnicode, + const bool writeUnicodeHeaderBytes) const +{ + TemporaryFile tempFile (*this, TemporaryFile::useHiddenFile); + tempFile.getFile().appendText (textToWrite, asUnicode, writeUnicodeHeaderBytes); + return tempFile.overwriteTargetFileWithTemporary(); +} + +bool File::hasIdenticalContentTo (const File& other) const +{ + if (other == *this) + return true; + + if (getSize() == other.getSize() && existsAsFile() && other.existsAsFile()) + { + FileInputStream in1 (*this), in2 (other); + + if (in1.openedOk() && in2.openedOk()) + { + const int bufferSize = 4096; + HeapBlock buffer1 (bufferSize), buffer2 (bufferSize); + + for (;;) + { + const int num1 = in1.read (buffer1, bufferSize); + const int num2 = in2.read (buffer2, bufferSize); + + if (num1 != num2) + break; + + if (num1 <= 0) + return true; + + if (memcmp (buffer1, buffer2, (size_t) num1) != 0) + break; + } + } + } + + return false; +} + +//============================================================================== +String File::createLegalPathName (const String& original) +{ + String s (original); + String start; + + if (s.isNotEmpty() && s[1] == ':') + { + start = s.substring (0, 2); + s = s.substring (2); + } + + return start + s.removeCharacters ("\"#@,;:<>*^|?") + .substring (0, 1024); +} + +String File::createLegalFileName (const String& original) +{ + String s (original.removeCharacters ("\"#@,;:<>*^|?\\/")); + + const int maxLength = 128; // only the length of the filename, not the whole path + const int len = s.length(); + + if (len > maxLength) + { + const int lastDot = s.lastIndexOfChar ('.'); + + if (lastDot > jmax (0, len - 12)) + { + s = s.substring (0, maxLength - (len - lastDot)) + + s.substring (lastDot); + } + else + { + s = s.substring (0, maxLength); + } + } + + return s; +} + +//============================================================================== +static int countNumberOfSeparators (String::CharPointerType s) +{ + int num = 0; + + for (;;) + { + const juce_wchar c = s.getAndAdvance(); + + if (c == 0) + break; + + if (c == File::separator) + ++num; + } + + return num; +} + +String File::getRelativePathFrom (const File& dir) const +{ + String thisPath (fullPath); + + while (thisPath.endsWithChar (separator)) + thisPath = thisPath.dropLastCharacters (1); + + String dirPath (addTrailingSeparator (dir.existsAsFile() ? dir.getParentDirectory().getFullPathName() + : dir.fullPath)); + + int commonBitLength = 0; + String::CharPointerType thisPathAfterCommon (thisPath.getCharPointer()); + String::CharPointerType dirPathAfterCommon (dirPath.getCharPointer()); + + { + String::CharPointerType thisPathIter (thisPath.getCharPointer()); + String::CharPointerType dirPathIter (dirPath.getCharPointer()); + + for (int i = 0;;) + { + const juce_wchar c1 = thisPathIter.getAndAdvance(); + const juce_wchar c2 = dirPathIter.getAndAdvance(); + + #if NAMES_ARE_CASE_SENSITIVE + if (c1 != c2 + #else + if ((c1 != c2 && CharacterFunctions::toLowerCase (c1) != CharacterFunctions::toLowerCase (c2)) + #endif + || c1 == 0) + break; + + ++i; + + if (c1 == separator) + { + thisPathAfterCommon = thisPathIter; + dirPathAfterCommon = dirPathIter; + commonBitLength = i; + } + } + } + + // if the only common bit is the root, then just return the full path.. + if (commonBitLength == 0 || (commonBitLength == 1 && thisPath[1] == separator)) + return fullPath; + + const int numUpDirectoriesNeeded = countNumberOfSeparators (dirPathAfterCommon); + + if (numUpDirectoriesNeeded == 0) + return thisPathAfterCommon; + + #if JUCE_WINDOWS + String s (String::repeatedString ("..\\", numUpDirectoriesNeeded)); + #else + String s (String::repeatedString ("../", numUpDirectoriesNeeded)); + #endif + s.appendCharPointer (thisPathAfterCommon); + return s; +} + +//============================================================================== +File File::createTempFile (StringRef fileNameEnding) +{ + const File tempFile (getSpecialLocation (tempDirectory) + .getChildFile ("temp_" + String::toHexString (Random::getSystemRandom().nextInt())) + .withFileExtension (fileNameEnding)); + + if (tempFile.exists()) + return createTempFile (fileNameEnding); + + return tempFile; +} + +//============================================================================== +MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) + : address (nullptr), range (0, file.getSize()), fileHandle (0) +{ + openInternal (file, mode); +} + +MemoryMappedFile::MemoryMappedFile (const File& file, const Range& fileRange, AccessMode mode) + : address (nullptr), range (fileRange.getIntersectionWith (Range (0, file.getSize()))), fileHandle (0) +{ + openInternal (file, mode); +} + + +//============================================================================== +#if JUCE_UNIT_TESTS + +class FileTests : public UnitTest +{ +public: + FileTests() : UnitTest ("Files") {} + + void runTest() + { + beginTest ("Reading"); + + const File home (File::getSpecialLocation (File::userHomeDirectory)); + const File temp (File::getSpecialLocation (File::tempDirectory)); + + expect (! File::nonexistent.exists()); + expect (home.isDirectory()); + expect (home.exists()); + expect (! home.existsAsFile()); + expect (File::getSpecialLocation (File::userDocumentsDirectory).isDirectory()); + expect (File::getSpecialLocation (File::userApplicationDataDirectory).isDirectory()); + expect (File::getSpecialLocation (File::currentExecutableFile).exists()); + expect (File::getSpecialLocation (File::currentApplicationFile).exists()); + expect (File::getSpecialLocation (File::invokedExecutableFile).exists()); + expect (home.getVolumeTotalSize() > 1024 * 1024); + expect (home.getBytesFreeOnVolume() > 0); + expect (! home.isHidden()); + expect (home.isOnHardDisk()); + expect (! home.isOnCDRomDrive()); + expect (File::getCurrentWorkingDirectory().exists()); + expect (home.setAsCurrentWorkingDirectory()); + expect (File::getCurrentWorkingDirectory() == home); + + { + Array roots; + File::findFileSystemRoots (roots); + expect (roots.size() > 0); + + int numRootsExisting = 0; + for (int i = 0; i < roots.size(); ++i) + if (roots[i].exists()) + ++numRootsExisting; + + // (on windows, some of the drives may not contain media, so as long as at least one is ok..) + expect (numRootsExisting > 0); + } + + beginTest ("Writing"); + + File demoFolder (temp.getChildFile ("Juce UnitTests Temp Folder.folder")); + expect (demoFolder.deleteRecursively()); + expect (demoFolder.createDirectory()); + expect (demoFolder.isDirectory()); + expect (demoFolder.getParentDirectory() == temp); + expect (temp.isDirectory()); + + { + Array files; + temp.findChildFiles (files, File::findFilesAndDirectories, false, "*"); + expect (files.contains (demoFolder)); + } + + { + Array files; + temp.findChildFiles (files, File::findDirectories, true, "*.folder"); + expect (files.contains (demoFolder)); + } + + File tempFile (demoFolder.getNonexistentChildFile ("test", ".txt", false)); + + expect (tempFile.getFileExtension() == ".txt"); + expect (tempFile.hasFileExtension (".txt")); + expect (tempFile.hasFileExtension ("txt")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension (".xyz")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("abc;xyz;foo")); + expect (tempFile.withFileExtension ("xyz").hasFileExtension ("xyz;foo")); + expect (! tempFile.withFileExtension ("h").hasFileExtension ("bar;foo;xx")); + expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); + expect (tempFile.hasWriteAccess()); + + { + FileOutputStream fo (tempFile); + fo.write ("0123456789", 10); + } + + expect (tempFile.exists()); + expect (tempFile.getSize() == 10); + expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); + expectEquals (tempFile.loadFileAsString(), String ("0123456789")); + expect (! demoFolder.containsSubDirectories()); + + expectEquals (tempFile.getRelativePathFrom (demoFolder.getParentDirectory()), demoFolder.getFileName() + File::separatorString + tempFile.getFileName()); + expectEquals (demoFolder.getParentDirectory().getRelativePathFrom (tempFile), ".." + File::separatorString + ".." + File::separatorString + demoFolder.getParentDirectory().getFileName()); + + expect (demoFolder.getNumberOfChildFiles (File::findFiles) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 0); + demoFolder.getNonexistentChildFile ("tempFolder", "", false).createDirectory(); + expect (demoFolder.getNumberOfChildFiles (File::findDirectories) == 1); + expect (demoFolder.getNumberOfChildFiles (File::findFilesAndDirectories) == 2); + expect (demoFolder.containsSubDirectories()); + + expect (tempFile.hasWriteAccess()); + tempFile.setReadOnly (true); + expect (! tempFile.hasWriteAccess()); + tempFile.setReadOnly (false); + expect (tempFile.hasWriteAccess()); + + Time t (Time::getCurrentTime()); + tempFile.setLastModificationTime (t); + Time t2 = tempFile.getLastModificationTime(); + expect (std::abs ((int) (t2.toMilliseconds() - t.toMilliseconds())) <= 1000); + + { + MemoryBlock mb; + tempFile.loadFileAsData (mb); + expect (mb.getSize() == 10); + expect (mb[0] == '0'); + } + + { + expect (tempFile.getSize() == 10); + FileOutputStream fo (tempFile); + expect (fo.openedOk()); + + expect (fo.setPosition (7)); + expect (fo.truncate().wasOk()); + expect (tempFile.getSize() == 7); + fo.write ("789", 3); + fo.flush(); + expect (tempFile.getSize() == 10); + } + + beginTest ("Memory-mapped files"); + + { + MemoryMappedFile mmf (tempFile, MemoryMappedFile::readOnly); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "0123456789", 10) == 0); + } + + { + const File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile2.create()); + expect (tempFile2.appendData ("xxxxxxxxxx", 10)); + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + memcpy (mmf.getData(), "abcdefghij", 10); + } + + { + MemoryMappedFile mmf (tempFile2, MemoryMappedFile::readWrite); + expect (mmf.getSize() == 10); + expect (mmf.getData() != nullptr); + expect (memcmp (mmf.getData(), "abcdefghij", 10) == 0); + } + + expect (tempFile2.deleteFile()); + } + + beginTest ("More writing"); + + expect (tempFile.appendData ("abcdefghij", 10)); + expect (tempFile.getSize() == 20); + expect (tempFile.replaceWithData ("abcdefghij", 10)); + expect (tempFile.getSize() == 10); + + File tempFile2 (tempFile.getNonexistentSibling (false)); + expect (tempFile.copyFileTo (tempFile2)); + expect (tempFile2.exists()); + expect (tempFile2.hasIdenticalContentTo (tempFile)); + expect (tempFile.deleteFile()); + expect (! tempFile.exists()); + expect (tempFile2.moveFileTo (tempFile)); + expect (tempFile.exists()); + expect (! tempFile2.exists()); + + expect (demoFolder.deleteRecursively()); + expect (! demoFolder.exists()); + } +}; + +static FileTests fileUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/files/juce_File.h b/JuceLibraryCode/modules/juce_core/files/juce_File.h new file mode 100644 index 0000000..522559a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_File.h @@ -0,0 +1,981 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILE_H_INCLUDED +#define JUCE_FILE_H_INCLUDED + + +//============================================================================== +/** + Represents a local file or directory. + + This class encapsulates the absolute pathname of a file or directory, and + has methods for finding out about the file and changing its properties. + + To read or write to the file, there are methods for returning an input or + output stream. + + @see FileInputStream, FileOutputStream +*/ +class JUCE_API File +{ +public: + //============================================================================== + /** Creates an (invalid) file object. + + The file is initially set to an empty path, so getFullPath() will return + an empty string, and comparing the file to File::nonexistent will return + true. + + You can use its operator= method to point it at a proper file. + */ + File() noexcept {} + + /** Creates a file from an absolute path. + + If the path supplied is a relative path, it is taken to be relative + to the current working directory (see File::getCurrentWorkingDirectory()), + but this isn't a recommended way of creating a file, because you + never know what the CWD is going to be. + + On the Mac/Linux, the path can include "~" notation for referring to + user home directories. + */ + File (const String& absolutePath); + + /** Creates a copy of another file object. */ + File (const File&); + + /** Destructor. */ + ~File() noexcept {} + + /** Sets the file based on an absolute pathname. + + If the path supplied is a relative path, it is taken to be relative + to the current working directory (see File::getCurrentWorkingDirectory()), + but this isn't a recommended way of creating a file, because you + never know what the CWD is going to be. + + On the Mac/Linux, the path can include "~" notation for referring to + user home directories. + */ + File& operator= (const String& newAbsolutePath); + + /** Copies from another file object. */ + File& operator= (const File& otherFile); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + File (File&&) noexcept; + File& operator= (File&&) noexcept; + #endif + + //============================================================================== + /** This static constant is used for referring to an 'invalid' file. */ + static const File nonexistent; + + //============================================================================== + /** Checks whether the file actually exists. + + @returns true if the file exists, either as a file or a directory. + @see existsAsFile, isDirectory + */ + bool exists() const; + + /** Checks whether the file exists and is a file rather than a directory. + + @returns true only if this is a real file, false if it's a directory + or doesn't exist + @see exists, isDirectory + */ + bool existsAsFile() const; + + /** Checks whether the file is a directory that exists. + + @returns true only if the file is a directory which actually exists, so + false if it's a file or doesn't exist at all + @see exists, existsAsFile + */ + bool isDirectory() const; + + /** Returns the size of the file in bytes. + + @returns the number of bytes in the file, or 0 if it doesn't exist. + */ + int64 getSize() const; + + /** Utility function to convert a file size in bytes to a neat string description. + + So for example 100 would return "100 bytes", 2000 would return "2 KB", + 2000000 would produce "2 MB", etc. + */ + static String descriptionOfSizeInBytes (int64 bytes); + + //============================================================================== + /** Returns the complete, absolute path of this file. + + This includes the filename and all its parent folders. On Windows it'll + also include the drive letter prefix; on Mac or Linux it'll be a complete + path starting from the root folder. + + If you just want the file's name, you should use getFileName() or + getFileNameWithoutExtension(). + + @see getFileName, getRelativePathFrom + */ + const String& getFullPathName() const noexcept { return fullPath; } + + /** Returns the last section of the pathname. + + Returns just the final part of the path - e.g. if the whole path + is "/moose/fish/foo.txt" this will return "foo.txt". + + For a directory, it returns the final part of the path - e.g. for the + directory "/moose/fish" it'll return "fish". + + If the filename begins with a dot, it'll return the whole filename, e.g. for + "/moose/.fish", it'll return ".fish" + + @see getFullPathName, getFileNameWithoutExtension + */ + String getFileName() const; + + /** Creates a relative path that refers to a file relatively to a given directory. + + e.g. File ("/moose/foo.txt").getRelativePathFrom (File ("/moose/fish/haddock")) + would return "../../foo.txt". + + If it's not possible to navigate from one file to the other, an absolute + path is returned. If the paths are invalid, an empty string may also be + returned. + + @param directoryToBeRelativeTo the directory which the resultant string will + be relative to. If this is actually a file rather than + a directory, its parent directory will be used instead. + If it doesn't exist, it's assumed to be a directory. + @see getChildFile, isAbsolutePath + */ + String getRelativePathFrom (const File& directoryToBeRelativeTo) const; + + //============================================================================== + /** Returns the file's extension. + + Returns the file extension of this file, also including the dot. + + e.g. "/moose/fish/foo.txt" would return ".txt" + + @see hasFileExtension, withFileExtension, getFileNameWithoutExtension + */ + String getFileExtension() const; + + /** Checks whether the file has a given extension. + + @param extensionToTest the extension to look for - it doesn't matter whether or + not this string has a dot at the start, so ".wav" and "wav" + will have the same effect. To compare with multiple extensions, this + parameter can contain multiple strings, separated by semi-colons - + so, for example: hasFileExtension (".jpeg;png;gif") would return + true if the file has any of those three extensions. + + @see getFileExtension, withFileExtension, getFileNameWithoutExtension + */ + bool hasFileExtension (StringRef extensionToTest) const; + + /** Returns a version of this file with a different file extension. + + e.g. File ("/moose/fish/foo.txt").withFileExtension ("html") returns "/moose/fish/foo.html" + + @param newExtension the new extension, either with or without a dot at the start (this + doesn't make any difference). To get remove a file's extension altogether, + pass an empty string into this function. + + @see getFileName, getFileExtension, hasFileExtension, getFileNameWithoutExtension + */ + File withFileExtension (StringRef newExtension) const; + + /** Returns the last part of the filename, without its file extension. + + e.g. for "/moose/fish/foo.txt" this will return "foo". + + @see getFileName, getFileExtension, hasFileExtension, withFileExtension + */ + String getFileNameWithoutExtension() const; + + //============================================================================== + /** Returns a 32-bit hash-code that identifies this file. + + This is based on the filename. Obviously it's possible, although unlikely, that + two files will have the same hash-code. + */ + int hashCode() const; + + /** Returns a 64-bit hash-code that identifies this file. + + This is based on the filename. Obviously it's possible, although unlikely, that + two files will have the same hash-code. + */ + int64 hashCode64() const; + + //============================================================================== + /** Returns a file that represents a relative (or absolute) sub-path of the current one. + + This will find a child file or directory of the current object. + + e.g. + File ("/moose/fish").getChildFile ("foo.txt") will produce "/moose/fish/foo.txt". + File ("/moose/fish").getChildFile ("haddock/foo.txt") will produce "/moose/fish/haddock/foo.txt". + File ("/moose/fish").getChildFile ("../foo.txt") will produce "/moose/foo.txt". + + If the string is actually an absolute path, it will be treated as such, e.g. + File ("/moose/fish").getChildFile ("/foo.txt") will produce "/foo.txt" + + @see getSiblingFile, getParentDirectory, getRelativePathFrom, isAChildOf + */ + File getChildFile (StringRef relativeOrAbsolutePath) const; + + /** Returns a file which is in the same directory as this one. + + This is equivalent to getParentDirectory().getChildFile (name). + + @see getChildFile, getParentDirectory + */ + File getSiblingFile (StringRef siblingFileName) const; + + //============================================================================== + /** Returns the directory that contains this file or directory. + + e.g. for "/moose/fish/foo.txt" this will return "/moose/fish". + */ + File getParentDirectory() const; + + /** Checks whether a file is somewhere inside a directory. + + Returns true if this file is somewhere inside a subdirectory of the directory + that is passed in. Neither file actually has to exist, because the function + just checks the paths for similarities. + + e.g. File ("/moose/fish/foo.txt").isAChildOf ("/moose") is true. + File ("/moose/fish/foo.txt").isAChildOf ("/moose/fish") is also true. + */ + bool isAChildOf (const File& potentialParentDirectory) const; + + //============================================================================== + /** Chooses a filename relative to this one that doesn't already exist. + + If this file is a directory, this will return a child file of this + directory that doesn't exist, by adding numbers to a prefix and suffix until + it finds one that isn't already there. + + If the prefix + the suffix doesn't exist, it won't bother adding a number. + + e.g. File ("/moose/fish").getNonexistentChildFile ("foo", ".txt", true) might + return "/moose/fish/foo(2).txt" if there's already a file called "foo.txt". + + @param prefix the string to use for the filename before the number + @param suffix the string to add to the filename after the number + @param putNumbersInBrackets if true, this will create filenames in the + format "prefix(number)suffix", if false, it will leave the + brackets out. + */ + File getNonexistentChildFile (const String& prefix, + const String& suffix, + bool putNumbersInBrackets = true) const; + + /** Chooses a filename for a sibling file to this one that doesn't already exist. + + If this file doesn't exist, this will just return itself, otherwise it + will return an appropriate sibling that doesn't exist, e.g. if a file + "/moose/fish/foo.txt" exists, this might return "/moose/fish/foo(2).txt". + + @param putNumbersInBrackets whether to add brackets around the numbers that + get appended to the new filename. + */ + File getNonexistentSibling (bool putNumbersInBrackets = true) const; + + //============================================================================== + /** Compares the pathnames for two files. */ + bool operator== (const File&) const; + /** Compares the pathnames for two files. */ + bool operator!= (const File&) const; + /** Compares the pathnames for two files. */ + bool operator< (const File&) const; + /** Compares the pathnames for two files. */ + bool operator> (const File&) const; + + //============================================================================== + /** Checks whether a file can be created or written to. + + @returns true if it's possible to create and write to this file. If the file + doesn't already exist, this will check its parent directory to + see if writing is allowed. + @see setReadOnly + */ + bool hasWriteAccess() const; + + /** Changes the write-permission of a file or directory. + + @param shouldBeReadOnly whether to add or remove write-permission + @param applyRecursively if the file is a directory and this is true, it will + recurse through all the subfolders changing the permissions + of all files + @returns true if it manages to change the file's permissions. + @see hasWriteAccess + */ + bool setReadOnly (bool shouldBeReadOnly, + bool applyRecursively = false) const; + + /** Changes the execute-permissions of a file. + + @param shouldBeExecutable whether to add or remove execute-permission + @returns true if it manages to change the file's permissions. + */ + bool setExecutePermission (bool shouldBeExecutable) const; + + /** Returns true if this file is a hidden or system file. + The criteria for deciding whether a file is hidden are platform-dependent. + */ + bool isHidden() const; + + /** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ + bool isLink() const; + + /** If this file is a link or alias, this returns the file that it points to. + If the file isn't actually link, it'll just return itself. + */ + File getLinkedTarget() const; + + /** Returns a unique identifier for the file, if one is available. + + Depending on the OS and file-system, this may be a unix inode number or + a win32 file identifier, or 0 if it fails to find one. The number will + be unique on the filesystem, but not globally. + */ + uint64 getFileIdentifier() const; + + //============================================================================== + /** Returns the last modification time of this file. + + @returns the time, or an invalid time if the file doesn't exist. + @see setLastModificationTime, getLastAccessTime, getCreationTime + */ + Time getLastModificationTime() const; + + /** Returns the last time this file was accessed. + + @returns the time, or an invalid time if the file doesn't exist. + @see setLastAccessTime, getLastModificationTime, getCreationTime + */ + Time getLastAccessTime() const; + + /** Returns the time that this file was created. + + @returns the time, or an invalid time if the file doesn't exist. + @see getLastModificationTime, getLastAccessTime + */ + Time getCreationTime() const; + + /** Changes the modification time for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getLastModificationTime, setLastAccessTime, setCreationTime + */ + bool setLastModificationTime (Time newTime) const; + + /** Changes the last-access time for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getLastAccessTime, setLastModificationTime, setCreationTime + */ + bool setLastAccessTime (Time newTime) const; + + /** Changes the creation date for this file. + + @param newTime the time to apply to the file + @returns true if it manages to change the file's time. + @see getCreationTime, setLastModificationTime, setLastAccessTime + */ + bool setCreationTime (Time newTime) const; + + /** If possible, this will try to create a version string for the given file. + + The OS may be able to look at the file and give a version for it - e.g. with + executables, bundles, dlls, etc. If no version is available, this will + return an empty string. + */ + String getVersion() const; + + //============================================================================== + /** Creates an empty file if it doesn't already exist. + + If the file that this object refers to doesn't exist, this will create a file + of zero size. + + If it already exists or is a directory, this method will do nothing. + + @returns true if the file has been created (or if it already existed). + @see createDirectory + */ + Result create() const; + + /** Creates a new directory for this filename. + + This will try to create the file as a directory, and fill also create + any parent directories it needs in order to complete the operation. + + @returns a result to indicate whether the directory was created successfully, or + an error message if it failed. + @see create + */ + Result createDirectory() const; + + /** Deletes a file. + + If this file is actually a directory, it may not be deleted correctly if it + contains files. See deleteRecursively() as a better way of deleting directories. + + @returns true if the file has been successfully deleted (or if it didn't exist to + begin with). + @see deleteRecursively + */ + bool deleteFile() const; + + /** Deletes a file or directory and all its subdirectories. + + If this file is a directory, this will try to delete it and all its subfolders. If + it's just a file, it will just try to delete the file. + + @returns true if the file and all its subfolders have been successfully deleted + (or if it didn't exist to begin with). + @see deleteFile + */ + bool deleteRecursively() const; + + /** Moves this file or folder to the trash. + + @returns true if the operation succeeded. It could fail if the trash is full, or + if the file is write-protected, so you should check the return value + and act appropriately. + */ + bool moveToTrash() const; + + /** Moves or renames a file. + + Tries to move a file to a different location. + If the target file already exists, this will attempt to delete it first, and + will fail if this can't be done. + + Note that the destination file isn't the directory to put it in, it's the actual + filename that you want the new file to have. + + @returns true if the operation succeeds + */ + bool moveFileTo (const File& targetLocation) const; + + /** Copies a file. + + Tries to copy a file to a different location. + If the target file already exists, this will attempt to delete it first, and + will fail if this can't be done. + + @returns true if the operation succeeds + */ + bool copyFileTo (const File& targetLocation) const; + + /** Copies a directory. + + Tries to copy an entire directory, recursively. + + If this file isn't a directory or if any target files can't be created, this + will return false. + + @param newDirectory the directory that this one should be copied to. Note that this + is the name of the actual directory to create, not the directory + into which the new one should be placed, so there must be enough + write privileges to create it if it doesn't exist. Any files inside + it will be overwritten by similarly named ones that are copied. + */ + bool copyDirectoryTo (const File& newDirectory) const; + + //============================================================================== + /** Used in file searching, to specify whether to return files, directories, or both. + */ + enum TypesOfFileToFind + { + findDirectories = 1, /**< Use this flag to indicate that you want to find directories. */ + findFiles = 2, /**< Use this flag to indicate that you want to find files. */ + findFilesAndDirectories = 3, /**< Use this flag to indicate that you want to find both files and directories. */ + ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ + }; + + /** Searches inside a directory for files matching a wildcard pattern. + + Assuming that this file is a directory, this method will search it + for either files or subdirectories whose names match a filename pattern. + + @param results an array to which File objects will be added for the + files that the search comes up with + @param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to + return files, directories, or both. If the ignoreHiddenFiles flag + is also added to this value, hidden files won't be returned + @param searchRecursively if true, all subdirectories will be recursed into to do + an exhaustive search + @param wildCardPattern the filename pattern to search for, e.g. "*.txt" + @returns the number of results that have been found + + @see getNumberOfChildFiles, DirectoryIterator + */ + int findChildFiles (Array& results, + int whatToLookFor, + bool searchRecursively, + const String& wildCardPattern = "*") const; + + /** Searches inside a directory and counts how many files match a wildcard pattern. + + Assuming that this file is a directory, this method will search it + for either files or subdirectories whose names match a filename pattern, + and will return the number of matches found. + + This isn't a recursive call, and will only search this directory, not + its children. + + @param whatToLookFor a value from the TypesOfFileToFind enum, specifying whether to + count files, directories, or both. If the ignoreHiddenFiles flag + is also added to this value, hidden files won't be counted + @param wildCardPattern the filename pattern to search for, e.g. "*.txt" + @returns the number of matches found + @see findChildFiles, DirectoryIterator + */ + int getNumberOfChildFiles (int whatToLookFor, + const String& wildCardPattern = "*") const; + + /** Returns true if this file is a directory that contains one or more subdirectories. + @see isDirectory, findChildFiles + */ + bool containsSubDirectories() const; + + //============================================================================== + /** Creates a stream to read from this file. + + @returns a stream that will read from this file (initially positioned at the + start of the file), or nullptr if the file can't be opened for some reason + @see createOutputStream, loadFileAsData + */ + FileInputStream* createInputStream() const; + + /** Creates a stream to write to this file. + + If the file exists, the stream that is returned will be positioned ready for + writing at the end of the file, so you might want to use deleteFile() first + to write to an empty file. + + @returns a stream that will write to this file (initially positioned at the + end of the file), or nullptr if the file can't be opened for some reason + @see createInputStream, appendData, appendText + */ + FileOutputStream* createOutputStream (size_t bufferSize = 0x8000) const; + + //============================================================================== + /** Loads a file's contents into memory as a block of binary data. + + Of course, trying to load a very large file into memory will blow up, so + it's better to check first. + + @param result the data block to which the file's contents should be appended - note + that if the memory block might already contain some data, you + might want to clear it first + @returns true if the file could all be read into memory + */ + bool loadFileAsData (MemoryBlock& result) const; + + /** Reads a file into memory as a string. + + Attempts to load the entire file as a zero-terminated string. + + This makes use of InputStream::readEntireStreamAsString, which can + read either UTF-16 or UTF-8 file formats. + */ + String loadFileAsString() const; + + /** Reads the contents of this file as text and splits it into lines, which are + appended to the given StringArray. + */ + void readLines (StringArray& destLines) const; + + //============================================================================== + /** Appends a block of binary data to the end of the file. + + This will try to write the given buffer to the end of the file. + + @returns false if it can't write to the file for some reason + */ + bool appendData (const void* dataToAppend, + size_t numberOfBytes) const; + + /** Replaces this file's contents with a given block of data. + + This will delete the file and replace it with the given data. + + A nice feature of this method is that it's safe - instead of deleting + the file first and then re-writing it, it creates a new temporary file, + writes the data to that, and then moves the new file to replace the existing + file. This means that if the power gets pulled out or something crashes, + you're a lot less likely to end up with a corrupted or unfinished file.. + + Returns true if the operation succeeds, or false if it fails. + + @see appendText + */ + bool replaceWithData (const void* dataToWrite, + size_t numberOfBytes) const; + + /** Appends a string to the end of the file. + + This will try to append a text string to the file, as either 16-bit unicode + or 8-bit characters in the default system encoding. + + It can also write the 'ff fe' unicode header bytes before the text to indicate + the endianness of the file. + + Any single \\n characters in the string are replaced with \\r\\n before it is written. + + @see replaceWithText + */ + bool appendText (const String& textToAppend, + bool asUnicode = false, + bool writeUnicodeHeaderBytes = false) const; + + /** Replaces this file's contents with a given text string. + + This will delete the file and replace it with the given text. + + A nice feature of this method is that it's safe - instead of deleting + the file first and then re-writing it, it creates a new temporary file, + writes the text to that, and then moves the new file to replace the existing + file. This means that if the power gets pulled out or something crashes, + you're a lot less likely to end up with an empty file.. + + For an explanation of the parameters here, see the appendText() method. + + Returns true if the operation succeeds, or false if it fails. + + @see appendText + */ + bool replaceWithText (const String& textToWrite, + bool asUnicode = false, + bool writeUnicodeHeaderBytes = false) const; + + /** Attempts to scan the contents of this file and compare it to another file, returning + true if this is possible and they match byte-for-byte. + */ + bool hasIdenticalContentTo (const File& other) const; + + //============================================================================== + /** Creates a set of files to represent each file root. + + e.g. on Windows this will create files for "c:\", "d:\" etc according + to which ones are available. On the Mac/Linux, this will probably + just add a single entry for "/". + */ + static void findFileSystemRoots (Array& results); + + /** Finds the name of the drive on which this file lives. + @returns the volume label of the drive, or an empty string if this isn't possible + */ + String getVolumeLabel() const; + + /** Returns the serial number of the volume on which this file lives. + @returns the serial number, or zero if there's a problem doing this + */ + int getVolumeSerialNumber() const; + + /** Returns the number of bytes free on the drive that this file lives on. + + @returns the number of bytes free, or 0 if there's a problem finding this out + @see getVolumeTotalSize + */ + int64 getBytesFreeOnVolume() const; + + /** Returns the total size of the drive that contains this file. + + @returns the total number of bytes that the volume can hold + @see getBytesFreeOnVolume + */ + int64 getVolumeTotalSize() const; + + /** Returns true if this file is on a CD or DVD drive. */ + bool isOnCDRomDrive() const; + + /** Returns true if this file is on a hard disk. + + This will fail if it's a network drive, but will still be true for + removable hard-disks. + */ + bool isOnHardDisk() const; + + /** Returns true if this file is on a removable disk drive. + + This might be a usb-drive, a CD-rom, or maybe a network drive. + */ + bool isOnRemovableDrive() const; + + //============================================================================== + /** Launches the file as a process. + + - if the file is executable, this will run it. + + - if it's a document of some kind, it will launch the document with its + default viewer application. + + - if it's a folder, it will be opened in Explorer, Finder, or equivalent. + + @see revealToUser + */ + bool startAsProcess (const String& parameters = String()) const; + + /** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. + @see startAsProcess + */ + void revealToUser() const; + + //============================================================================== + /** A set of types of location that can be passed to the getSpecialLocation() method. + */ + enum SpecialLocationType + { + /** The user's home folder. This is the same as using File ("~"). */ + userHomeDirectory, + + /** The user's default documents folder. On Windows, this might be the user's + "My Documents" folder. On the Mac it'll be their "Documents" folder. Linux + doesn't tend to have one of these, so it might just return their home folder. + */ + userDocumentsDirectory, + + /** The folder that contains the user's desktop objects. */ + userDesktopDirectory, + + /** The most likely place where a user might store their music files. */ + userMusicDirectory, + + /** The most likely place where a user might store their movie files. */ + userMoviesDirectory, + + /** The most likely place where a user might store their picture files. */ + userPicturesDirectory, + + /** The folder in which applications store their persistent user-specific settings. + On Windows, this might be "\Documents and Settings\username\Application Data". + On the Mac, it might be "~/Library". If you're going to store your settings in here, + always create your own sub-folder to put them in, to avoid making a mess. + */ + userApplicationDataDirectory, + + /** An equivalent of the userApplicationDataDirectory folder that is shared by all users + of the computer, rather than just the current user. + + On the Mac it'll be "/Library", on Windows, it could be something like + "\Documents and Settings\All Users\Application Data". + + Depending on the setup, this folder may be read-only. + */ + commonApplicationDataDirectory, + + /** A place to put documents which are shared by all users of the machine. + On Windows this may be somewhere like "C:\Users\Public\Documents", on OSX it + will be something like "/Users/Shared". Other OSes may have no such concept + though, so be careful. + */ + commonDocumentsDirectory, + + /** The folder that should be used for temporary files. + Always delete them when you're finished, to keep the user's computer tidy! + */ + tempDirectory, + + /** Returns this application's executable file. + + If running as a plug-in or DLL, this will (where possible) be the DLL rather than the + host app. + + On the mac this will return the unix binary, not the package folder - see + currentApplicationFile for that. + + See also invokedExecutableFile, which is similar, but if the exe was launched from a + file link, invokedExecutableFile will return the name of the link. + */ + currentExecutableFile, + + /** Returns this application's location. + + If running as a plug-in or DLL, this will (where possible) be the DLL rather than the + host app. + + On the mac this will return the package folder (if it's in one), not the unix binary + that's inside it - compare with currentExecutableFile. + */ + currentApplicationFile, + + /** Returns the file that was invoked to launch this executable. + This may differ from currentExecutableFile if the app was started from e.g. a link - this + will return the name of the link that was used, whereas currentExecutableFile will return + the actual location of the target executable. + */ + invokedExecutableFile, + + /** In a plugin, this will return the path of the host executable. */ + hostApplicationPath, + + #if JUCE_WINDOWS + /** On a Windows machine, returns the location of the Windows/System32 folder. */ + windowsSystemDirectory, + #endif + + /** The directory in which applications normally get installed. + So on windows, this would be something like "c:\program files", on the + Mac "/Applications", or "/usr" on linux. + */ + globalApplicationsDirectory + }; + + /** Finds the location of a special type of file or directory, such as a home folder or + documents folder. + + @see SpecialLocationType + */ + static File JUCE_CALLTYPE getSpecialLocation (const SpecialLocationType type); + + //============================================================================== + /** Returns a temporary file in the system's temp directory. + This will try to return the name of a non-existent temp file. + To get the temp folder, you can use getSpecialLocation (File::tempDirectory). + */ + static File createTempFile (StringRef fileNameEnding); + + + //============================================================================== + /** Returns the current working directory. + @see setAsCurrentWorkingDirectory + */ + static File getCurrentWorkingDirectory(); + + /** Sets the current working directory to be this file. + + For this to work the file must point to a valid directory. + + @returns true if the current directory has been changed. + @see getCurrentWorkingDirectory + */ + bool setAsCurrentWorkingDirectory() const; + + //============================================================================== + /** The system-specific file separator character. + On Windows, this will be '\', on Mac/Linux, it'll be '/' + */ + static const juce_wchar separator; + + /** The system-specific file separator character, as a string. + On Windows, this will be '\', on Mac/Linux, it'll be '/' + */ + static const String separatorString; + + //============================================================================== + /** Returns a version of a filename with any illegal characters removed. + + This will return a copy of the given string after removing characters + that are not allowed in a legal filename, and possibly shortening the + string if it's too long. + + Because this will remove slashes, don't use it on an absolute pathname - use + createLegalPathName() for that. + + @see createLegalPathName + */ + static String createLegalFileName (const String& fileNameToFix); + + /** Returns a version of a path with any illegal characters removed. + + Similar to createLegalFileName(), but this won't remove slashes, so can + be used on a complete pathname. + + @see createLegalFileName + */ + static String createLegalPathName (const String& pathNameToFix); + + /** Indicates whether filenames are case-sensitive on the current operating system. */ + static bool areFileNamesCaseSensitive(); + + /** Returns true if the string seems to be a fully-specified absolute path. */ + static bool isAbsolutePath (StringRef path); + + /** Creates a file that simply contains this string, without doing the sanity-checking + that the normal constructors do. + + Best to avoid this unless you really know what you're doing. + */ + static File createFileWithoutCheckingPath (const String& absolutePath) noexcept; + + /** Adds a separator character to the end of a path if it doesn't already have one. */ + static String addTrailingSeparator (const String& path); + + #if JUCE_MAC || JUCE_IOS || DOXYGEN + //============================================================================== + /** OSX ONLY - Finds the OSType of a file from the its resources. */ + OSType getMacOSType() const; + + /** OSX ONLY - Returns true if this file is actually a bundle. */ + bool isBundle() const; + #endif + + #if JUCE_MAC || DOXYGEN + /** OSX ONLY - Adds this file to the OSX dock */ + void addToDock() const; + #endif + + #if JUCE_WINDOWS + /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ + bool createLink (const String& description, const File& linkFileToCreate) const; + #endif + +private: + //============================================================================== + String fullPath; + + static String parseAbsolutePath (const String&); + String getPathUpToLastSlash() const; + + Result createDirectoryInternal (const String&) const; + bool copyInternal (const File&) const; + bool moveInternal (const File&) const; + bool setFileTimesInternal (int64 m, int64 a, int64 c) const; + void getFileTimesInternal (int64& m, int64& a, int64& c) const; + bool setFileReadOnlyInternal (bool) const; + bool setFileExecutableInternal (bool) const; +}; + +#endif // JUCE_FILE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.cpp b/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.cpp new file mode 100644 index 0000000..bd0bdf8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.cpp @@ -0,0 +1,41 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +FileFilter::FileFilter (const String& filterDescription) + : description (filterDescription) +{ +} + +FileFilter::~FileFilter() +{ +} + +const String& FileFilter::getDescription() const noexcept +{ + return description; +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.h b/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.h new file mode 100644 index 0000000..4c02415 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileFilter.h @@ -0,0 +1,77 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEFILTER_H_INCLUDED +#define JUCE_FILEFILTER_H_INCLUDED + + +//============================================================================== +/** + Interface for deciding which files are suitable for something. + + For example, this is used by DirectoryContentsList to select which files + go into the list. + + @see WildcardFileFilter, DirectoryContentsList, FileListComponent, FileBrowserComponent +*/ +class JUCE_API FileFilter +{ +public: + //============================================================================== + /** Creates a filter with the given description. + + The description can be returned later with the getDescription() method. + */ + FileFilter (const String& filterDescription); + + /** Destructor. */ + virtual ~FileFilter(); + + //============================================================================== + /** Returns the description that the filter was created with. */ + const String& getDescription() const noexcept; + + //============================================================================== + /** Should return true if this file is suitable for inclusion in whatever context + the object is being used. + */ + virtual bool isFileSuitable (const File& file) const = 0; + + /** Should return true if this directory is suitable for inclusion in whatever context + the object is being used. + */ + virtual bool isDirectorySuitable (const File& file) const = 0; + + +protected: + //============================================================================== + String description; +}; + + +#endif // JUCE_FILEFILTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.cpp b/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.cpp new file mode 100644 index 0000000..801ccfa --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.cpp @@ -0,0 +1,84 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +int64 juce_fileSetPosition (void* handle, int64 pos); + + +//============================================================================== +FileInputStream::FileInputStream (const File& f) + : file (f), + fileHandle (nullptr), + currentPosition (0), + status (Result::ok()) +{ + openHandle(); +} + +int64 FileInputStream::getTotalLength() +{ + // You should always check that a stream opened successfully before using it! + jassert (openedOk()); + + return file.getSize(); +} + +int FileInputStream::read (void* buffer, int bytesToRead) +{ + // You should always check that a stream opened successfully before using it! + jassert (openedOk()); + + // The buffer should never be null, and a negative size is probably a + // sign that something is broken! + jassert (buffer != nullptr && bytesToRead >= 0); + + const size_t num = readInternal (buffer, (size_t) bytesToRead); + currentPosition += num; + + return (int) num; +} + +bool FileInputStream::isExhausted() +{ + return currentPosition >= getTotalLength(); +} + +int64 FileInputStream::getPosition() +{ + return currentPosition; +} + +bool FileInputStream::setPosition (int64 pos) +{ + // You should always check that a stream opened successfully before using it! + jassert (openedOk()); + + if (pos != currentPosition) + currentPosition = juce_fileSetPosition (fileHandle, pos); + + return currentPosition == pos; +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.h b/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.h new file mode 100644 index 0000000..35963ba --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileInputStream.h @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEINPUTSTREAM_H_INCLUDED +#define JUCE_FILEINPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + An input stream that reads from a local file. + + @see InputStream, FileOutputStream, File::createInputStream +*/ +class JUCE_API FileInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a FileInputStream to read from the given file. + + After creating a FileInputStream, you should use openedOk() or failedToOpen() + to make sure that it's OK before trying to read from it! If it failed, you + can call getStatus() to get more error information. + */ + explicit FileInputStream (const File& fileToRead); + + /** Destructor. */ + ~FileInputStream(); + + //============================================================================== + /** Returns the file that this stream is reading from. */ + const File& getFile() const noexcept { return file; } + + /** Returns the status of the file stream. + The result will be ok if the file opened successfully. If an error occurs while + opening or reading from the file, this will contain an error message. + */ + const Result& getStatus() const noexcept { return status; } + + /** Returns true if the stream couldn't be opened for some reason. + @see getResult() + */ + bool failedToOpen() const noexcept { return status.failed(); } + + /** Returns true if the stream opened without problems. + @see getResult() + */ + bool openedOk() const noexcept { return status.wasOk(); } + + + //============================================================================== + int64 getTotalLength() override; + int read (void*, int) override; + bool isExhausted() override; + int64 getPosition() override; + bool setPosition (int64) override; + +private: + //============================================================================== + const File file; + void* fileHandle; + int64 currentPosition; + Result status; + + void openHandle(); + size_t readInternal (void*, size_t); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) +}; + + +#endif // JUCE_FILEINPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.cpp b/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.cpp new file mode 100644 index 0000000..961a8d1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.cpp @@ -0,0 +1,134 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +int64 juce_fileSetPosition (void* handle, int64 pos); + +//============================================================================== +FileOutputStream::FileOutputStream (const File& f, const size_t bufferSizeToUse) + : file (f), + fileHandle (nullptr), + status (Result::ok()), + currentPosition (0), + bufferSize (bufferSizeToUse), + bytesInBuffer (0), + buffer (jmax (bufferSizeToUse, (size_t) 16)) +{ + openHandle(); +} + +FileOutputStream::~FileOutputStream() +{ + flushBuffer(); + closeHandle(); +} + +int64 FileOutputStream::getPosition() +{ + return currentPosition; +} + +bool FileOutputStream::setPosition (int64 newPosition) +{ + if (newPosition != currentPosition) + { + flushBuffer(); + currentPosition = juce_fileSetPosition (fileHandle, newPosition); + } + + return newPosition == currentPosition; +} + +bool FileOutputStream::flushBuffer() +{ + bool ok = true; + + if (bytesInBuffer > 0) + { + ok = (writeInternal (buffer, bytesInBuffer) == (ssize_t) bytesInBuffer); + bytesInBuffer = 0; + } + + return ok; +} + +void FileOutputStream::flush() +{ + flushBuffer(); + flushInternal(); +} + +bool FileOutputStream::write (const void* const src, const size_t numBytes) +{ + jassert (src != nullptr && ((ssize_t) numBytes) >= 0); + + if (bytesInBuffer + numBytes < bufferSize) + { + memcpy (buffer + bytesInBuffer, src, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + } + else + { + if (! flushBuffer()) + return false; + + if (numBytes < bufferSize) + { + memcpy (buffer + bytesInBuffer, src, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + } + else + { + const ssize_t bytesWritten = writeInternal (src, numBytes); + + if (bytesWritten < 0) + return false; + + currentPosition += bytesWritten; + return bytesWritten == (ssize_t) numBytes; + } + } + + return true; +} + +bool FileOutputStream::writeRepeatedByte (uint8 byte, size_t numBytes) +{ + jassert (((ssize_t) numBytes) >= 0); + + if (bytesInBuffer + numBytes < bufferSize) + { + memset (buffer + bytesInBuffer, byte, numBytes); + bytesInBuffer += numBytes; + currentPosition += numBytes; + return true; + } + + return OutputStream::writeRepeatedByte (byte, numBytes); +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.h b/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.h new file mode 100644 index 0000000..f80705f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileOutputStream.h @@ -0,0 +1,116 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEOUTPUTSTREAM_H_INCLUDED +#define JUCE_FILEOUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + An output stream that writes into a local file. + + @see OutputStream, FileInputStream, File::createOutputStream +*/ +class JUCE_API FileOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates a FileOutputStream. + + If the file doesn't exist, it will first be created. If the file can't be + created or opened, the failedToOpen() method will return + true. + + If the file already exists when opened, the stream's write-postion will + be set to the end of the file. To overwrite an existing file, + use File::deleteFile() before opening the stream, or use setPosition(0) + after it's opened (although this won't truncate the file). + + @see TemporaryFile + */ + FileOutputStream (const File& fileToWriteTo, + size_t bufferSizeToUse = 16384); + + /** Destructor. */ + ~FileOutputStream(); + + //============================================================================== + /** Returns the file that this stream is writing to. + */ + const File& getFile() const { return file; } + + /** Returns the status of the file stream. + The result will be ok if the file opened successfully. If an error occurs while + opening or writing to the file, this will contain an error message. + */ + const Result& getStatus() const noexcept { return status; } + + /** Returns true if the stream couldn't be opened for some reason. + @see getResult() + */ + bool failedToOpen() const noexcept { return status.failed(); } + + /** Returns true if the stream opened without problems. + @see getResult() + */ + bool openedOk() const noexcept { return status.wasOk(); } + + /** Attempts to truncate the file to the current write position. + To truncate a file to a specific size, first use setPosition() to seek to the + appropriate location, and then call this method. + */ + Result truncate(); + + //============================================================================== + void flush() override; + int64 getPosition() override; + bool setPosition (int64) override; + bool write (const void*, size_t) override; + bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) override; + + +private: + //============================================================================== + File file; + void* fileHandle; + Result status; + int64 currentPosition; + size_t bufferSize, bytesInBuffer; + HeapBlock buffer; + + void openHandle(); + void closeHandle(); + void flushInternal(); + bool flushBuffer(); + int64 setPositionInternal (int64); + ssize_t writeInternal (const void*, size_t); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileOutputStream) +}; + +#endif // JUCE_FILEOUTPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp new file mode 100644 index 0000000..ce0af7e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp @@ -0,0 +1,172 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +FileSearchPath::FileSearchPath() {} +FileSearchPath::~FileSearchPath() {} + +FileSearchPath::FileSearchPath (const String& path) +{ + init (path); +} + +FileSearchPath::FileSearchPath (const FileSearchPath& other) + : directories (other.directories) +{ +} + +FileSearchPath& FileSearchPath::operator= (const FileSearchPath& other) +{ + directories = other.directories; + return *this; +} + +FileSearchPath& FileSearchPath::operator= (const String& path) +{ + init (path); + return *this; +} + +void FileSearchPath::init (const String& path) +{ + directories.clear(); + directories.addTokens (path, ";", "\""); + directories.trim(); + directories.removeEmptyStrings(); + + for (int i = directories.size(); --i >= 0;) + directories.set (i, directories[i].unquoted()); +} + +int FileSearchPath::getNumPaths() const +{ + return directories.size(); +} + +File FileSearchPath::operator[] (const int index) const +{ + return File (directories [index]); +} + +String FileSearchPath::toString() const +{ + StringArray directories2 (directories); + for (int i = directories2.size(); --i >= 0;) + if (directories2[i].containsChar (';')) + directories2.set (i, directories2[i].quoted()); + + return directories2.joinIntoString (";"); +} + +void FileSearchPath::add (const File& dir, const int insertIndex) +{ + directories.insert (insertIndex, dir.getFullPathName()); +} + +void FileSearchPath::addIfNotAlreadyThere (const File& dir) +{ + for (int i = 0; i < directories.size(); ++i) + if (File (directories[i]) == dir) + return; + + add (dir); +} + +void FileSearchPath::remove (const int index) +{ + directories.remove (index); +} + +void FileSearchPath::addPath (const FileSearchPath& other) +{ + for (int i = 0; i < other.getNumPaths(); ++i) + addIfNotAlreadyThere (other[i]); +} + +void FileSearchPath::removeRedundantPaths() +{ + for (int i = directories.size(); --i >= 0;) + { + const File d1 (directories[i]); + + for (int j = directories.size(); --j >= 0;) + { + const File d2 (directories[j]); + + if ((i != j) && (d1.isAChildOf (d2) || d1 == d2)) + { + directories.remove (i); + break; + } + } + } +} + +void FileSearchPath::removeNonExistentPaths() +{ + for (int i = directories.size(); --i >= 0;) + if (! File (directories[i]).isDirectory()) + directories.remove (i); +} + +int FileSearchPath::findChildFiles (Array& results, + const int whatToLookFor, + const bool searchRecursively, + const String& wildCardPattern) const +{ + int total = 0; + + for (int i = 0; i < directories.size(); ++i) + total += operator[] (i).findChildFiles (results, + whatToLookFor, + searchRecursively, + wildCardPattern); + + return total; +} + +bool FileSearchPath::isFileInPath (const File& fileToCheck, + const bool checkRecursively) const +{ + for (int i = directories.size(); --i >= 0;) + { + const File d (directories[i]); + + if (checkRecursively) + { + if (fileToCheck.isAChildOf (d)) + return true; + } + else + { + if (fileToCheck.getParentDirectory() == d) + return true; + } + } + + return false; +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h new file mode 100644 index 0000000..51baf2e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h @@ -0,0 +1,165 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILESEARCHPATH_H_INCLUDED +#define JUCE_FILESEARCHPATH_H_INCLUDED + + +//============================================================================== +/** + Represents a set of folders that make up a search path. + + @see File +*/ +class JUCE_API FileSearchPath +{ +public: + //============================================================================== + /** Creates an empty search path. */ + FileSearchPath(); + + /** Creates a search path from a string of pathnames. + + The path can be semicolon- or comma-separated, e.g. + "/foo/bar;/foo/moose;/fish/moose" + + The separate folders are tokenised and added to the search path. + */ + FileSearchPath (const String& path); + + /** Creates a copy of another search path. */ + FileSearchPath (const FileSearchPath&); + + /** Copies another search path. */ + FileSearchPath& operator= (const FileSearchPath&); + + /** Destructor. */ + ~FileSearchPath(); + + /** Uses a string containing a list of pathnames to re-initialise this list. + + This search path is cleared and the semicolon- or comma-separated folders + in this string are added instead. e.g. "/foo/bar;/foo/moose;/fish/moose" + */ + FileSearchPath& operator= (const String& path); + + //============================================================================== + /** Returns the number of folders in this search path. + @see operator[] + */ + int getNumPaths() const; + + /** Returns one of the folders in this search path. + The file returned isn't guaranteed to actually be a valid directory. + @see getNumPaths + */ + File operator[] (int index) const; + + /** Returns the search path as a semicolon-separated list of directories. */ + String toString() const; + + //============================================================================== + /** Adds a new directory to the search path. + + The new directory is added to the end of the list if the insertIndex parameter is + less than zero, otherwise it is inserted at the given index. + */ + void add (const File& directoryToAdd, + int insertIndex = -1); + + /** Adds a new directory to the search path if it's not already in there. */ + void addIfNotAlreadyThere (const File& directoryToAdd); + + /** Removes a directory from the search path. */ + void remove (int indexToRemove); + + /** Merges another search path into this one. + This will remove any duplicate directories. + */ + void addPath (const FileSearchPath&); + + /** Removes any directories that are actually subdirectories of one of the other directories in the search path. + + If the search is intended to be recursive, there's no point having nested folders in the search + path, because they'll just get searched twice and you'll get duplicate results. + + e.g. if the path is "c:\abc\de;c:\abc", this method will simplify it to "c:\abc" + */ + void removeRedundantPaths(); + + /** Removes any directories that don't actually exist. */ + void removeNonExistentPaths(); + + //============================================================================== + /** Searches the path for a wildcard. + + This will search all the directories in the search path in order, adding any + matching files to the results array. + + @param results an array to append the results to + @param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying whether to + return files, directories, or both. + @param searchRecursively whether to recursively search the subdirectories too + @param wildCardPattern a pattern to match against the filenames + @returns the number of files added to the array + @see File::findChildFiles + */ + int findChildFiles (Array& results, + int whatToLookFor, + bool searchRecursively, + const String& wildCardPattern = "*") const; + + //============================================================================== + /** Finds out whether a file is inside one of the path's directories. + + This will return true if the specified file is a child of one of the + directories specified by this path. Note that this doesn't actually do any + searching or check that the files exist - it just looks at the pathnames + to work out whether the file would be inside a directory. + + @param fileToCheck the file to look for + @param checkRecursively if true, then this will return true if the file is inside a + subfolder of one of the path's directories (at any depth). If false + it will only return true if the file is actually a direct child + of one of the directories. + @see File::isAChildOf + + */ + bool isFileInPath (const File& fileToCheck, + bool checkRecursively) const; + +private: + //============================================================================== + StringArray directories; + + void init (const String&); + + JUCE_LEAK_DETECTOR (FileSearchPath) +}; + +#endif // JUCE_FILESEARCHPATH_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_MemoryMappedFile.h b/JuceLibraryCode/modules/juce_core/files/juce_MemoryMappedFile.h new file mode 100644 index 0000000..8a79185 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_MemoryMappedFile.h @@ -0,0 +1,115 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYMAPPEDFILE_H_INCLUDED +#define JUCE_MEMORYMAPPEDFILE_H_INCLUDED + + +//============================================================================== +/** + Maps a file into virtual memory for easy reading and/or writing. +*/ +class JUCE_API MemoryMappedFile +{ +public: + /** The read/write flags used when opening a memory mapped file. */ + enum AccessMode + { + readOnly, /**< Indicates that the memory can only be read. */ + readWrite /**< Indicates that the memory can be read and written to - changes that are + made will be flushed back to disk at the whim of the OS. */ + }; + + /** Opens a file and maps it to an area of virtual memory. + + The file should already exist, and should already be the size that you want to work with + when you call this. If the file is resized after being opened, the behaviour is undefined. + + If the file exists and the operation succeeds, the getData() and getSize() methods will + return the location and size of the data that can be read or written. Note that the entire + file is not read into memory immediately - the OS simply creates a virtual mapping, which + will lazily pull the data into memory when blocks are accessed. + + If the file can't be opened for some reason, the getData() method will return a null pointer. + */ + MemoryMappedFile (const File& file, AccessMode mode); + + /** Opens a section of a file and maps it to an area of virtual memory. + + The file should already exist, and should already be the size that you want to work with + when you call this. If the file is resized after being opened, the behaviour is undefined. + + If the file exists and the operation succeeds, the getData() and getSize() methods will + return the location and size of the data that can be read or written. Note that the entire + file is not read into memory immediately - the OS simply creates a virtual mapping, which + will lazily pull the data into memory when blocks are accessed. + + If the file can't be opened for some reason, the getData() method will return a null pointer. + + NOTE: the start of the actual range used may be rounded-down to a multiple of the OS's page-size, + so do not assume that the mapped memory will begin at exactly the position you requested - always + use getRange() to check the actual range that is being used. + */ + MemoryMappedFile (const File& file, + const Range& fileRange, + AccessMode mode); + + /** Destructor. */ + ~MemoryMappedFile(); + + /** Returns the address at which this file has been mapped, or a null pointer if + the file couldn't be successfully mapped. + */ + void* getData() const noexcept { return address; } + + /** Returns the number of bytes of data that are available for reading or writing. + This will normally be the size of the file. + */ + size_t getSize() const noexcept { return (size_t) range.getLength(); } + + /** Returns the section of the file at which the mapped memory represents. */ + Range getRange() const noexcept { return range; } + +private: + //============================================================================== + void* address; + Range range; + + #if JUCE_WINDOWS + void* fileHandle; + #else + int fileHandle; + #endif + + void openInternal (const File&, AccessMode); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedFile) +}; + + +#endif // JUCE_MEMORYMAPPEDFILE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp new file mode 100644 index 0000000..50475d5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.cpp @@ -0,0 +1,117 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +static File createTempFile (const File& parentDirectory, String name, + const String& suffix, const int optionFlags) +{ + if ((optionFlags & TemporaryFile::useHiddenFile) != 0) + name = "." + name; + + return parentDirectory.getNonexistentChildFile (name, suffix, (optionFlags & TemporaryFile::putNumbersInBrackets) != 0); +} + +TemporaryFile::TemporaryFile (const String& suffix, const int optionFlags) + : temporaryFile (createTempFile (File::getSpecialLocation (File::tempDirectory), + "temp_" + String::toHexString (Random::getSystemRandom().nextInt()), + suffix, optionFlags)) +{ +} + +TemporaryFile::TemporaryFile (const File& target, const int optionFlags) + : temporaryFile (createTempFile (target.getParentDirectory(), + target.getFileNameWithoutExtension() + + "_temp" + String::toHexString (Random::getSystemRandom().nextInt()), + target.getFileExtension(), optionFlags)), + targetFile (target) +{ + // If you use this constructor, you need to give it a valid target file! + jassert (targetFile != File()); +} + +TemporaryFile::TemporaryFile (const File& target, const File& temporary) + : temporaryFile (temporary), targetFile (target) +{ +} + +TemporaryFile::~TemporaryFile() +{ + if (! deleteTemporaryFile()) + { + /* Failed to delete our temporary file! The most likely reason for this would be + that you've not closed an output stream that was being used to write to file. + + If you find that something beyond your control is changing permissions on + your temporary files and preventing them from being deleted, you may want to + call TemporaryFile::deleteTemporaryFile() to detect those error cases and + handle them appropriately. + */ + jassertfalse; + } +} + +//============================================================================== +bool TemporaryFile::overwriteTargetFileWithTemporary() const +{ + // This method only works if you created this object with the constructor + // that takes a target file! + jassert (targetFile != File()); + + if (temporaryFile.exists()) + { + // Have a few attempts at overwriting the file before giving up.. + for (int i = 5; --i >= 0;) + { + if (temporaryFile.moveFileTo (targetFile)) + return true; + + Thread::sleep (100); + } + } + else + { + // There's no temporary file to use. If your write failed, you should + // probably check, and not bother calling this method. + jassertfalse; + } + + return false; +} + +bool TemporaryFile::deleteTemporaryFile() const +{ + // Have a few attempts at deleting the file before giving up.. + for (int i = 5; --i >= 0;) + { + if (temporaryFile.deleteFile()) + return true; + + Thread::sleep (50); + } + + return false; +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h new file mode 100644 index 0000000..04561a7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_TemporaryFile.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TEMPORARYFILE_H_INCLUDED +#define JUCE_TEMPORARYFILE_H_INCLUDED + + +//============================================================================== +/** + Manages a temporary file, which will be deleted when this object is deleted. + + This object is intended to be used as a stack based object, using its scope + to make sure the temporary file isn't left lying around. + + For example: + + @code + { + File myTargetFile ("~/myfile.txt"); + + // this will choose a file called something like "~/myfile_temp239348.txt" + // which definitely doesn't exist at the time the constructor is called. + TemporaryFile temp (myTargetFile); + + // create a stream to the temporary file, and write some data to it... + ScopedPointer out (temp.getFile().createOutputStream()); + + if (out != nullptr) + { + out->write ( ...etc ) + out = nullptr; // (deletes the stream) + + // ..now we've finished writing, this will rename the temp file to + // make it replace the target file we specified above. + bool succeeded = temp.overwriteTargetFileWithTemporary(); + } + + // ..and even if something went wrong and our overwrite failed, + // as the TemporaryFile object goes out of scope here, it'll make sure + // that the temp file gets deleted. + } + @endcode + + @see File, FileOutputStream +*/ +class JUCE_API TemporaryFile +{ +public: + //============================================================================== + enum OptionFlags + { + useHiddenFile = 1, /**< Indicates that the temporary file should be hidden - + i.e. its name should start with a dot. */ + putNumbersInBrackets = 2 /**< Indicates that when numbers are appended to make sure + the file is unique, they should go in brackets rather + than just being appended (see File::getNonexistentSibling() )*/ + }; + + //============================================================================== + /** Creates a randomly-named temporary file in the default temp directory. + + @param suffix a file suffix to use for the file + @param optionFlags a combination of the values listed in the OptionFlags enum + The file will not be created until you write to it. And remember that when + this object is deleted, the file will also be deleted! + */ + TemporaryFile (const String& suffix = String(), + int optionFlags = 0); + + /** Creates a temporary file in the same directory as a specified file. + + This is useful if you have a file that you want to overwrite, but don't + want to harm the original file if the write operation fails. You can + use this to create a temporary file next to the target file, then + write to the temporary file, and finally use overwriteTargetFileWithTemporary() + to replace the target file with the one you've just written. + + This class won't create any files until you actually write to them. And remember + that when this object is deleted, the temporary file will also be deleted! + + @param targetFile the file that you intend to overwrite - the temporary + file will be created in the same directory as this + @param optionFlags a combination of the values listed in the OptionFlags enum + */ + TemporaryFile (const File& targetFile, + int optionFlags = 0); + + /** Creates a temporary file using an explicit filename. + The other constructors are a better choice than this one, unless for some reason + you need to explicitly specify the temporary file you want to use. + + @param targetFile the file that you intend to overwrite + @param temporaryFile the temporary file to be used + */ + TemporaryFile (const File& targetFile, + const File& temporaryFile); + + /** Destructor. + + When this object is deleted it will make sure that its temporary file is + also deleted! If the operation fails, it'll throw an assertion in debug + mode. + */ + ~TemporaryFile(); + + //============================================================================== + /** Returns the temporary file. */ + const File& getFile() const noexcept { return temporaryFile; } + + /** Returns the target file that was specified in the constructor. */ + const File& getTargetFile() const noexcept { return targetFile; } + + /** Tries to move the temporary file to overwrite the target file that was + specified in the constructor. + + If you used the constructor that specified a target file, this will attempt + to replace that file with the temporary one. + + Before calling this, make sure: + - that you've actually written to the temporary file + - that you've closed any open streams that you were using to write to it + - and that you don't have any streams open to the target file, which would + prevent it being overwritten + + If the file move succeeds, this returns false, and the temporary file will + have disappeared. If it fails, the temporary file will probably still exist, + but will be deleted when this object is destroyed. + */ + bool overwriteTargetFileWithTemporary() const; + + /** Attempts to delete the temporary file, if it exists. + @returns true if the file is successfully deleted (or if it didn't exist). + */ + bool deleteTemporaryFile() const; + + +private: + //============================================================================== + const File temporaryFile, targetFile; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryFile) +}; + +#endif // JUCE_TEMPORARYFILE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.cpp b/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.cpp new file mode 100644 index 0000000..f18a2eb --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.cpp @@ -0,0 +1,76 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +static void parseWildcard (const String& pattern, StringArray& result) +{ + result.addTokens (pattern.toLowerCase(), ";,", "\"'"); + + result.trim(); + result.removeEmptyStrings(); + + // special case for *.*, because people use it to mean "any file", but it + // would actually ignore files with no extension. + for (int i = result.size(); --i >= 0;) + if (result[i] == "*.*") + result.set (i, "*"); +} + +static bool matchWildcard (const File& file, const StringArray& wildcards) +{ + const String filename (file.getFileName()); + + for (int i = wildcards.size(); --i >= 0;) + if (filename.matchesWildcard (wildcards[i], true)) + return true; + + return false; +} + +WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, + const String& desc) + : FileFilter (desc.isEmpty() ? fileWildcardPatterns + : (desc + " (" + fileWildcardPatterns + ")")) +{ + parseWildcard (fileWildcardPatterns, fileWildcards); + parseWildcard (directoryWildcardPatterns, directoryWildcards); +} + +WildcardFileFilter::~WildcardFileFilter() +{ +} + +bool WildcardFileFilter::isFileSuitable (const File& file) const +{ + return matchWildcard (file, fileWildcards); +} + +bool WildcardFileFilter::isDirectorySuitable (const File& file) const +{ + return matchWildcard (file, directoryWildcards); +} diff --git a/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.h b/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.h new file mode 100644 index 0000000..fb398ce --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/files/juce_WildcardFileFilter.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WILDCARDFILEFILTER_H_INCLUDED +#define JUCE_WILDCARDFILEFILTER_H_INCLUDED + + +//============================================================================== +/** + A type of FileFilter that works by wildcard pattern matching. + + This filter only allows files that match one of the specified patterns, but + allows all directories through. + + @see FileFilter, DirectoryContentsList, FileListComponent, FileBrowserComponent +*/ +class JUCE_API WildcardFileFilter : public FileFilter +{ +public: + //============================================================================== + /** + Creates a wildcard filter for one or more patterns. + + The wildcardPatterns parameter is a comma or semicolon-delimited set of + patterns, e.g. "*.wav;*.aiff" would look for files ending in either .wav + or .aiff. + + Passing an empty string as a pattern will fail to match anything, so by leaving + either the file or directory pattern parameter empty means you can control + whether files or directories are found. + + The description is a name to show the user in a list of possible patterns, so + for the wav/aiff example, your description might be "audio files". + */ + WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, + const String& description); + + /** Destructor. */ + ~WildcardFileFilter(); + + //============================================================================== + /** Returns true if the filename matches one of the patterns specified. */ + bool isFileSuitable (const File& file) const; + + /** This always returns true. */ + bool isDirectorySuitable (const File& file) const; + +private: + //============================================================================== + StringArray fileWildcards, directoryWildcards; + + JUCE_LEAK_DETECTOR (WildcardFileFilter) +}; + + +#endif // JUCE_WILDCARDFILEFILTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp new file mode 100644 index 0000000..5a0f7f7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.cpp @@ -0,0 +1,643 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class JSONParser +{ +public: + static Result parseObjectOrArray (String::CharPointerType t, var& result) + { + t = t.findEndOfWhitespace(); + + switch (t.getAndAdvance()) + { + case 0: result = var(); return Result::ok(); + case '{': return parseObject (t, result); + case '[': return parseArray (t, result); + } + + return createFail ("Expected '{' or '['", &t); + } + + static Result parseString (const juce_wchar quoteChar, String::CharPointerType& t, var& result) + { + MemoryOutputStream buffer (256); + + for (;;) + { + juce_wchar c = t.getAndAdvance(); + + if (c == quoteChar) + break; + + if (c == '\\') + { + c = t.getAndAdvance(); + + switch (c) + { + case '"': + case '\'': + case '\\': + case '/': break; + + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + + case 'u': + { + c = 0; + + for (int i = 4; --i >= 0;) + { + const int digitValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); + if (digitValue < 0) + return createFail ("Syntax error in unicode escape sequence"); + + c = (juce_wchar) ((c << 4) + digitValue); + } + + break; + } + } + } + + if (c == 0) + return createFail ("Unexpected end-of-input in string constant"); + + buffer.appendUTF8Char (c); + } + + result = buffer.toUTF8(); + return Result::ok(); + } + + static Result parseAny (String::CharPointerType& t, var& result) + { + t = t.findEndOfWhitespace(); + String::CharPointerType t2 (t); + + switch (t2.getAndAdvance()) + { + case '{': t = t2; return parseObject (t, result); + case '[': t = t2; return parseArray (t, result); + case '"': t = t2; return parseString ('"', t, result); + case '\'': t = t2; return parseString ('\'', t, result); + + case '-': + t2 = t2.findEndOfWhitespace(); + if (! CharacterFunctions::isDigit (*t2)) + break; + + t = t2; + return parseNumber (t, result, true); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parseNumber (t, result, false); + + case 't': // "true" + if (t2.getAndAdvance() == 'r' && t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'e') + { + t = t2; + result = var (true); + return Result::ok(); + } + break; + + case 'f': // "false" + if (t2.getAndAdvance() == 'a' && t2.getAndAdvance() == 'l' + && t2.getAndAdvance() == 's' && t2.getAndAdvance() == 'e') + { + t = t2; + result = var (false); + return Result::ok(); + } + break; + + case 'n': // "null" + if (t2.getAndAdvance() == 'u' && t2.getAndAdvance() == 'l' && t2.getAndAdvance() == 'l') + { + t = t2; + result = var(); + return Result::ok(); + } + break; + + default: + break; + } + + return createFail ("Syntax error", &t); + } + +private: + static Result createFail (const char* const message, const String::CharPointerType* location = nullptr) + { + String m (message); + if (location != nullptr) + m << ": \"" << String (*location, 20) << '"'; + + return Result::fail (m); + } + + static Result parseNumber (String::CharPointerType& t, var& result, const bool isNegative) + { + String::CharPointerType oldT (t); + + int64 intValue = t.getAndAdvance() - '0'; + jassert (intValue >= 0 && intValue < 10); + + for (;;) + { + String::CharPointerType previousChar (t); + const juce_wchar c = t.getAndAdvance(); + const int digit = ((int) c) - '0'; + + if (isPositiveAndBelow (digit, 10)) + { + intValue = intValue * 10 + digit; + continue; + } + + if (c == 'e' || c == 'E' || c == '.') + { + t = oldT; + const double asDouble = CharacterFunctions::readDoubleValue (t); + result = isNegative ? -asDouble : asDouble; + return Result::ok(); + } + + if (CharacterFunctions::isWhitespace (c) + || c == ',' || c == '}' || c == ']' || c == 0) + { + t = previousChar; + break; + } + + return createFail ("Syntax error in number", &oldT); + } + + const int64 correctedValue = isNegative ? -intValue : intValue; + + if ((intValue >> 31) != 0) + result = correctedValue; + else + result = (int) correctedValue; + + return Result::ok(); + } + + static Result parseObject (String::CharPointerType& t, var& result) + { + DynamicObject* const resultObject = new DynamicObject(); + result = resultObject; + NamedValueSet& resultProperties = resultObject->getProperties(); + + for (;;) + { + t = t.findEndOfWhitespace(); + + String::CharPointerType oldT (t); + const juce_wchar c = t.getAndAdvance(); + + if (c == '}') + break; + + if (c == 0) + return createFail ("Unexpected end-of-input in object declaration"); + + if (c == '"') + { + var propertyNameVar; + Result r (parseString ('"', t, propertyNameVar)); + + if (r.failed()) + return r; + + const Identifier propertyName (propertyNameVar.toString()); + + if (propertyName.isValid()) + { + t = t.findEndOfWhitespace(); + oldT = t; + + const juce_wchar c2 = t.getAndAdvance(); + if (c2 != ':') + return createFail ("Expected ':', but found", &oldT); + + resultProperties.set (propertyName, var()); + var* propertyValue = resultProperties.getVarPointer (propertyName); + + Result r2 (parseAny (t, *propertyValue)); + + if (r2.failed()) + return r2; + + t = t.findEndOfWhitespace(); + oldT = t; + + const juce_wchar nextChar = t.getAndAdvance(); + + if (nextChar == ',') + continue; + + if (nextChar == '}') + break; + } + } + + return createFail ("Expected object member declaration, but found", &oldT); + } + + return Result::ok(); + } + + static Result parseArray (String::CharPointerType& t, var& result) + { + result = var (Array()); + Array* const destArray = result.getArray(); + + for (;;) + { + t = t.findEndOfWhitespace(); + + String::CharPointerType oldT (t); + const juce_wchar c = t.getAndAdvance(); + + if (c == ']') + break; + + if (c == 0) + return createFail ("Unexpected end-of-input in array declaration"); + + t = oldT; + destArray->add (var()); + Result r (parseAny (t, destArray->getReference (destArray->size() - 1))); + + if (r.failed()) + return r; + + t = t.findEndOfWhitespace(); + oldT = t; + + const juce_wchar nextChar = t.getAndAdvance(); + + if (nextChar == ',') + continue; + + if (nextChar == ']') + break; + + return createFail ("Expected object array item, but found", &oldT); + } + + return Result::ok(); + } +}; + +//============================================================================== +class JSONFormatter +{ +public: + static void write (OutputStream& out, const var& v, + const int indentLevel, const bool allOnOneLine) + { + if (v.isString()) + { + out << '"'; + writeString (out, v.toString().getCharPointer()); + out << '"'; + } + else if (v.isVoid()) + { + out << "null"; + } + else if (v.isUndefined()) + { + out << "undefined"; + } + else if (v.isBool()) + { + out << (static_cast (v) ? "true" : "false"); + } + else if (v.isArray()) + { + writeArray (out, *v.getArray(), indentLevel, allOnOneLine); + } + else if (v.isObject()) + { + if (DynamicObject* object = v.getDynamicObject()) + object->writeAsJSON (out, indentLevel, allOnOneLine); + else + jassertfalse; // Only DynamicObjects can be converted to JSON! + } + else + { + // Can't convert these other types of object to JSON! + jassert (! (v.isMethod() || v.isBinaryData())); + + out << v.toString(); + } + } + + static void writeEscapedChar (OutputStream& out, const unsigned short value) + { + out << "\\u" << String::toHexString ((int) value).paddedLeft ('0', 4); + } + + static void writeString (OutputStream& out, String::CharPointerType t) + { + for (;;) + { + const juce_wchar c (t.getAndAdvance()); + + switch (c) + { + case 0: return; + + case '\"': out << "\\\""; break; + case '\\': out << "\\\\"; break; + case '\a': out << "\\a"; break; + case '\b': out << "\\b"; break; + case '\f': out << "\\f"; break; + case '\t': out << "\\t"; break; + case '\r': out << "\\r"; break; + case '\n': out << "\\n"; break; + + default: + if (c >= 32 && c < 127) + { + out << (char) c; + } + else + { + if (CharPointer_UTF16::getBytesRequiredFor (c) > 2) + { + CharPointer_UTF16::CharType chars[2]; + CharPointer_UTF16 utf16 (chars); + utf16.write (c); + + for (int i = 0; i < 2; ++i) + writeEscapedChar (out, (unsigned short) chars[i]); + } + else + { + writeEscapedChar (out, (unsigned short) c); + } + } + + break; + } + } + } + + static void writeSpaces (OutputStream& out, int numSpaces) + { + out.writeRepeatedByte (' ', (size_t) numSpaces); + } + + static void writeArray (OutputStream& out, const Array& array, + const int indentLevel, const bool allOnOneLine) + { + out << '['; + + if (array.size() > 0) + { + if (! allOnOneLine) + out << newLine; + + for (int i = 0; i < array.size(); ++i) + { + if (! allOnOneLine) + writeSpaces (out, indentLevel + indentSize); + + write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine); + + if (i < array.size() - 1) + { + if (allOnOneLine) + out << ", "; + else + out << ',' << newLine; + } + else if (! allOnOneLine) + out << newLine; + } + + if (! allOnOneLine) + writeSpaces (out, indentLevel); + } + + out << ']'; + } + + enum { indentSize = 2 }; +}; + +//============================================================================== +var JSON::parse (const String& text) +{ + var result; + + if (! parse (text, result)) + result = var(); + + return result; +} + +var JSON::fromString (StringRef text) +{ + var result; + + if (! JSONParser::parseAny (text.text, result)) + result = var(); + + return result; +} + +var JSON::parse (InputStream& input) +{ + return parse (input.readEntireStreamAsString()); +} + +var JSON::parse (const File& file) +{ + return parse (file.loadFileAsString()); +} + +Result JSON::parse (const String& text, var& result) +{ + return JSONParser::parseObjectOrArray (text.getCharPointer(), result); +} + +String JSON::toString (const var& data, const bool allOnOneLine) +{ + MemoryOutputStream mo (1024); + JSONFormatter::write (mo, data, 0, allOnOneLine); + return mo.toUTF8(); +} + +void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine) +{ + JSONFormatter::write (output, data, 0, allOnOneLine); +} + +String JSON::escapeString (StringRef s) +{ + MemoryOutputStream mo; + JSONFormatter::writeString (mo, s.text); + return mo.toString(); +} + +Result JSON::parseQuotedString (String::CharPointerType& t, var& result) +{ + const juce_wchar quote = t.getAndAdvance(); + + if (quote == '"' || quote == '\'') + return JSONParser::parseString (quote, t, result); + + return Result::fail ("Not a quoted string!"); +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class JSONTests : public UnitTest +{ +public: + JSONTests() : UnitTest ("JSON") {} + + static String createRandomWideCharString (Random& r) + { + juce_wchar buffer[40] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } + + static String createRandomIdentifier (Random& r) + { + char buffer[30] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + static const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"; + buffer[i] = chars [r.nextInt (sizeof (chars) - 1)]; + } + + return CharPointer_ASCII (buffer); + } + + static var createRandomVar (Random& r, int depth) + { + switch (r.nextInt (depth > 3 ? 6 : 8)) + { + case 0: return var(); + case 1: return r.nextInt(); + case 2: return r.nextInt64(); + case 3: return r.nextBool(); + case 4: return r.nextDouble(); + case 5: return createRandomWideCharString (r); + + case 6: + { + var v (createRandomVar (r, depth + 1)); + + for (int i = 1 + r.nextInt (30); --i >= 0;) + v.append (createRandomVar (r, depth + 1)); + + return v; + } + + case 7: + { + DynamicObject* o = new DynamicObject(); + + for (int i = r.nextInt (30); --i >= 0;) + o->setProperty (createRandomIdentifier (r), createRandomVar (r, depth + 1)); + + return o; + } + + default: + return var(); + } + } + + void runTest() + { + beginTest ("JSON"); + Random r = getRandom(); + + expect (JSON::parse (String::empty) == var::null); + expect (JSON::parse ("{}").isObject()); + expect (JSON::parse ("[]").isArray()); + expect (JSON::parse ("[ 1234 ]")[0].isInt()); + expect (JSON::parse ("[ 12345678901234 ]")[0].isInt64()); + expect (JSON::parse ("[ 1.123e3 ]")[0].isDouble()); + expect (JSON::parse ("[ -1234]")[0].isInt()); + expect (JSON::parse ("[-12345678901234]")[0].isInt64()); + expect (JSON::parse ("[-1.123e3]")[0].isDouble()); + + for (int i = 100; --i >= 0;) + { + var v; + + if (i > 0) + v = createRandomVar (r, 0); + + const bool oneLine = r.nextBool(); + String asString (JSON::toString (v, oneLine)); + var parsed = JSON::parse ("[" + asString + "]")[0]; + String parsedString (JSON::toString (parsed, oneLine)); + expect (asString.isNotEmpty() && parsedString == asString); + } + } +}; + +static JSONTests JSONUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.h b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.h new file mode 100644 index 0000000..7fb17ee --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_JSON.h @@ -0,0 +1,136 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_JSON_H_INCLUDED +#define JUCE_JSON_H_INCLUDED + + +//============================================================================== +/** + Contains static methods for converting JSON-formatted text to and from var objects. + + The var class is structurally compatible with JSON-formatted data, so these + functions allow you to parse JSON into a var object, and to convert a var + object to JSON-formatted text. + + @see var +*/ +class JUCE_API JSON +{ +public: + //============================================================================== + /** Parses a string of JSON-formatted text, and returns a result code containing + any parse errors. + + This will return the parsed structure in the parsedResult parameter, and will + return a Result object to indicate whether parsing was successful, and if not, + it will contain an error message. + + If you're not interested in the error message, you can use one of the other + shortcut parse methods, which simply return a var::null if the parsing fails. + + Note that this will only parse valid JSON, which means that the item given must + be either an object or an array definition. If you want to also be able to parse + any kind of primitive JSON object, use the fromString() method. + */ + static Result parse (const String& text, var& parsedResult); + + /** Attempts to parse some JSON-formatted text, and returns the result as a var object. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + + Note that this will only parse valid JSON, which means that the item given must + be either an object or an array definition. If you want to also be able to parse + any kind of primitive JSON object, use the fromString() method. + */ + static var parse (const String& text); + + /** Attempts to parse some JSON-formatted text from a file, and returns the result + as a var object. + + Note that this is just a short-cut for reading the entire file into a string and + parsing the result. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + */ + static var parse (const File& file); + + /** Attempts to parse some JSON-formatted text from a stream, and returns the result + as a var object. + + Note that this is just a short-cut for reading the entire stream into a string and + parsing the result. + + If the parsing fails, this simply returns var::null - if you need to find out more + detail about the parse error, use the alternative parse() method which returns a Result. + */ + static var parse (InputStream& input); + + //============================================================================== + /** Returns a string which contains a JSON-formatted representation of the var object. + If allOnOneLine is true, the result will be compacted into a single line of text + with no carriage-returns. If false, it will be laid-out in a more human-readable format. + @see writeToStream + */ + static String toString (const var& objectToFormat, + bool allOnOneLine = false); + + /** Parses a string that was created with the toString() method. + This is slightly different to the parse() methods because they will reject primitive + values and only accept array or object definitions, whereas this method will handle + either. + */ + static var fromString (StringRef); + + /** Writes a JSON-formatted representation of the var object to the given stream. + If allOnOneLine is true, the result will be compacted into a single line of text + with no carriage-returns. If false, it will be laid-out in a more human-readable format. + @see toString + */ + static void writeToStream (OutputStream& output, + const var& objectToFormat, + bool allOnOneLine = false); + + /** Returns a version of a string with any extended characters escaped. */ + static String escapeString (StringRef); + + /** Parses a quoted string-literal in JSON format, returning the un-escaped result in the + result parameter, and an error message in case the content was illegal. + This advances the text parameter, leaving it positioned after the closing quote. + */ + static Result parseQuotedString (String::CharPointerType& text, var& result); + +private: + //============================================================================== + JSON() JUCE_DELETED_FUNCTION; // This class can't be instantiated - just use its static methods. +}; + + +#endif // JUCE_JSON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp new file mode 100644 index 0000000..e78bb37 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp @@ -0,0 +1,1725 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#define JUCE_JS_OPERATORS(X) \ + X(semicolon, ";") X(dot, ".") X(comma, ",") \ + X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \ + X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \ + X(typeEquals, "===") X(equals, "==") X(assign, "=") \ + X(typeNotEquals, "!==") X(notEquals, "!=") X(logicalNot, "!") \ + X(plusEquals, "+=") X(plusplus, "++") X(plus, "+") \ + X(minusEquals, "-=") X(minusminus, "--") X(minus, "-") \ + X(timesEquals, "*=") X(times, "*") X(divideEquals, "/=") X(divide, "/") \ + X(moduloEquals, "%=") X(modulo, "%") X(xorEquals, "^=") X(bitwiseXor, "^") \ + X(andEquals, "&=") X(logicalAnd, "&&") X(bitwiseAnd, "&") \ + X(orEquals, "|=") X(logicalOr, "||") X(bitwiseOr, "|") \ + X(leftShiftEquals, "<<=") X(lessThanOrEqual, "<=") X(leftShift, "<<") X(lessThan, "<") \ + X(rightShiftUnsigned, ">>>") X(rightShiftEquals, ">>=") X(rightShift, ">>") X(greaterThanOrEqual, ">=") X(greaterThan, ">") + +#define JUCE_JS_KEYWORDS(X) \ + X(var, "var") X(if_, "if") X(else_, "else") X(do_, "do") X(null_, "null") \ + X(while_, "while") X(for_, "for") X(break_, "break") X(continue_, "continue") X(undefined, "undefined") \ + X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") + +namespace TokenTypes +{ + #define JUCE_DECLARE_JS_TOKEN(name, str) static const char* const name = str; + JUCE_JS_KEYWORDS (JUCE_DECLARE_JS_TOKEN) + JUCE_JS_OPERATORS (JUCE_DECLARE_JS_TOKEN) + JUCE_DECLARE_JS_TOKEN (eof, "$eof") + JUCE_DECLARE_JS_TOKEN (literal, "$literal") + JUCE_DECLARE_JS_TOKEN (identifier, "$identifier") +} + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4702) +#endif + +//============================================================================== +struct JavascriptEngine::RootObject : public DynamicObject +{ + RootObject() + { + setMethod ("exec", exec); + setMethod ("eval", eval); + setMethod ("trace", trace); + setMethod ("charToInt", charToInt); + setMethod ("parseInt", IntegerClass::parseInt); + } + + Time timeout; + + typedef const var::NativeFunctionArgs& Args; + typedef const char* TokenType; + + void execute (const String& code) + { + ExpressionTreeBuilder tb (code); + ScopedPointer (tb.parseStatementList())->perform (Scope (nullptr, this, this), nullptr); + } + + var evaluate (const String& code) + { + ExpressionTreeBuilder tb (code); + return ExpPtr (tb.parseExpression())->getResult (Scope (nullptr, this, this)); + } + + //============================================================================== + static bool areTypeEqual (const var& a, const var& b) + { + return a.hasSameTypeAs (b) && isFunction (a) == isFunction (b) + && (((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) || a == b); + } + + static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } + static bool isFunction (const var& v) { return dynamic_cast (v.getObject()) != nullptr; } + static bool isNumericOrUndefined (const var& v) { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool() || v.isUndefined(); } + static int64 getOctalValue (const String& s) { BigInteger b; b.parseString (s, 8); return b.toInt64(); } + static Identifier getPrototypeIdentifier() { static const Identifier i ("prototype"); return i; } + static var* getPropertyPointer (DynamicObject* o, Identifier i) { return o->getProperties().getVarPointer (i); } + + //============================================================================== + struct CodeLocation + { + CodeLocation (const String& code) noexcept : program (code), location (program.getCharPointer()) {} + CodeLocation (const CodeLocation& other) noexcept : program (other.program), location (other.location) {} + + void throwError (const String& message) const + { + int col = 1, line = 1; + + for (String::CharPointerType i (program.getCharPointer()); i < location && ! i.isEmpty(); ++i) + { + ++col; + if (*i == '\n') { col = 1; ++line; } + } + + throw "Line " + String (line) + ", column " + String (col) + " : " + message; + } + + String program; + String::CharPointerType location; + }; + + //============================================================================== + struct Scope + { + Scope (const Scope* p, RootObject* r, DynamicObject* s) noexcept : parent (p), root (r), scope (s) {} + + const Scope* parent; + ReferenceCountedObjectPtr root; + DynamicObject::Ptr scope; + + var findFunctionCall (const CodeLocation& location, const var& targetObject, Identifier functionName) const + { + if (DynamicObject* o = targetObject.getDynamicObject()) + { + if (const var* prop = getPropertyPointer (o, functionName)) + return *prop; + + for (DynamicObject* p = o->getProperty (getPrototypeIdentifier()).getDynamicObject(); p != nullptr; + p = p->getProperty (getPrototypeIdentifier()).getDynamicObject()) + { + if (const var* prop = getPropertyPointer (p, functionName)) + return *prop; + } + } + + if (targetObject.isString()) + if (var* m = findRootClassProperty (StringClass::getClassName(), functionName)) + return *m; + + if (targetObject.isArray()) + if (var* m = findRootClassProperty (ArrayClass::getClassName(), functionName)) + return *m; + + if (var* m = findRootClassProperty (ObjectClass::getClassName(), functionName)) + return *m; + + location.throwError ("Unknown function '" + functionName.toString() + "'"); + return var(); + } + + var* findRootClassProperty (Identifier className, Identifier propName) const + { + if (DynamicObject* cls = root->getProperty (className).getDynamicObject()) + return getPropertyPointer (cls, propName); + + return nullptr; + } + + var findSymbolInParentScopes (Identifier name) const + { + if (const var* v = getPropertyPointer (scope, name)) + return *v; + + return parent != nullptr ? parent->findSymbolInParentScopes (name) + : var::undefined(); + } + + bool findAndInvokeMethod (Identifier function, const var::NativeFunctionArgs& args, var& result) const + { + DynamicObject* target = args.thisObject.getDynamicObject(); + + if (target == nullptr || target == scope) + { + if (const var* m = getPropertyPointer (scope, function)) + { + if (FunctionObject* fo = dynamic_cast (m->getObject())) + { + result = fo->invoke (*this, args); + return true; + } + } + } + + const NamedValueSet& props = scope->getProperties(); + + for (int i = 0; i < props.size(); ++i) + if (DynamicObject* o = props.getValueAt (i).getDynamicObject()) + if (Scope (this, root, o).findAndInvokeMethod (function, args, result)) + return true; + + return false; + } + + void checkTimeOut (const CodeLocation& location) const + { + if (Time::getCurrentTime() > root->timeout) + location.throwError ("Execution timed-out"); + } + }; + + //============================================================================== + struct Statement + { + Statement (const CodeLocation& l) noexcept : location (l) {} + virtual ~Statement() {} + + enum ResultCode { ok = 0, returnWasHit, breakWasHit, continueWasHit }; + virtual ResultCode perform (const Scope&, var*) const { return ok; } + + CodeLocation location; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Statement) + }; + + struct Expression : public Statement + { + Expression (const CodeLocation& l) noexcept : Statement (l) {} + + virtual var getResult (const Scope&) const { return var::undefined(); } + virtual void assign (const Scope&, const var&) const { location.throwError ("Cannot assign to this expression!"); } + + ResultCode perform (const Scope& s, var*) const override { getResult (s); return ok; } + }; + + typedef ScopedPointer ExpPtr; + + struct BlockStatement : public Statement + { + BlockStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + for (int i = 0; i < statements.size(); ++i) + if (ResultCode r = statements.getUnchecked(i)->perform (s, returnedValue)) + return r; + + return ok; + } + + OwnedArray statements; + }; + + struct IfStatement : public Statement + { + IfStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + return (condition->getResult(s) ? trueBranch : falseBranch)->perform (s, returnedValue); + } + + ExpPtr condition; + ScopedPointer trueBranch, falseBranch; + }; + + struct VarStatement : public Statement + { + VarStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var*) const override + { + s.scope->setProperty (name, initialiser->getResult (s)); + return ok; + } + + Identifier name; + ExpPtr initialiser; + }; + + struct LoopStatement : public Statement + { + LoopStatement (const CodeLocation& l, bool isDo) noexcept : Statement (l), isDoLoop (isDo) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + initialiser->perform (s, nullptr); + + while (isDoLoop || condition->getResult (s)) + { + s.checkTimeOut (location); + ResultCode r = body->perform (s, returnedValue); + + if (r == returnWasHit) return r; + if (r == breakWasHit) break; + + iterator->perform (s, nullptr); + + if (isDoLoop && r != continueWasHit && ! condition->getResult (s)) + break; + } + + return ok; + } + + ScopedPointer initialiser, iterator, body; + ExpPtr condition; + bool isDoLoop; + }; + + struct ReturnStatement : public Statement + { + ReturnStatement (const CodeLocation& l, Expression* v) noexcept : Statement (l), returnValue (v) {} + + ResultCode perform (const Scope& s, var* ret) const override + { + if (ret != nullptr) *ret = returnValue->getResult (s); + return returnWasHit; + } + + ExpPtr returnValue; + }; + + struct BreakStatement : public Statement + { + BreakStatement (const CodeLocation& l) noexcept : Statement (l) {} + ResultCode perform (const Scope&, var*) const override { return breakWasHit; } + }; + + struct ContinueStatement : public Statement + { + ContinueStatement (const CodeLocation& l) noexcept : Statement (l) {} + ResultCode perform (const Scope&, var*) const override { return continueWasHit; } + }; + + struct LiteralValue : public Expression + { + LiteralValue (const CodeLocation& l, const var& v) noexcept : Expression (l), value (v) {} + var getResult (const Scope&) const override { return value; } + var value; + }; + + struct UnqualifiedName : public Expression + { + UnqualifiedName (const CodeLocation& l, Identifier n) noexcept : Expression (l), name (n) {} + + var getResult (const Scope& s) const override { return s.findSymbolInParentScopes (name); } + + void assign (const Scope& s, const var& newValue) const override + { + if (var* v = getPropertyPointer (s.scope, name)) + *v = newValue; + else + s.root->setProperty (name, newValue); + } + + Identifier name; + }; + + struct DotOperator : public Expression + { + DotOperator (const CodeLocation& l, ExpPtr& p, Identifier c) noexcept : Expression (l), parent (p), child (c) {} + + var getResult (const Scope& s) const override + { + var p (parent->getResult (s)); + static const Identifier lengthID ("length"); + + if (child == lengthID) + { + if (Array* array = p.getArray()) return array->size(); + if (p.isString()) return p.toString().length(); + } + + if (DynamicObject* o = p.getDynamicObject()) + if (const var* v = getPropertyPointer (o, child)) + return *v; + + return var::undefined(); + } + + void assign (const Scope& s, const var& newValue) const override + { + if (DynamicObject* o = parent->getResult (s).getDynamicObject()) + o->setProperty (child, newValue); + else + Expression::assign (s, newValue); + } + + ExpPtr parent; + Identifier child; + }; + + struct ArraySubscript : public Expression + { + ArraySubscript (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + if (const Array* array = object->getResult (s).getArray()) + return (*array) [static_cast (index->getResult (s))]; + + return var::undefined(); + } + + void assign (const Scope& s, const var& newValue) const override + { + if (Array* array = object->getResult (s).getArray()) + { + const int i = index->getResult (s); + while (array->size() < i) + array->add (var::undefined()); + + array->set (i, newValue); + return; + } + + Expression::assign (s, newValue); + } + + ExpPtr object, index; + }; + + struct BinaryOperatorBase : public Expression + { + BinaryOperatorBase (const CodeLocation& l, ExpPtr& a, ExpPtr& b, TokenType op) noexcept + : Expression (l), lhs (a), rhs (b), operation (op) {} + + ExpPtr lhs, rhs; + TokenType operation; + }; + + struct BinaryOperator : public BinaryOperatorBase + { + BinaryOperator (const CodeLocation& l, ExpPtr& a, ExpPtr& b, TokenType op) noexcept + : BinaryOperatorBase (l, a, b, op) {} + + virtual var getWithUndefinedArg() const { return var::undefined(); } + virtual var getWithDoubles (double, double) const { return throwError ("Double"); } + virtual var getWithInts (int64, int64) const { return throwError ("Integer"); } + virtual var getWithArrayOrObject (const var& a, const var&) const { return throwError (a.isArray() ? "Array" : "Object"); } + virtual var getWithStrings (const String&, const String&) const { return throwError ("String"); } + + var getResult (const Scope& s) const override + { + var a (lhs->getResult (s)), b (rhs->getResult (s)); + + if ((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) + return getWithUndefinedArg(); + + if (isNumericOrUndefined (a) && isNumericOrUndefined (b)) + return (a.isDouble() || b.isDouble()) ? getWithDoubles (a, b) : getWithInts (a, b); + + if (a.isArray() || a.isObject()) + return getWithArrayOrObject (a, b); + + return getWithStrings (a.toString(), b.toString()); + } + + var throwError (const char* typeName) const + { location.throwError (getTokenName (operation) + " is not allowed on the " + typeName + " type"); return var(); } + }; + + struct EqualsOp : public BinaryOperator + { + EqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::equals) {} + var getWithUndefinedArg() const override { return true; } + var getWithDoubles (double a, double b) const override { return a == b; } + var getWithInts (int64 a, int64 b) const override { return a == b; } + var getWithStrings (const String& a, const String& b) const override { return a == b; } + var getWithArrayOrObject (const var& a, const var& b) const override { return a == b; } + }; + + struct NotEqualsOp : public BinaryOperator + { + NotEqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::notEquals) {} + var getWithUndefinedArg() const override { return false; } + var getWithDoubles (double a, double b) const override { return a != b; } + var getWithInts (int64 a, int64 b) const override { return a != b; } + var getWithStrings (const String& a, const String& b) const override { return a != b; } + var getWithArrayOrObject (const var& a, const var& b) const override { return a != b; } + }; + + struct LessThanOp : public BinaryOperator + { + LessThanOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::lessThan) {} + var getWithDoubles (double a, double b) const override { return a < b; } + var getWithInts (int64 a, int64 b) const override { return a < b; } + var getWithStrings (const String& a, const String& b) const override { return a < b; } + }; + + struct LessThanOrEqualOp : public BinaryOperator + { + LessThanOrEqualOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::lessThanOrEqual) {} + var getWithDoubles (double a, double b) const override { return a <= b; } + var getWithInts (int64 a, int64 b) const override { return a <= b; } + var getWithStrings (const String& a, const String& b) const override { return a <= b; } + }; + + struct GreaterThanOp : public BinaryOperator + { + GreaterThanOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::greaterThan) {} + var getWithDoubles (double a, double b) const override { return a > b; } + var getWithInts (int64 a, int64 b) const override { return a > b; } + var getWithStrings (const String& a, const String& b) const override { return a > b; } + }; + + struct GreaterThanOrEqualOp : public BinaryOperator + { + GreaterThanOrEqualOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::greaterThanOrEqual) {} + var getWithDoubles (double a, double b) const override { return a >= b; } + var getWithInts (int64 a, int64 b) const override { return a >= b; } + var getWithStrings (const String& a, const String& b) const override { return a >= b; } + }; + + struct AdditionOp : public BinaryOperator + { + AdditionOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::plus) {} + var getWithDoubles (double a, double b) const override { return a + b; } + var getWithInts (int64 a, int64 b) const override { return a + b; } + var getWithStrings (const String& a, const String& b) const override { return a + b; } + }; + + struct SubtractionOp : public BinaryOperator + { + SubtractionOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::minus) {} + var getWithDoubles (double a, double b) const override { return a - b; } + var getWithInts (int64 a, int64 b) const override { return a - b; } + }; + + struct MultiplyOp : public BinaryOperator + { + MultiplyOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::times) {} + var getWithDoubles (double a, double b) const override { return a * b; } + var getWithInts (int64 a, int64 b) const override { return a * b; } + }; + + struct DivideOp : public BinaryOperator + { + DivideOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::divide) {} + var getWithDoubles (double a, double b) const override { return b != 0 ? a / b : std::numeric_limits::infinity(); } + var getWithInts (int64 a, int64 b) const override { return b != 0 ? var (a / (double) b) : var (std::numeric_limits::infinity()); } + }; + + struct ModuloOp : public BinaryOperator + { + ModuloOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::modulo) {} + var getWithInts (int64 a, int64 b) const override { return b != 0 ? var (a % b) : var (std::numeric_limits::infinity()); } + }; + + struct BitwiseOrOp : public BinaryOperator + { + BitwiseOrOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseOr) {} + var getWithInts (int64 a, int64 b) const override { return a | b; } + }; + + struct BitwiseAndOp : public BinaryOperator + { + BitwiseAndOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseAnd) {} + var getWithInts (int64 a, int64 b) const override { return a & b; } + }; + + struct BitwiseXorOp : public BinaryOperator + { + BitwiseXorOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseXor) {} + var getWithInts (int64 a, int64 b) const override { return a ^ b; } + }; + + struct LeftShiftOp : public BinaryOperator + { + LeftShiftOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::leftShift) {} + var getWithInts (int64 a, int64 b) const override { return ((int) a) << (int) b; } + }; + + struct RightShiftOp : public BinaryOperator + { + RightShiftOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::rightShift) {} + var getWithInts (int64 a, int64 b) const override { return ((int) a) >> (int) b; } + }; + + struct RightShiftUnsignedOp : public BinaryOperator + { + RightShiftUnsignedOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::rightShiftUnsigned) {} + var getWithInts (int64 a, int64 b) const override { return (int) (((uint32) a) >> (int) b); } + }; + + struct LogicalAndOp : public BinaryOperatorBase + { + LogicalAndOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::logicalAnd) {} + var getResult (const Scope& s) const override { return lhs->getResult (s) && rhs->getResult (s); } + }; + + struct LogicalOrOp : public BinaryOperatorBase + { + LogicalOrOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::logicalOr) {} + var getResult (const Scope& s) const override { return lhs->getResult (s) || rhs->getResult (s); } + }; + + struct TypeEqualsOp : public BinaryOperatorBase + { + TypeEqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::typeEquals) {} + var getResult (const Scope& s) const override { return areTypeEqual (lhs->getResult (s), rhs->getResult (s)); } + }; + + struct TypeNotEqualsOp : public BinaryOperatorBase + { + TypeNotEqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::typeNotEquals) {} + var getResult (const Scope& s) const override { return ! areTypeEqual (lhs->getResult (s), rhs->getResult (s)); } + }; + + struct ConditionalOp : public Expression + { + ConditionalOp (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override { return (condition->getResult (s) ? trueBranch : falseBranch)->getResult (s); } + void assign (const Scope& s, const var& v) const override { (condition->getResult (s) ? trueBranch : falseBranch)->assign (s, v); } + + ExpPtr condition, trueBranch, falseBranch; + }; + + struct Assignment : public Expression + { + Assignment (const CodeLocation& l, ExpPtr& dest, ExpPtr& source) noexcept : Expression (l), target (dest), newValue (source) {} + + var getResult (const Scope& s) const override + { + var value (newValue->getResult (s)); + target->assign (s, value); + return value; + } + + ExpPtr target, newValue; + }; + + struct SelfAssignment : public Expression + { + SelfAssignment (const CodeLocation& l, Expression* dest, Expression* source) noexcept + : Expression (l), target (dest), newValue (source) {} + + var getResult (const Scope& s) const override + { + var value (newValue->getResult (s)); + target->assign (s, value); + return value; + } + + Expression* target; // Careful! this pointer aliases a sub-term of newValue! + ExpPtr newValue; + TokenType op; + }; + + struct PostAssignment : public SelfAssignment + { + PostAssignment (const CodeLocation& l, Expression* dest, Expression* source) noexcept : SelfAssignment (l, dest, source) {} + + var getResult (const Scope& s) const override + { + var oldValue (target->getResult (s)); + target->assign (s, newValue->getResult (s)); + return oldValue; + } + }; + + struct FunctionCall : public Expression + { + FunctionCall (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + if (DotOperator* dot = dynamic_cast (object.get())) + { + var thisObject (dot->parent->getResult (s)); + return invokeFunction (s, s.findFunctionCall (location, thisObject, dot->child), thisObject); + } + + var function (object->getResult (s)); + return invokeFunction (s, function, var (s.scope)); + } + + var invokeFunction (const Scope& s, const var& function, const var& thisObject) const + { + s.checkTimeOut (location); + + Array argVars; + for (int i = 0; i < arguments.size(); ++i) + argVars.add (arguments.getUnchecked(i)->getResult (s)); + + const var::NativeFunctionArgs args (thisObject, argVars.begin(), argVars.size()); + + if (var::NativeFunction nativeFunction = function.getNativeFunction()) + return nativeFunction (args); + + if (FunctionObject* fo = dynamic_cast (function.getObject())) + return fo->invoke (s, args); + + location.throwError ("This expression is not a function!"); return var(); + } + + ExpPtr object; + OwnedArray arguments; + }; + + struct NewOperator : public FunctionCall + { + NewOperator (const CodeLocation& l) noexcept : FunctionCall (l) {} + + var getResult (const Scope& s) const override + { + var classOrFunc = object->getResult (s); + + const bool isFunc = isFunction (classOrFunc); + if (! (isFunc || classOrFunc.getDynamicObject() != nullptr)) + return var::undefined(); + + DynamicObject::Ptr newObject (new DynamicObject()); + + if (isFunc) + invokeFunction (s, classOrFunc, newObject.get()); + else + newObject->setProperty (getPrototypeIdentifier(), classOrFunc); + + return newObject.get(); + } + }; + + struct ObjectDeclaration : public Expression + { + ObjectDeclaration (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + DynamicObject::Ptr newObject (new DynamicObject()); + + for (int i = 0; i < names.size(); ++i) + newObject->setProperty (names.getUnchecked(i), initialisers.getUnchecked(i)->getResult (s)); + + return newObject.get(); + } + + Array names; + OwnedArray initialisers; + }; + + struct ArrayDeclaration : public Expression + { + ArrayDeclaration (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + Array a; + + for (int i = 0; i < values.size(); ++i) + a.add (values.getUnchecked(i)->getResult (s)); + + return a; + } + + OwnedArray values; + }; + + //============================================================================== + struct FunctionObject : public DynamicObject + { + FunctionObject() noexcept {} + + FunctionObject (const FunctionObject& other) : DynamicObject(), functionCode (other.functionCode) + { + ExpressionTreeBuilder tb (functionCode); + tb.parseFunctionParamsAndBody (*this); + } + + DynamicObject::Ptr clone() override { return new FunctionObject (*this); } + + void writeAsJSON (OutputStream& out, int /*indentLevel*/, bool /*allOnOneLine*/) override + { + out << "function " << functionCode; + } + + var invoke (const Scope& s, const var::NativeFunctionArgs& args) const + { + DynamicObject::Ptr functionRoot (new DynamicObject()); + + static const Identifier thisIdent ("this"); + functionRoot->setProperty (thisIdent, args.thisObject); + + for (int i = 0; i < parameters.size(); ++i) + functionRoot->setProperty (parameters.getReference(i), + i < args.numArguments ? args.arguments[i] : var::undefined()); + + var result; + body->perform (Scope (&s, s.root, functionRoot), &result); + return result; + } + + String functionCode; + Array parameters; + ScopedPointer body; + }; + + //============================================================================== + struct TokenIterator + { + TokenIterator (const String& code) : location (code), p (code.getCharPointer()) { skip(); } + + void skip() + { + skipWhitespaceAndComments(); + location.location = p; + currentType = matchNextToken(); + } + + void match (TokenType expected) + { + if (currentType != expected) + location.throwError ("Found " + getTokenName (currentType) + " when expecting " + getTokenName (expected)); + + skip(); + } + + bool matchIf (TokenType expected) { if (currentType == expected) { skip(); return true; } return false; } + bool matchesAny (TokenType t1, TokenType t2) const { return currentType == t1 || currentType == t2; } + bool matchesAny (TokenType t1, TokenType t2, TokenType t3) const { return matchesAny (t1, t2) || currentType == t3; } + + CodeLocation location; + TokenType currentType; + var currentValue; + + private: + String::CharPointerType p; + + static bool isIdentifierStart (const juce_wchar c) noexcept { return CharacterFunctions::isLetter (c) || c == '_'; } + static bool isIdentifierBody (const juce_wchar c) noexcept { return CharacterFunctions::isLetterOrDigit (c) || c == '_'; } + + TokenType matchNextToken() + { + if (isIdentifierStart (*p)) + { + String::CharPointerType end (p); + while (isIdentifierBody (*++end)) {} + + const size_t len = (size_t) (end - p); + #define JUCE_JS_COMPARE_KEYWORD(name, str) if (len == sizeof (str) - 1 && matchToken (TokenTypes::name, len)) return TokenTypes::name; + JUCE_JS_KEYWORDS (JUCE_JS_COMPARE_KEYWORD) + + currentValue = String (p, end); p = end; + return TokenTypes::identifier; + } + + if (p.isDigit()) + { + if (parseHexLiteral() || parseFloatLiteral() || parseOctalLiteral() || parseDecimalLiteral()) + return TokenTypes::literal; + + location.throwError ("Syntax error in numeric constant"); + } + + if (parseStringLiteral (*p) || (*p == '.' && parseFloatLiteral())) + return TokenTypes::literal; + + #define JUCE_JS_COMPARE_OPERATOR(name, str) if (matchToken (TokenTypes::name, sizeof (str) - 1)) return TokenTypes::name; + JUCE_JS_OPERATORS (JUCE_JS_COMPARE_OPERATOR) + + if (! p.isEmpty()) + location.throwError ("Unexpected character '" + String::charToString (*p) + "' in source"); + + return TokenTypes::eof; + } + + bool matchToken (TokenType name, const size_t len) noexcept + { + if (p.compareUpTo (CharPointer_ASCII (name), (int) len) != 0) return false; + p += (int) len; return true; + } + + void skipWhitespaceAndComments() + { + for (;;) + { + p = p.findEndOfWhitespace(); + + if (*p == '/') + { + const juce_wchar c2 = p[1]; + + if (c2 == '/') { p = CharacterFunctions::find (p, (juce_wchar) '\n'); continue; } + + if (c2 == '*') + { + location.location = p; + p = CharacterFunctions::find (p + 2, CharPointer_ASCII ("*/")); + if (p.isEmpty()) location.throwError ("Unterminated '/*' comment"); + p += 2; continue; + } + } + + break; + } + } + + bool parseStringLiteral (juce_wchar quoteType) + { + if (quoteType != '"' && quoteType != '\'') + return false; + + Result r (JSON::parseQuotedString (p, currentValue)); + if (r.failed()) location.throwError (r.getErrorMessage()); + return true; + } + + bool parseHexLiteral() + { + if (*p != '0' || (p[1] != 'x' && p[1] != 'X')) return false; + + String::CharPointerType t (++p); + int64 v = CharacterFunctions::getHexDigitValue (*++t); + if (v < 0) return false; + + for (;;) + { + const int digit = CharacterFunctions::getHexDigitValue (*++t); + if (digit < 0) break; + v = v * 16 + digit; + } + + currentValue = v; p = t; + return true; + } + + bool parseFloatLiteral() + { + int numDigits = 0; + String::CharPointerType t (p); + while (t.isDigit()) { ++t; ++numDigits; } + + const bool hasPoint = (*t == '.'); + + if (hasPoint) + while ((++t).isDigit()) ++numDigits; + + if (numDigits == 0) + return false; + + juce_wchar c = *t; + const bool hasExponent = (c == 'e' || c == 'E'); + + if (hasExponent) + { + c = *++t; + if (c == '+' || c == '-') ++t; + if (! t.isDigit()) return false; + while ((++t).isDigit()) {} + } + + if (! (hasExponent || hasPoint)) return false; + + currentValue = CharacterFunctions::getDoubleValue (p); p = t; + return true; + } + + bool parseOctalLiteral() + { + String::CharPointerType t (p); + int64 v = *t - '0'; + if (v != 0) return false; // first digit of octal must be 0 + + for (;;) + { + const int digit = (int) (*++t - '0'); + if (isPositiveAndBelow (digit, 8)) v = v * 8 + digit; + else if (isPositiveAndBelow (digit, 10)) location.throwError ("Decimal digit in octal constant"); + else break; + } + + currentValue = v; p = t; + return true; + } + + bool parseDecimalLiteral() + { + int64 v = 0; + + for (;; ++p) + { + const int digit = (int) (*p - '0'); + if (isPositiveAndBelow (digit, 10)) v = v * 10 + digit; + else break; + } + + currentValue = v; + return true; + } + }; + + //============================================================================== + struct ExpressionTreeBuilder : private TokenIterator + { + ExpressionTreeBuilder (const String code) : TokenIterator (code) {} + + BlockStatement* parseStatementList() + { + ScopedPointer b (new BlockStatement (location)); + + while (currentType != TokenTypes::closeBrace && currentType != TokenTypes::eof) + b->statements.add (parseStatement()); + + return b.release(); + } + + void parseFunctionParamsAndBody (FunctionObject& fo) + { + match (TokenTypes::openParen); + + while (currentType != TokenTypes::closeParen) + { + fo.parameters.add (currentValue.toString()); + match (TokenTypes::identifier); + + if (currentType != TokenTypes::closeParen) + match (TokenTypes::comma); + } + + match (TokenTypes::closeParen); + fo.body = parseBlock(); + } + + Expression* parseExpression() + { + ExpPtr lhs (parseLogicOperator()); + + if (matchIf (TokenTypes::question)) return parseTerneryOperator (lhs); + if (matchIf (TokenTypes::assign)) { ExpPtr rhs (parseExpression()); return new Assignment (location, lhs, rhs); } + if (matchIf (TokenTypes::plusEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::minusEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::leftShiftEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::rightShiftEquals)) return parseInPlaceOpExpression (lhs); + + return lhs.release(); + } + + private: + void throwError (const String& err) const { location.throwError (err); } + + template + Expression* parseInPlaceOpExpression (ExpPtr& lhs) + { + ExpPtr rhs (parseExpression()); + Expression* bareLHS = lhs; // careful - bare pointer is deliberately alised + return new SelfAssignment (location, bareLHS, new OpType (location, lhs, rhs)); + } + + BlockStatement* parseBlock() + { + match (TokenTypes::openBrace); + ScopedPointer b (parseStatementList()); + match (TokenTypes::closeBrace); + return b.release(); + } + + Statement* parseStatement() + { + if (currentType == TokenTypes::openBrace) return parseBlock(); + if (matchIf (TokenTypes::var)) return parseVar(); + if (matchIf (TokenTypes::if_)) return parseIf(); + if (matchIf (TokenTypes::while_)) return parseDoOrWhileLoop (false); + if (matchIf (TokenTypes::do_)) return parseDoOrWhileLoop (true); + if (matchIf (TokenTypes::for_)) return parseForLoop(); + if (matchIf (TokenTypes::return_)) return new ReturnStatement (location, matchIf (TokenTypes::semicolon) ? new Expression (location) : parseExpression()); + if (matchIf (TokenTypes::break_)) return new BreakStatement (location); + if (matchIf (TokenTypes::continue_)) return new ContinueStatement (location); + if (matchIf (TokenTypes::function)) return parseFunction(); + if (matchIf (TokenTypes::semicolon)) return new Statement (location); + if (matchIf (TokenTypes::plusplus)) return parsePreIncDec(); + if (matchIf (TokenTypes::minusminus)) return parsePreIncDec(); + + if (matchesAny (TokenTypes::openParen, TokenTypes::openBracket)) + return matchEndOfStatement (parseFactor()); + + if (matchesAny (TokenTypes::identifier, TokenTypes::literal, TokenTypes::minus)) + return matchEndOfStatement (parseExpression()); + + throwError ("Found " + getTokenName (currentType) + " when expecting a statement"); + return nullptr; + } + + Expression* matchEndOfStatement (Expression* ex) { ExpPtr e (ex); if (currentType != TokenTypes::eof) match (TokenTypes::semicolon); return e.release(); } + Expression* matchCloseParen (Expression* ex) { ExpPtr e (ex); match (TokenTypes::closeParen); return e.release(); } + + Statement* parseIf() + { + ScopedPointer s (new IfStatement (location)); + match (TokenTypes::openParen); + s->condition = parseExpression(); + match (TokenTypes::closeParen); + s->trueBranch = parseStatement(); + s->falseBranch = matchIf (TokenTypes::else_) ? parseStatement() : new Statement (location); + return s.release(); + } + + Statement* parseVar() + { + ScopedPointer s (new VarStatement (location)); + s->name = parseIdentifier(); + s->initialiser = matchIf (TokenTypes::assign) ? parseExpression() : new Expression (location); + + if (matchIf (TokenTypes::comma)) + { + ScopedPointer block (new BlockStatement (location)); + block->statements.add (s.release()); + block->statements.add (parseVar()); + return block.release(); + } + + match (TokenTypes::semicolon); + return s.release(); + } + + Statement* parseFunction() + { + Identifier name; + var fn = parseFunctionDefinition (name); + + if (name.isNull()) + throwError ("Functions defined at statement-level must have a name"); + + ExpPtr nm (new UnqualifiedName (location, name)), value (new LiteralValue (location, fn)); + return new Assignment (location, nm, value); + } + + Statement* parseForLoop() + { + ScopedPointer s (new LoopStatement (location, false)); + match (TokenTypes::openParen); + s->initialiser = parseStatement(); + + if (matchIf (TokenTypes::semicolon)) + s->condition = new LiteralValue (location, true); + else + { + s->condition = parseExpression(); + match (TokenTypes::semicolon); + } + + if (matchIf (TokenTypes::closeParen)) + s->iterator = new Statement (location); + else + { + s->iterator = parseExpression(); + match (TokenTypes::closeParen); + } + + s->body = parseStatement(); + return s.release(); + } + + Statement* parseDoOrWhileLoop (bool isDoLoop) + { + ScopedPointer s (new LoopStatement (location, isDoLoop)); + s->initialiser = new Statement (location); + s->iterator = new Statement (location); + + if (isDoLoop) + { + s->body = parseBlock(); + match (TokenTypes::while_); + } + + match (TokenTypes::openParen); + s->condition = parseExpression(); + match (TokenTypes::closeParen); + + if (! isDoLoop) + s->body = parseStatement(); + + return s.release(); + } + + Identifier parseIdentifier() + { + Identifier i; + if (currentType == TokenTypes::identifier) + i = currentValue.toString(); + + match (TokenTypes::identifier); + return i; + } + + var parseFunctionDefinition (Identifier& functionName) + { + const String::CharPointerType functionStart (location.location); + + if (currentType == TokenTypes::identifier) + functionName = parseIdentifier(); + + ScopedPointer fo (new FunctionObject()); + parseFunctionParamsAndBody (*fo); + fo->functionCode = String (functionStart, location.location); + return var (fo.release()); + } + + Expression* parseFunctionCall (FunctionCall* call, ExpPtr& function) + { + ScopedPointer s (call); + s->object = function; + match (TokenTypes::openParen); + + while (currentType != TokenTypes::closeParen) + { + s->arguments.add (parseExpression()); + if (currentType != TokenTypes::closeParen) + match (TokenTypes::comma); + } + + return matchCloseParen (s.release()); + } + + Expression* parseSuffixes (Expression* e) + { + ExpPtr input (e); + + if (matchIf (TokenTypes::dot)) + return parseSuffixes (new DotOperator (location, input, parseIdentifier())); + + if (currentType == TokenTypes::openParen) + return parseSuffixes (parseFunctionCall (new FunctionCall (location), input)); + + if (matchIf (TokenTypes::openBracket)) + { + ScopedPointer s (new ArraySubscript (location)); + s->object = input; + s->index = parseExpression(); + match (TokenTypes::closeBracket); + return parseSuffixes (s.release()); + } + + if (matchIf (TokenTypes::plusplus)) return parsePostIncDec (input); + if (matchIf (TokenTypes::minusminus)) return parsePostIncDec (input); + + return input.release(); + } + + Expression* parseFactor() + { + if (currentType == TokenTypes::identifier) return parseSuffixes (new UnqualifiedName (location, parseIdentifier())); + if (matchIf (TokenTypes::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); + if (matchIf (TokenTypes::true_)) return parseSuffixes (new LiteralValue (location, (int) 1)); + if (matchIf (TokenTypes::false_)) return parseSuffixes (new LiteralValue (location, (int) 0)); + if (matchIf (TokenTypes::null_)) return parseSuffixes (new LiteralValue (location, var())); + if (matchIf (TokenTypes::undefined)) return parseSuffixes (new Expression (location)); + + if (currentType == TokenTypes::literal) + { + var v (currentValue); skip(); + return parseSuffixes (new LiteralValue (location, v)); + } + + if (matchIf (TokenTypes::openBrace)) + { + ScopedPointer e (new ObjectDeclaration (location)); + + while (currentType != TokenTypes::closeBrace) + { + e->names.add (currentValue.toString()); + match ((currentType == TokenTypes::literal && currentValue.isString()) + ? TokenTypes::literal : TokenTypes::identifier); + match (TokenTypes::colon); + e->initialisers.add (parseExpression()); + + if (currentType != TokenTypes::closeBrace) + match (TokenTypes::comma); + } + + match (TokenTypes::closeBrace); + return parseSuffixes (e.release()); + } + + if (matchIf (TokenTypes::openBracket)) + { + ScopedPointer e (new ArrayDeclaration (location)); + + while (currentType != TokenTypes::closeBracket) + { + e->values.add (parseExpression()); + + if (currentType != TokenTypes::closeBracket) + match (TokenTypes::comma); + } + + match (TokenTypes::closeBracket); + return parseSuffixes (e.release()); + } + + if (matchIf (TokenTypes::function)) + { + Identifier name; + var fn = parseFunctionDefinition (name); + + if (name.isValid()) + throwError ("Inline functions definitions cannot have a name"); + + return new LiteralValue (location, fn); + } + + if (matchIf (TokenTypes::new_)) + { + ExpPtr name (new UnqualifiedName (location, parseIdentifier())); + + while (matchIf (TokenTypes::dot)) + name = new DotOperator (location, name, parseIdentifier()); + + return parseFunctionCall (new NewOperator (location), name); + } + + throwError ("Found " + getTokenName (currentType) + " when expecting an expression"); + return nullptr; + } + + template + Expression* parsePreIncDec() + { + Expression* e = parseFactor(); // careful - bare pointer is deliberately alised + ExpPtr lhs (e), one (new LiteralValue (location, (int) 1)); + return new SelfAssignment (location, e, new OpType (location, lhs, one)); + } + + template + Expression* parsePostIncDec (ExpPtr& lhs) + { + Expression* e = lhs.release(); // careful - bare pointer is deliberately alised + ExpPtr lhs2 (e), one (new LiteralValue (location, (int) 1)); + return new PostAssignment (location, e, new OpType (location, lhs2, one)); + } + + Expression* parseUnary() + { + if (matchIf (TokenTypes::minus)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new SubtractionOp (location, a, b); } + if (matchIf (TokenTypes::logicalNot)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new EqualsOp (location, a, b); } + if (matchIf (TokenTypes::plusplus)) return parsePreIncDec(); + if (matchIf (TokenTypes::minusminus)) return parsePreIncDec(); + + return parseFactor(); + } + + Expression* parseMultiplyDivide() + { + ExpPtr a (parseUnary()); + + for (;;) + { + if (matchIf (TokenTypes::times)) { ExpPtr b (parseUnary()); a = new MultiplyOp (location, a, b); } + else if (matchIf (TokenTypes::divide)) { ExpPtr b (parseUnary()); a = new DivideOp (location, a, b); } + else if (matchIf (TokenTypes::modulo)) { ExpPtr b (parseUnary()); a = new ModuloOp (location, a, b); } + else break; + } + + return a.release(); + } + + Expression* parseAdditionSubtraction() + { + ExpPtr a (parseMultiplyDivide()); + + for (;;) + { + if (matchIf (TokenTypes::plus)) { ExpPtr b (parseMultiplyDivide()); a = new AdditionOp (location, a, b); } + else if (matchIf (TokenTypes::minus)) { ExpPtr b (parseMultiplyDivide()); a = new SubtractionOp (location, a, b); } + else break; + } + + return a.release(); + } + + Expression* parseShiftOperator() + { + ExpPtr a (parseAdditionSubtraction()); + + for (;;) + { + if (matchIf (TokenTypes::leftShift)) { ExpPtr b (parseExpression()); a = new LeftShiftOp (location, a, b); } + else if (matchIf (TokenTypes::rightShift)) { ExpPtr b (parseExpression()); a = new RightShiftOp (location, a, b); } + else if (matchIf (TokenTypes::rightShiftUnsigned)) { ExpPtr b (parseExpression()); a = new RightShiftUnsignedOp (location, a, b); } + else break; + } + + return a.release(); + } + + Expression* parseComparator() + { + ExpPtr a (parseShiftOperator()); + + for (;;) + { + if (matchIf (TokenTypes::equals)) { ExpPtr b (parseShiftOperator()); a = new EqualsOp (location, a, b); } + else if (matchIf (TokenTypes::notEquals)) { ExpPtr b (parseShiftOperator()); a = new NotEqualsOp (location, a, b); } + else if (matchIf (TokenTypes::typeEquals)) { ExpPtr b (parseShiftOperator()); a = new TypeEqualsOp (location, a, b); } + else if (matchIf (TokenTypes::typeNotEquals)) { ExpPtr b (parseShiftOperator()); a = new TypeNotEqualsOp (location, a, b); } + else if (matchIf (TokenTypes::lessThan)) { ExpPtr b (parseShiftOperator()); a = new LessThanOp (location, a, b); } + else if (matchIf (TokenTypes::lessThanOrEqual)) { ExpPtr b (parseShiftOperator()); a = new LessThanOrEqualOp (location, a, b); } + else if (matchIf (TokenTypes::greaterThan)) { ExpPtr b (parseShiftOperator()); a = new GreaterThanOp (location, a, b); } + else if (matchIf (TokenTypes::greaterThanOrEqual)) { ExpPtr b (parseShiftOperator()); a = new GreaterThanOrEqualOp (location, a, b); } + else break; + } + + return a.release(); + } + + Expression* parseLogicOperator() + { + ExpPtr a (parseComparator()); + + for (;;) + { + if (matchIf (TokenTypes::logicalAnd)) { ExpPtr b (parseComparator()); a = new LogicalAndOp (location, a, b); } + else if (matchIf (TokenTypes::logicalOr)) { ExpPtr b (parseComparator()); a = new LogicalOrOp (location, a, b); } + else if (matchIf (TokenTypes::bitwiseAnd)) { ExpPtr b (parseComparator()); a = new BitwiseAndOp (location, a, b); } + else if (matchIf (TokenTypes::bitwiseOr)) { ExpPtr b (parseComparator()); a = new BitwiseOrOp (location, a, b); } + else if (matchIf (TokenTypes::bitwiseXor)) { ExpPtr b (parseComparator()); a = new BitwiseXorOp (location, a, b); } + else break; + } + + return a.release(); + } + + Expression* parseTerneryOperator (ExpPtr& condition) + { + ScopedPointer e (new ConditionalOp (location)); + e->condition = condition; + e->trueBranch = parseExpression(); + match (TokenTypes::colon); + e->falseBranch = parseExpression(); + return e.release(); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExpressionTreeBuilder) + }; + + //============================================================================== + static var get (Args a, int index) noexcept { return index < a.numArguments ? a.arguments[index] : var(); } + static bool isInt (Args a, int index) noexcept { return get (a, index).isInt() || get (a, index).isInt64(); } + static int getInt (Args a, int index) noexcept { return get (a, index); } + static double getDouble (Args a, int index) noexcept { return get (a, index); } + static String getString (Args a, int index) noexcept { return get (a, index).toString(); } + + //============================================================================== + struct ObjectClass : public DynamicObject + { + ObjectClass() + { + setMethod ("dump", dump); + setMethod ("clone", clone); + } + + static Identifier getClassName() { static const Identifier i ("Object"); return i; } + static var dump (Args a) { DBG (JSON::toString (a.thisObject)); (void) a; return var::undefined(); } + static var clone (Args a) { return a.thisObject.clone(); } + }; + + //============================================================================== + struct ArrayClass : public DynamicObject + { + ArrayClass() + { + setMethod ("contains", contains); + setMethod ("remove", remove); + setMethod ("join", join); + } + + static Identifier getClassName() { static const Identifier i ("Array"); return i; } + + static var contains (Args a) + { + if (const Array* array = a.thisObject.getArray()) + return array->contains (get (a, 0)); + + return false; + } + + static var remove (Args a) + { + if (Array* array = a.thisObject.getArray()) + array->removeAllInstancesOf (get (a, 0)); + + return var::undefined(); + } + + static var join (Args a) + { + StringArray strings; + + if (const Array* array = a.thisObject.getArray()) + for (int i = 0; i < array->size(); ++i) + strings.add (array->getReference(i).toString()); + + return strings.joinIntoString (getString (a, 0)); + } + }; + + //============================================================================== + struct StringClass : public DynamicObject + { + StringClass() + { + setMethod ("substring", substring); + setMethod ("indexOf", indexOf); + setMethod ("charAt", charAt); + setMethod ("charCodeAt", charCodeAt); + setMethod ("fromCharCode", fromCharCode); + setMethod ("split", split); + } + + static Identifier getClassName() { static const Identifier i ("String"); return i; } + + static var fromCharCode (Args a) { return String::charToString (getInt (a, 0)); } + static var substring (Args a) { return a.thisObject.toString().substring (getInt (a, 0), getInt (a, 1)); } + static var indexOf (Args a) { return a.thisObject.toString().indexOf (getString (a, 0)); } + static var charCodeAt (Args a) { return (int) a.thisObject.toString() [getInt (a, 0)]; } + static var charAt (Args a) { int p = getInt (a, 0); return a.thisObject.toString().substring (p, p + 1); } + + static var split (Args a) + { + const String str (a.thisObject.toString()); + const String sep (getString (a, 0)); + StringArray strings; + + if (sep.isNotEmpty()) + strings.addTokens (str, sep.substring (0, 1), ""); + else // special-case for empty separator: split all chars separately + for (String::CharPointerType pos = str.getCharPointer(); ! pos.isEmpty(); ++pos) + strings.add (String::charToString (*pos)); + + var array; + for (int i = 0; i < strings.size(); ++i) + array.append (strings[i]); + + return array; + } + }; + + //============================================================================== + struct MathClass : public DynamicObject + { + MathClass() + { + setMethod ("abs", Math_abs); setMethod ("round", Math_round); + setMethod ("random", Math_random); setMethod ("randInt", Math_randInt); + setMethod ("min", Math_min); setMethod ("max", Math_max); + setMethod ("range", Math_range); setMethod ("sign", Math_sign); + setMethod ("PI", Math_pi); setMethod ("E", Math_e); + setMethod ("toDegrees", Math_toDegrees); setMethod ("toRadians", Math_toRadians); + setMethod ("sin", Math_sin); setMethod ("asin", Math_asin); + setMethod ("sinh", Math_sinh); setMethod ("asinh", Math_asinh); + setMethod ("cos", Math_cos); setMethod ("acos", Math_acos); + setMethod ("cosh", Math_cosh); setMethod ("acosh", Math_acosh); + setMethod ("tan", Math_tan); setMethod ("atan", Math_atan); + setMethod ("tanh", Math_tanh); setMethod ("atanh", Math_atanh); + setMethod ("log", Math_log); setMethod ("log10", Math_log10); + setMethod ("exp", Math_exp); setMethod ("pow", Math_pow); + setMethod ("sqr", Math_sqr); setMethod ("sqrt", Math_sqrt); + setMethod ("ceil", Math_ceil); setMethod ("floor", Math_floor); + } + + static var Math_pi (Args) { return double_Pi; } + static var Math_e (Args) { return exp (1.0); } + static var Math_random (Args) { return Random::getSystemRandom().nextDouble(); } + static var Math_randInt (Args a) { return Random::getSystemRandom().nextInt (Range (getInt (a, 0), getInt (a, 1))); } + static var Math_abs (Args a) { return isInt (a, 0) ? var (std::abs (getInt (a, 0))) : var (std::abs (getDouble (a, 0))); } + static var Math_round (Args a) { return isInt (a, 0) ? var (roundToInt (getInt (a, 0))) : var (roundToInt (getDouble (a, 0))); } + static var Math_sign (Args a) { return isInt (a, 0) ? var (sign (getInt (a, 0))) : var (sign (getDouble (a, 0))); } + static var Math_range (Args a) { return isInt (a, 0) ? var (jlimit (getInt (a, 1), getInt (a, 2), getInt (a, 0))) : var (jlimit (getDouble (a, 1), getDouble (a, 2), getDouble (a, 0))); } + static var Math_min (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmin (getInt (a, 0), getInt (a, 1))) : var (jmin (getDouble (a, 0), getDouble (a, 1))); } + static var Math_max (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmax (getInt (a, 0), getInt (a, 1))) : var (jmax (getDouble (a, 0), getDouble (a, 1))); } + static var Math_toDegrees (Args a) { return (180.0 / double_Pi) * getDouble (a, 0); } + static var Math_toRadians (Args a) { return (double_Pi / 180.0) * getDouble (a, 0); } + static var Math_sin (Args a) { return sin (getDouble (a, 0)); } + static var Math_asin (Args a) { return asin (getDouble (a, 0)); } + static var Math_cos (Args a) { return cos (getDouble (a, 0)); } + static var Math_acos (Args a) { return acos (getDouble (a, 0)); } + static var Math_sinh (Args a) { return sinh (getDouble (a, 0)); } + static var Math_asinh (Args a) { return asinh (getDouble (a, 0)); } + static var Math_cosh (Args a) { return cosh (getDouble (a, 0)); } + static var Math_acosh (Args a) { return acosh (getDouble (a, 0)); } + static var Math_tan (Args a) { return tan (getDouble (a, 0)); } + static var Math_tanh (Args a) { return tanh (getDouble (a, 0)); } + static var Math_atan (Args a) { return atan (getDouble (a, 0)); } + static var Math_atanh (Args a) { return atanh (getDouble (a, 0)); } + static var Math_log (Args a) { return log (getDouble (a, 0)); } + static var Math_log10 (Args a) { return log10 (getDouble (a, 0)); } + static var Math_exp (Args a) { return exp (getDouble (a, 0)); } + static var Math_pow (Args a) { return pow (getDouble (a, 0), getDouble (a, 1)); } + static var Math_sqr (Args a) { double x = getDouble (a, 0); return x * x; } + static var Math_sqrt (Args a) { return std::sqrt (getDouble (a, 0)); } + static var Math_ceil (Args a) { return std::ceil (getDouble (a, 0)); } + static var Math_floor (Args a) { return std::floor (getDouble (a, 0)); } + + static Identifier getClassName() { static const Identifier i ("Math"); return i; } + template static Type sign (Type n) noexcept { return n > 0 ? (Type) 1 : (n < 0 ? (Type) -1 : 0); } + }; + + //============================================================================== + struct JSONClass : public DynamicObject + { + JSONClass() { setMethod ("stringify", stringify); } + static Identifier getClassName() { static const Identifier i ("JSON"); return i; } + static var stringify (Args a) { return JSON::toString (get (a, 0)); } + }; + + //============================================================================== + struct IntegerClass : public DynamicObject + { + IntegerClass() { setMethod ("parseInt", parseInt); } + static Identifier getClassName() { static const Identifier i ("Integer"); return i; } + + static var parseInt (Args a) + { + const String s (getString (a, 0).trim()); + + return s[0] == '0' ? (s[1] == 'x' ? s.substring(2).getHexValue64() : getOctalValue (s)) + : s.getLargeIntValue(); + } + }; + + //============================================================================== + static var trace (Args a) { Logger::outputDebugString (JSON::toString (a.thisObject)); return var::undefined(); } + static var charToInt (Args a) { return (int) (getString (a, 0)[0]); } + + static var exec (Args a) + { + if (RootObject* root = dynamic_cast (a.thisObject.getObject())) + root->execute (getString (a, 0)); + + return var::undefined(); + } + + static var eval (Args a) + { + if (RootObject* root = dynamic_cast (a.thisObject.getObject())) + return root->evaluate (getString (a, 0)); + + return var::undefined(); + } +}; + +//============================================================================== +JavascriptEngine::JavascriptEngine() : maximumExecutionTime (15.0), root (new RootObject()) +{ + registerNativeObject (RootObject::ObjectClass ::getClassName(), new RootObject::ObjectClass()); + registerNativeObject (RootObject::ArrayClass ::getClassName(), new RootObject::ArrayClass()); + registerNativeObject (RootObject::StringClass ::getClassName(), new RootObject::StringClass()); + registerNativeObject (RootObject::MathClass ::getClassName(), new RootObject::MathClass()); + registerNativeObject (RootObject::JSONClass ::getClassName(), new RootObject::JSONClass()); + registerNativeObject (RootObject::IntegerClass ::getClassName(), new RootObject::IntegerClass()); +} + +JavascriptEngine::~JavascriptEngine() {} + +void JavascriptEngine::prepareTimeout() const noexcept { root->timeout = Time::getCurrentTime() + maximumExecutionTime; } + +void JavascriptEngine::registerNativeObject (Identifier name, DynamicObject* object) +{ + root->setProperty (name, object); +} + +Result JavascriptEngine::execute (const String& code) +{ + try + { + prepareTimeout(); + root->execute (code); + } + catch (String& error) + { + return Result::fail (error); + } + + return Result::ok(); +} + +var JavascriptEngine::evaluate (const String& code, Result* result) +{ + try + { + prepareTimeout(); + if (result != nullptr) *result = Result::ok(); + return root->evaluate (code); + } + catch (String& error) + { + if (result != nullptr) *result = Result::fail (error); + } + + return var::undefined(); +} + +var JavascriptEngine::callFunction (Identifier function, const var::NativeFunctionArgs& args, Result* result) +{ + var returnVal (var::undefined()); + + try + { + prepareTimeout(); + if (result != nullptr) *result = Result::ok(); + RootObject::Scope (nullptr, root, root).findAndInvokeMethod (function, args, returnVal); + } + catch (String& error) + { + if (result != nullptr) *result = Result::fail (error); + } + + return returnVal; +} + +const NamedValueSet& JavascriptEngine::getRootObjectProperties() const noexcept +{ + return root->getProperties(); +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h new file mode 100644 index 0000000..eafc3cd --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.h @@ -0,0 +1,108 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +/** + A simple javascript interpreter! + + It's not fully standards-compliant, and won't be as fast as the fancy JIT-compiled + engines that you get in browsers, but this is an extremely compact, low-overhead javascript + interpreter, which is integrated with the juce var and DynamicObject classes. If you need + a few simple bits of scripting in your app, and want to be able to easily let the JS + work with native objects defined as DynamicObject subclasses, then this might do the job. + + To use, simply create an instance of this class and call execute() to run your code. + Variables that the script sets can be retrieved with evaluate(), and if you need to provide + native objects for the script to use, you can add them with registerNativeObject(). + + One caveat: Because the values and objects that the engine works with are DynamicObject + and var objects, they use reference-counting rather than garbage-collection, so if your + script creates complex connections between objects, you run the risk of creating cyclic + dependencies and hence leaking. +*/ +class JavascriptEngine +{ +public: + /** Creates an instance of the engine. + This creates a root namespace and defines some basic Object, String, Array + and Math library methods. + */ + JavascriptEngine(); + + /** Destructor. */ + ~JavascriptEngine(); + + /** Attempts to parse and run a block of javascript code. + If there's a parse or execution error, the error description is returned in + the result. + You can specify a maximum time for which the program is allowed to run, and + it'll return with an error message if this time is exceeded. + */ + Result execute (const String& javascriptCode); + + /** Attempts to parse and run a javascript expression, and returns the result. + If there's a syntax error, or the expression can't be evaluated, the return value + will be var::undefined(). The errorMessage parameter gives you a way to find out + any parsing errors. + You can specify a maximum time for which the program is allowed to run, and + it'll return with an error message if this time is exceeded. + */ + var evaluate (const String& javascriptCode, + Result* errorMessage = nullptr); + + /** Calls a function in the root namespace, and returns the result. + The function arguments are passed in the same format as used by native + methods in the var class. + */ + var callFunction (Identifier function, + const var::NativeFunctionArgs& args, + Result* errorMessage = nullptr); + + /** Adds a native object to the root namespace. + The object passed-in is reference-counted, and will be retained by the + engine until the engine is deleted. The name must be a simple JS identifier, + without any dots. + */ + void registerNativeObject (Identifier objectName, DynamicObject* object); + + /** This value indicates how long a call to one of the evaluate methods is permitted + to run before timing-out and failing. + The default value is a number of seconds, but you can change this to whatever value + suits your application. + */ + RelativeTime maximumExecutionTime; + + /** Provides access to the set of properties of the root namespace object. */ + const NamedValueSet& getRootObjectProperties() const noexcept; + +private: + JUCE_PUBLIC_IN_DLL_BUILD (struct RootObject) + const ReferenceCountedObjectPtr root; + void prepareTimeout() const noexcept; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavascriptEngine) +}; diff --git a/JuceLibraryCode/modules/juce_core/juce_core.cpp b/JuceLibraryCode/modules/juce_core/juce_core.cpp new file mode 100644 index 0000000..7cfc664 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp @@ -0,0 +1,226 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if defined (JUCE_CORE_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +// Your project must contain an AppConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "AppConfig.h" + +//============================================================================== +#include "native/juce_BasicNativeHeaders.h" +#include "juce_core.h" + +#include +#include +#include + +#if ! JUCE_ANDROID + #include +#endif + +#if JUCE_WINDOWS + #include + + #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 + #include + #include + + #if ! JUCE_MINGW + #include + + #if ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment (lib, "DbgHelp.lib") + #endif + #endif + + #if JUCE_MINGW + #include + #endif + +#else + #if JUCE_LINUX || JUCE_ANDROID + #include + #include + #include + #include + #include + #endif + + #if JUCE_LINUX + #include + #include + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + + #if ! JUCE_ANDROID + #include + #endif +#endif + +#if JUCE_MAC || JUCE_IOS + #include + #include +#endif + +#if JUCE_ANDROID + #include +#endif + + +//============================================================================== +namespace juce +{ + +#include "containers/juce_AbstractFifo.cpp" +#include "containers/juce_NamedValueSet.cpp" +#include "containers/juce_PropertySet.cpp" +#include "containers/juce_Variant.cpp" +#include "files/juce_DirectoryIterator.cpp" +#include "files/juce_File.cpp" +#include "files/juce_FileInputStream.cpp" +#include "files/juce_FileOutputStream.cpp" +#include "files/juce_FileSearchPath.cpp" +#include "files/juce_TemporaryFile.cpp" +#include "javascript/juce_JSON.cpp" +#include "javascript/juce_Javascript.cpp" +#include "containers/juce_DynamicObject.cpp" +#include "logging/juce_FileLogger.cpp" +#include "logging/juce_Logger.cpp" +#include "maths/juce_BigInteger.cpp" +#include "maths/juce_Expression.cpp" +#include "maths/juce_Random.cpp" +#include "memory/juce_MemoryBlock.cpp" +#include "misc/juce_Result.cpp" +#include "misc/juce_Uuid.cpp" +#include "network/juce_MACAddress.cpp" +#include "network/juce_NamedPipe.cpp" +#include "network/juce_Socket.cpp" +#include "network/juce_IPAddress.cpp" +#include "streams/juce_BufferedInputStream.cpp" +#include "streams/juce_FileInputSource.cpp" +#include "streams/juce_InputStream.cpp" +#include "streams/juce_MemoryInputStream.cpp" +#include "streams/juce_MemoryOutputStream.cpp" +#include "streams/juce_SubregionStream.cpp" +#include "system/juce_SystemStats.cpp" +#include "text/juce_CharacterFunctions.cpp" +#include "text/juce_Identifier.cpp" +#include "text/juce_LocalisedStrings.cpp" +#include "text/juce_String.cpp" +#include "streams/juce_OutputStream.cpp" +#include "text/juce_StringArray.cpp" +#include "text/juce_StringPairArray.cpp" +#include "text/juce_StringPool.cpp" +#include "text/juce_TextDiff.cpp" +#include "threads/juce_ReadWriteLock.cpp" +#include "threads/juce_Thread.cpp" +#include "threads/juce_ThreadPool.cpp" +#include "threads/juce_TimeSliceThread.cpp" +#include "time/juce_PerformanceCounter.cpp" +#include "time/juce_RelativeTime.cpp" +#include "time/juce_Time.cpp" +#include "unit_tests/juce_UnitTest.cpp" +#include "xml/juce_XmlDocument.cpp" +#include "xml/juce_XmlElement.cpp" +#include "zip/juce_GZIPDecompressorInputStream.cpp" +#include "zip/juce_GZIPCompressorOutputStream.cpp" +#include "zip/juce_ZipFile.cpp" +#include "files/juce_FileFilter.cpp" +#include "files/juce_WildcardFileFilter.cpp" + +//============================================================================== +#if JUCE_MAC || JUCE_IOS +#include "native/juce_osx_ObjCHelpers.h" +#endif + +#if JUCE_ANDROID +#include "native/juce_android_JNIHelpers.h" +#endif + +#if ! JUCE_WINDOWS +#include "native/juce_posix_SharedCode.h" +#include "native/juce_posix_NamedPipe.cpp" +#endif + +//============================================================================== +#if JUCE_MAC || JUCE_IOS +#include "native/juce_mac_Files.mm" +#include "native/juce_mac_Network.mm" +#include "native/juce_mac_Strings.mm" +#include "native/juce_mac_SystemStats.mm" +#include "native/juce_mac_Threads.mm" + +//============================================================================== +#elif JUCE_WINDOWS +#include "native/juce_win32_ComSmartPtr.h" +#include "native/juce_win32_Files.cpp" +#include "native/juce_win32_Network.cpp" +#include "native/juce_win32_Registry.cpp" +#include "native/juce_win32_SystemStats.cpp" +#include "native/juce_win32_Threads.cpp" + +//============================================================================== +#elif JUCE_LINUX +#include "native/juce_linux_CommonFile.cpp" +#include "native/juce_linux_Files.cpp" +#include "native/juce_linux_Network.cpp" +#include "native/juce_linux_SystemStats.cpp" +#include "native/juce_linux_Threads.cpp" + +//============================================================================== +#elif JUCE_ANDROID +#include "native/juce_linux_CommonFile.cpp" +#include "native/juce_android_Files.cpp" +#include "native/juce_android_Misc.cpp" +#include "native/juce_android_Network.cpp" +#include "native/juce_android_SystemStats.cpp" +#include "native/juce_android_Threads.cpp" + +#endif + +#include "threads/juce_ChildProcess.cpp" +#include "threads/juce_HighResolutionTimer.cpp" +#include "network/juce_URL.cpp" + +} diff --git a/JuceLibraryCode/modules/juce_core/juce_core.h b/JuceLibraryCode/modules/juce_core/juce_core.h new file mode 100644 index 0000000..ce5219e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/juce_core.h @@ -0,0 +1,285 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CORE_H_INCLUDED +#define JUCE_CORE_H_INCLUDED + +#ifndef JUCE_MODULE_AVAILABLE_juce_core + /* If you fail to make sure that all your compile units are building JUCE with the same set of + option flags, then there's a risk that different compile units will treat the classes as having + different memory layouts, leading to very nasty memory corruption errors when they all get + linked together. That's why it's best to always include the Introjucer-generated AppConfig.h + file before any juce headers. + + Note that if you do have an AppConfig.h file and hit this warning, it means that it doesn't + contain the JUCE_MODULE_AVAILABLE_xxx flags, which are necessary for some inter-module + functionality to work correctly. In that case, you should either rebuild your AppConfig.h with + the latest introjucer, or fix it manually to contain these flags. + */ + #ifdef _MSC_VER + #pragma message ("Have you included your AppConfig.h file before including the JUCE headers?") + #else + #warning "Have you included your AppConfig.h file before including the JUCE headers?" + #endif +#endif + +//============================================================================== +#include "system/juce_TargetPlatform.h" + +//============================================================================= +/** Config: JUCE_FORCE_DEBUG + + Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, + but if you define this value, you can override this to force it to be true or false. +*/ +#ifndef JUCE_FORCE_DEBUG + //#define JUCE_FORCE_DEBUG 0 +#endif + +//============================================================================= +/** Config: JUCE_LOG_ASSERTIONS + + If this flag is enabled, the jassert and jassertfalse macros will always use Logger::writeToLog() + to write a message when an assertion happens. + + Enabling it will also leave this turned on in release builds. When it's disabled, + however, the jassert and jassertfalse macros will not be compiled in a + release build. + + @see jassert, jassertfalse, Logger +*/ +#ifndef JUCE_LOG_ASSERTIONS + #if JUCE_ANDROID + #define JUCE_LOG_ASSERTIONS 1 + #else + #define JUCE_LOG_ASSERTIONS 0 + #endif +#endif + +//============================================================================= +/** Config: JUCE_CHECK_MEMORY_LEAKS + + Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector + class and the JUCE_LEAK_DETECTOR macro for more details about enabling leak checking for specific classes. +*/ +#if JUCE_DEBUG && ! defined (JUCE_CHECK_MEMORY_LEAKS) + #define JUCE_CHECK_MEMORY_LEAKS 1 +#endif + +//============================================================================= +/** Config: JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + + In a Visual C++ build, this can be used to stop the required system libs being + automatically added to the link stage. +*/ +#ifndef JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 +#endif + +/** Config: JUCE_INCLUDE_ZLIB_CODE + This can be used to disable Juce's embedded 3rd-party zlib code. + You might need to tweak this if you're linking to an external zlib library in your app, + but for normal apps, this option should be left alone. + + If you disable this, you might also want to set a value for JUCE_ZLIB_INCLUDE_PATH, to + specify the path where your zlib headers live. +*/ +#ifndef JUCE_INCLUDE_ZLIB_CODE + #define JUCE_INCLUDE_ZLIB_CODE 1 +#endif + +#ifndef JUCE_ZLIB_INCLUDE_PATH + #define JUCE_ZLIB_INCLUDE_PATH +#endif + +/* Config: JUCE_CATCH_UNHANDLED_EXCEPTIONS + If enabled, this will add some exception-catching code to forward unhandled exceptions + to your JUCEApplicationBase::unhandledException() callback. +*/ +#ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS + //#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 1 +#endif + +#ifndef JUCE_STRING_UTF_TYPE + #define JUCE_STRING_UTF_TYPE 8 +#endif + +//============================================================================= +//============================================================================= +#if JUCE_MSVC + #pragma warning (disable: 4251) // (DLL build warning, must be disabled before pushing the warning state) + #pragma warning (push) + #pragma warning (disable: 4786) // (long class name warning) + #ifdef __INTEL_COMPILER + #pragma warning (disable: 1125) + #endif +#endif + +#include "system/juce_StandardHeader.h" + +namespace juce +{ + +class StringRef; +class MemoryBlock; +class File; +class InputStream; +class OutputStream; +class DynamicObject; +class FileInputStream; +class FileOutputStream; +class XmlElement; +class JSONFormatter; + +extern JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger(); +extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noexcept; + +#include "memory/juce_Memory.h" +#include "maths/juce_MathsFunctions.h" +#include "memory/juce_ByteOrder.h" +#include "memory/juce_Atomic.h" +#include "text/juce_CharacterFunctions.h" + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +#include "text/juce_CharPointer_UTF8.h" +#include "text/juce_CharPointer_UTF16.h" +#include "text/juce_CharPointer_UTF32.h" +#include "text/juce_CharPointer_ASCII.h" + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#include "text/juce_String.h" +#include "text/juce_StringRef.h" +#include "logging/juce_Logger.h" +#include "memory/juce_LeakedObjectDetector.h" +#include "memory/juce_ContainerDeletePolicy.h" +#include "memory/juce_HeapBlock.h" +#include "memory/juce_MemoryBlock.h" +#include "memory/juce_ReferenceCountedObject.h" +#include "memory/juce_ScopedPointer.h" +#include "memory/juce_OptionalScopedPointer.h" +#include "memory/juce_Singleton.h" +#include "memory/juce_WeakReference.h" +#include "threads/juce_ScopedLock.h" +#include "threads/juce_CriticalSection.h" +#include "maths/juce_Range.h" +#include "maths/juce_NormalisableRange.h" +#include "containers/juce_ElementComparator.h" +#include "containers/juce_ArrayAllocationBase.h" +#include "containers/juce_Array.h" +#include "containers/juce_LinkedListPointer.h" +#include "containers/juce_OwnedArray.h" +#include "containers/juce_ReferenceCountedArray.h" +#include "containers/juce_ScopedValueSetter.h" +#include "containers/juce_SortedSet.h" +#include "containers/juce_SparseSet.h" +#include "containers/juce_AbstractFifo.h" +#include "text/juce_NewLine.h" +#include "text/juce_StringPool.h" +#include "text/juce_Identifier.h" +#include "text/juce_StringArray.h" +#include "text/juce_StringPairArray.h" +#include "text/juce_TextDiff.h" +#include "text/juce_LocalisedStrings.h" +#include "misc/juce_Result.h" +#include "containers/juce_Variant.h" +#include "containers/juce_NamedValueSet.h" +#include "containers/juce_DynamicObject.h" +#include "containers/juce_HashMap.h" +#include "time/juce_RelativeTime.h" +#include "time/juce_Time.h" +#include "streams/juce_InputStream.h" +#include "streams/juce_OutputStream.h" +#include "streams/juce_BufferedInputStream.h" +#include "streams/juce_MemoryInputStream.h" +#include "streams/juce_MemoryOutputStream.h" +#include "streams/juce_SubregionStream.h" +#include "streams/juce_InputSource.h" +#include "files/juce_File.h" +#include "files/juce_DirectoryIterator.h" +#include "files/juce_FileInputStream.h" +#include "files/juce_FileOutputStream.h" +#include "files/juce_FileSearchPath.h" +#include "files/juce_MemoryMappedFile.h" +#include "files/juce_TemporaryFile.h" +#include "files/juce_FileFilter.h" +#include "files/juce_WildcardFileFilter.h" +#include "streams/juce_FileInputSource.h" +#include "logging/juce_FileLogger.h" +#include "javascript/juce_JSON.h" +#include "javascript/juce_Javascript.h" +#include "maths/juce_BigInteger.h" +#include "maths/juce_Expression.h" +#include "maths/juce_Random.h" +#include "misc/juce_Uuid.h" +#include "misc/juce_WindowsRegistry.h" +#include "system/juce_PlatformDefs.h" +#include "system/juce_CompilerSupport.h" +#include "system/juce_SystemStats.h" +#include "threads/juce_ChildProcess.h" +#include "threads/juce_DynamicLibrary.h" +#include "threads/juce_HighResolutionTimer.h" +#include "threads/juce_InterProcessLock.h" +#include "threads/juce_Process.h" +#include "threads/juce_SpinLock.h" +#include "threads/juce_WaitableEvent.h" +#include "threads/juce_Thread.h" +#include "threads/juce_ThreadLocalValue.h" +#include "threads/juce_ThreadPool.h" +#include "threads/juce_TimeSliceThread.h" +#include "threads/juce_ReadWriteLock.h" +#include "threads/juce_ScopedReadLock.h" +#include "threads/juce_ScopedWriteLock.h" +#include "network/juce_IPAddress.h" +#include "network/juce_MACAddress.h" +#include "network/juce_NamedPipe.h" +#include "network/juce_Socket.h" +#include "network/juce_URL.h" +#include "time/juce_PerformanceCounter.h" +#include "unit_tests/juce_UnitTest.h" +#include "xml/juce_XmlDocument.h" +#include "xml/juce_XmlElement.h" +#include "zip/juce_GZIPCompressorOutputStream.h" +#include "zip/juce_GZIPDecompressorInputStream.h" +#include "zip/juce_ZipFile.h" +#include "containers/juce_PropertySet.h" +#include "memory/juce_SharedResourcePointer.h" + +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_CORE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/juce_core.mm b/JuceLibraryCode/modules/juce_core/juce_core.mm new file mode 100644 index 0000000..90a2f7c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/juce_core.mm @@ -0,0 +1,29 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#include "juce_core.cpp" diff --git a/JuceLibraryCode/modules/juce_core/juce_module_info b/JuceLibraryCode/modules/juce_core/juce_module_info new file mode 100644 index 0000000..33eab6c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/juce_module_info @@ -0,0 +1,38 @@ +{ + "id": "juce_core", + "name": "JUCE core classes", + "version": "3.1.1", + "description": "The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality.", + "website": "http://www.juce.com/juce", + "license": "ISC Permissive", + + "dependencies": [], + + "include": "juce_core.h", + + "compile": [ { "file": "juce_core.cpp", "target": "! xcode" }, + { "file": "juce_core.mm", "target": "xcode" } ], + + "browse": [ "text/*", + "maths/*", + "memory/*", + "containers/*", + "threads/*", + "time/*", + "files/*", + "network/*", + "streams/*", + "logging/*", + "system/*", + "xml/*", + "javascript/*", + "zip/*", + "unit_tests/*", + "misc/*", + "native/*" ], + + "OSXFrameworks": "Cocoa IOKit", + "iOSFrameworks": "Foundation", + "LinuxLibs": "rt dl pthread", + "mingwLibs": "uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm" +} diff --git a/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.cpp b/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.cpp new file mode 100644 index 0000000..1b8f302 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.cpp @@ -0,0 +1,134 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +FileLogger::FileLogger (const File& file, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes) + : logFile (file) +{ + if (maxInitialFileSizeBytes >= 0) + trimFileSize (logFile, maxInitialFileSizeBytes); + + if (! file.exists()) + file.create(); // (to create the parent directories) + + String welcome; + welcome << newLine + << "**********************************************************" << newLine + << welcomeMessage << newLine + << "Log started: " << Time::getCurrentTime().toString (true, true) << newLine; + + FileLogger::logMessage (welcome); +} + +FileLogger::~FileLogger() {} + +//============================================================================== +void FileLogger::logMessage (const String& message) +{ + const ScopedLock sl (logLock); + DBG (message); + FileOutputStream out (logFile, 256); + out << message << newLine; +} + +void FileLogger::trimFileSize (const File& file, int64 maxFileSizeBytes) +{ + if (maxFileSizeBytes <= 0) + { + file.deleteFile(); + } + else + { + const int64 fileSize = file.getSize(); + + if (fileSize > maxFileSizeBytes) + { + TemporaryFile tempFile (file); + + { + FileOutputStream out (tempFile.getFile()); + FileInputStream in (file); + + if (! (out.openedOk() && in.openedOk())) + return; + + in.setPosition (fileSize - maxFileSizeBytes); + + for (;;) + { + const char c = in.readByte(); + if (c == 0) + return; + + if (c == '\n' || c == '\r') + { + out << c; + break; + } + } + + out.writeFromInputStream (in, -1); + } + + tempFile.overwriteTargetFileWithTemporary(); + } + } +} + +//============================================================================== +File FileLogger::getSystemLogFileFolder() +{ + #if JUCE_MAC + return File ("~/Library/Logs"); + #else + return File::getSpecialLocation (File::userApplicationDataDirectory); + #endif +} + +FileLogger* FileLogger::createDefaultAppLogger (const String& logFileSubDirectoryName, + const String& logFileName, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes) +{ + return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) + .getChildFile (logFileName), + welcomeMessage, maxInitialFileSizeBytes); +} + +FileLogger* FileLogger::createDateStampedLogger (const String& logFileSubDirectoryName, + const String& logFileNameRoot, + const String& logFileNameSuffix, + const String& welcomeMessage) +{ + return new FileLogger (getSystemLogFileFolder().getChildFile (logFileSubDirectoryName) + .getChildFile (logFileNameRoot + Time::getCurrentTime().formatted ("%Y-%m-%d_%H-%M-%S")) + .withFileExtension (logFileNameSuffix) + .getNonexistentSibling(), + welcomeMessage, 0); +} diff --git a/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.h b/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.h new file mode 100644 index 0000000..09301b8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/logging/juce_FileLogger.h @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILELOGGER_H_INCLUDED +#define JUCE_FILELOGGER_H_INCLUDED + + +//============================================================================== +/** + A simple implementation of a Logger that writes to a file. + + @see Logger +*/ +class JUCE_API FileLogger : public Logger +{ +public: + //============================================================================== + /** Creates a FileLogger for a given file. + + @param fileToWriteTo the file that to use - new messages will be appended + to the file. If the file doesn't exist, it will be created, + along with any parent directories that are needed. + @param welcomeMessage when opened, the logger will write a header to the log, along + with the current date and time, and this welcome message + @param maxInitialFileSizeBytes if this is zero or greater, then if the file already exists + but is larger than this number of bytes, then the start of the + file will be truncated to keep the size down. This prevents a log + file getting ridiculously large over time. The file will be truncated + at a new-line boundary. If this value is less than zero, no size limit + will be imposed; if it's zero, the file will always be deleted. Note that + the size is only checked once when this object is created - any logging + that is done later will be appended without any checking + */ + FileLogger (const File& fileToWriteTo, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes = 128 * 1024); + + /** Destructor. */ + ~FileLogger(); + + //============================================================================== + /** Returns the file that this logger is writing to. */ + const File& getLogFile() const noexcept { return logFile; } + + //============================================================================== + /** Helper function to create a log file in the correct place for this platform. + + The method might return nullptr if the file can't be created for some reason. + + @param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as + returned by getSystemLogFileFolder). It's best to use something + like the name of your application here. + @param logFileName the name of the file to create, e.g. "MyAppLog.txt". + @param welcomeMessage a message that will be written to the log when it's opened. + @param maxInitialFileSizeBytes (see the FileLogger constructor for more info on this) + */ + static FileLogger* createDefaultAppLogger (const String& logFileSubDirectoryName, + const String& logFileName, + const String& welcomeMessage, + const int64 maxInitialFileSizeBytes = 128 * 1024); + + /** Helper function to create a log file in the correct place for this platform. + + The filename used is based on the root and suffix strings provided, along with a + time and date string, meaning that a new, empty log file will be always be created + rather than appending to an exising one. + + The method might return nullptr if the file can't be created for some reason. + + @param logFileSubDirectoryName the name of the subdirectory to create inside the logs folder (as + returned by getSystemLogFileFolder). It's best to use something + like the name of your application here. + @param logFileNameRoot the start of the filename to use, e.g. "MyAppLog_". This will have + a timestamp and the logFileNameSuffix appended to it + @param logFileNameSuffix the file suffix to use, e.g. ".txt" + @param welcomeMessage a message that will be written to the log when it's opened. + */ + static FileLogger* createDateStampedLogger (const String& logFileSubDirectoryName, + const String& logFileNameRoot, + const String& logFileNameSuffix, + const String& welcomeMessage); + + //============================================================================== + /** Returns an OS-specific folder where log-files should be stored. + + On Windows this will return a logger with a path such as: + c:\\Documents and Settings\\username\\Application Data\\[logFileSubDirectoryName]\\[logFileName] + + On the Mac it'll create something like: + ~/Library/Logs/[logFileSubDirectoryName]/[logFileName] + + @see createDefaultAppLogger + */ + static File getSystemLogFileFolder(); + + // (implementation of the Logger virtual method) + void logMessage (const String&); + + //============================================================================== + /** This is a utility function which removes lines from the start of a text + file to make sure that its total size is below the given size. + */ + static void trimFileSize (const File& file, int64 maxFileSize); + +private: + //============================================================================== + File logFile; + CriticalSection logLock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileLogger) +}; + + +#endif // JUCE_FILELOGGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/logging/juce_Logger.cpp b/JuceLibraryCode/modules/juce_core/logging/juce_Logger.cpp new file mode 100644 index 0000000..0b1c8d6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/logging/juce_Logger.cpp @@ -0,0 +1,63 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Logger::Logger() {} + +Logger::~Logger() +{ + // You're deleting this logger while it's still being used! + // Always call Logger::setCurrentLogger (nullptr) before deleting the active logger. + jassert (currentLogger != this); +} + +Logger* Logger::currentLogger = nullptr; + +void Logger::setCurrentLogger (Logger* const newLogger) noexcept { currentLogger = newLogger; } +Logger* Logger::getCurrentLogger() noexcept { return currentLogger; } + +void Logger::writeToLog (const String& message) +{ + if (currentLogger != nullptr) + currentLogger->logMessage (message); + else + outputDebugString (message); +} + +#if JUCE_LOG_ASSERTIONS || JUCE_DEBUG +void JUCE_API JUCE_CALLTYPE logAssertion (const char* const filename, const int lineNum) noexcept +{ + String m ("JUCE Assertion failure in "); + m << File::createFileWithoutCheckingPath (filename).getFileName() << ':' << lineNum; + + #if JUCE_LOG_ASSERTIONS + Logger::writeToLog (m); + #else + DBG (m); + #endif +} +#endif diff --git a/JuceLibraryCode/modules/juce_core/logging/juce_Logger.h b/JuceLibraryCode/modules/juce_core/logging/juce_Logger.h new file mode 100644 index 0000000..d0d3af5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/logging/juce_Logger.h @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LOGGER_H_INCLUDED +#define JUCE_LOGGER_H_INCLUDED + + +//============================================================================== +/** + Acts as an application-wide logging class. + + A subclass of Logger can be created and passed into the Logger::setCurrentLogger + method and this will then be used by all calls to writeToLog. + + The logger class also contains methods for writing messages to the debugger's + output stream. + + @see FileLogger +*/ +class JUCE_API Logger +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~Logger(); + + //============================================================================== + /** Sets the current logging class to use. + + Note that the object passed in will not be owned or deleted by the logger, so + the caller must make sure that it is not deleted while still being used. + A null pointer can be passed-in to disable any logging. + */ + static void JUCE_CALLTYPE setCurrentLogger (Logger* newLogger) noexcept; + + /** Returns the current logger, or nullptr if none has been set. */ + static Logger* getCurrentLogger() noexcept; + + /** Writes a string to the current logger. + + This will pass the string to the logger's logMessage() method if a logger + has been set. + + @see logMessage + */ + static void JUCE_CALLTYPE writeToLog (const String& message); + + + //============================================================================== + /** Writes a message to the standard error stream. + + This can be called directly, or by using the DBG() macro in + juce_PlatformDefs.h (which will avoid calling the method in non-debug builds). + */ + static void JUCE_CALLTYPE outputDebugString (const String& text); + + +protected: + //============================================================================== + Logger(); + + /** This is overloaded by subclasses to implement custom logging behaviour. + @see setCurrentLogger + */ + virtual void logMessage (const String& message) = 0; + +private: + static Logger* currentLogger; +}; + + +#endif // JUCE_LOGGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp new file mode 100644 index 0000000..dd09616 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.cpp @@ -0,0 +1,1014 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +namespace +{ + inline size_t bitToIndex (const int bit) noexcept { return (size_t) (bit >> 5); } + inline uint32 bitToMask (const int bit) noexcept { return (uint32) 1 << (bit & 31); } +} + +//============================================================================== +BigInteger::BigInteger() + : numValues (4), + highestBit (-1), + negative (false) +{ + values.calloc (numValues + 1); +} + +BigInteger::BigInteger (const int32 value) + : numValues (4), + highestBit (31), + negative (value < 0) +{ + values.calloc (numValues + 1); + values[0] = (uint32) abs (value); + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (const uint32 value) + : numValues (4), + highestBit (31), + negative (false) +{ + values.calloc (numValues + 1); + values[0] = value; + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (int64 value) + : numValues (4), + highestBit (63), + negative (value < 0) +{ + values.calloc (numValues + 1); + + if (value < 0) + value = -value; + + values[0] = (uint32) value; + values[1] = (uint32) (value >> 32); + highestBit = getHighestBit(); +} + +BigInteger::BigInteger (const BigInteger& other) + : numValues ((size_t) jmax ((size_t) 4, bitToIndex (other.highestBit) + 1)), + highestBit (other.getHighestBit()), + negative (other.negative) +{ + values.malloc (numValues + 1); + memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +BigInteger::BigInteger (BigInteger&& other) noexcept + : values (static_cast &&> (other.values)), + numValues (other.numValues), + highestBit (other.highestBit), + negative (other.negative) +{ +} + +BigInteger& BigInteger::operator= (BigInteger&& other) noexcept +{ + values = static_cast &&> (other.values); + numValues = other.numValues; + highestBit = other.highestBit; + negative = other.negative; + return *this; +} +#endif + +BigInteger::~BigInteger() +{ +} + +void BigInteger::swapWith (BigInteger& other) noexcept +{ + values.swapWith (other.values); + std::swap (numValues, other.numValues); + std::swap (highestBit, other.highestBit); + std::swap (negative, other.negative); +} + +BigInteger& BigInteger::operator= (const BigInteger& other) +{ + if (this != &other) + { + highestBit = other.getHighestBit(); + jassert (other.numValues >= 4); + numValues = (size_t) jmax ((size_t) 4, bitToIndex (highestBit) + 1); + negative = other.negative; + values.malloc (numValues + 1); + memcpy (values, other.values, sizeof (uint32) * (numValues + 1)); + } + + return *this; +} + +void BigInteger::ensureSize (const size_t numVals) +{ + if (numVals + 2 >= numValues) + { + size_t oldSize = numValues; + numValues = ((numVals + 2) * 3) / 2; + values.realloc (numValues + 1); + + while (oldSize < numValues) + values [oldSize++] = 0; + } +} + +//============================================================================== +bool BigInteger::operator[] (const int bit) const noexcept +{ + return bit <= highestBit && bit >= 0 + && ((values [bitToIndex (bit)] & bitToMask (bit)) != 0); +} + +int BigInteger::toInteger() const noexcept +{ + const int n = (int) (values[0] & 0x7fffffff); + return negative ? -n : n; +} + +int64 BigInteger::toInt64() const noexcept +{ + const int64 n = (((int64) (values[1] & 0x7fffffff)) << 32) | values[0]; + return negative ? -n : n; +} + +BigInteger BigInteger::getBitRange (int startBit, int numBits) const +{ + BigInteger r; + numBits = jmin (numBits, getHighestBit() + 1 - startBit); + r.ensureSize ((size_t) bitToIndex (numBits)); + r.highestBit = numBits; + + int i = 0; + while (numBits > 0) + { + r.values[i++] = getBitRangeAsInt (startBit, (int) jmin (32, numBits)); + numBits -= 32; + startBit += 32; + } + + r.highestBit = r.getHighestBit(); + return r; +} + +uint32 BigInteger::getBitRangeAsInt (const int startBit, int numBits) const noexcept +{ + if (numBits > 32) + { + jassertfalse; // use getBitRange() if you need more than 32 bits.. + numBits = 32; + } + + numBits = jmin (numBits, highestBit + 1 - startBit); + + if (numBits <= 0) + return 0; + + const size_t pos = bitToIndex (startBit); + const int offset = startBit & 31; + const int endSpace = 32 - numBits; + + uint32 n = ((uint32) values [pos]) >> offset; + + if (offset > endSpace) + n |= ((uint32) values [pos + 1]) << (32 - offset); + + return n & (((uint32) 0xffffffff) >> endSpace); +} + +void BigInteger::setBitRangeAsInt (const int startBit, int numBits, uint32 valueToSet) +{ + if (numBits > 32) + { + jassertfalse; + numBits = 32; + } + + for (int i = 0; i < numBits; ++i) + { + setBit (startBit + i, (valueToSet & 1) != 0); + valueToSet >>= 1; + } +} + +//============================================================================== +void BigInteger::clear() +{ + if (numValues > 16) + { + numValues = 4; + values.calloc (numValues + 1); + } + else + { + values.clear (numValues + 1); + } + + highestBit = -1; + negative = false; +} + +void BigInteger::setBit (const int bit) +{ + if (bit >= 0) + { + if (bit > highestBit) + { + ensureSize (bitToIndex (bit)); + highestBit = bit; + } + + values [bitToIndex (bit)] |= bitToMask (bit); + } +} + +void BigInteger::setBit (const int bit, const bool shouldBeSet) +{ + if (shouldBeSet) + setBit (bit); + else + clearBit (bit); +} + +void BigInteger::clearBit (const int bit) noexcept +{ + if (bit >= 0 && bit <= highestBit) + values [bitToIndex (bit)] &= ~bitToMask (bit); +} + +void BigInteger::setRange (int startBit, int numBits, const bool shouldBeSet) +{ + while (--numBits >= 0) + setBit (startBit++, shouldBeSet); +} + +void BigInteger::insertBit (const int bit, const bool shouldBeSet) +{ + if (bit >= 0) + shiftBits (1, bit); + + setBit (bit, shouldBeSet); +} + +//============================================================================== +bool BigInteger::isZero() const noexcept +{ + return getHighestBit() < 0; +} + +bool BigInteger::isOne() const noexcept +{ + return getHighestBit() == 0 && ! negative; +} + +bool BigInteger::isNegative() const noexcept +{ + return negative && ! isZero(); +} + +void BigInteger::setNegative (const bool neg) noexcept +{ + negative = neg; +} + +void BigInteger::negate() noexcept +{ + negative = (! negative) && ! isZero(); +} + +#if JUCE_USE_MSVC_INTRINSICS && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_BitScanReverse) +#endif + +inline static int highestBitInInt (uint32 n) noexcept +{ + jassert (n != 0); // (the built-in functions may not work for n = 0) + + #if JUCE_GCC + return 31 - __builtin_clz (n); + #elif JUCE_USE_MSVC_INTRINSICS + unsigned long highest; + _BitScanReverse (&highest, n); + return (int) highest; + #else + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return countBitsInInt32 (n >> 1); + #endif +} + +int BigInteger::countNumberOfSetBits() const noexcept +{ + int total = 0; + + for (int i = (int) bitToIndex (highestBit) + 1; --i >= 0;) + total += countNumberOfBits (values[i]); + + return total; +} + +int BigInteger::getHighestBit() const noexcept +{ + for (int i = (int) bitToIndex (highestBit + 1); i >= 0; --i) + { + const uint32 n = values[i]; + + if (n != 0) + return highestBitInInt (n) + (i << 5); + } + + return -1; +} + +int BigInteger::findNextSetBit (int i) const noexcept +{ + for (; i <= highestBit; ++i) + if ((values [bitToIndex (i)] & bitToMask (i)) != 0) + return i; + + return -1; +} + +int BigInteger::findNextClearBit (int i) const noexcept +{ + for (; i <= highestBit; ++i) + if ((values [bitToIndex (i)] & bitToMask (i)) == 0) + break; + + return i; +} + +//============================================================================== +BigInteger& BigInteger::operator+= (const BigInteger& other) +{ + if (other.isNegative()) + return operator-= (-other); + + if (isNegative()) + { + if (compareAbsolute (other) < 0) + { + BigInteger temp (*this); + temp.negate(); + *this = other; + operator-= (temp); + } + else + { + negate(); + operator-= (other); + negate(); + } + } + else + { + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + ++highestBit; + + const size_t numInts = bitToIndex (highestBit) + 1; + ensureSize (numInts); + + int64 remainder = 0; + + for (size_t i = 0; i <= numInts; ++i) + { + if (i < numValues) + remainder += values[i]; + + if (i < other.numValues) + remainder += other.values[i]; + + values[i] = (uint32) remainder; + remainder >>= 32; + } + + jassert (remainder == 0); + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator-= (const BigInteger& other) +{ + if (other.isNegative()) + return operator+= (-other); + + if (! isNegative()) + { + if (compareAbsolute (other) < 0) + { + BigInteger temp (other); + swapWith (temp); + operator-= (temp); + negate(); + return *this; + } + } + else + { + negate(); + operator+= (other); + negate(); + return *this; + } + + const size_t numInts = bitToIndex (highestBit) + 1; + const size_t maxOtherInts = bitToIndex (other.highestBit) + 1; + int64 amountToSubtract = 0; + + for (size_t i = 0; i <= numInts; ++i) + { + if (i <= maxOtherInts) + amountToSubtract += (int64) other.values[i]; + + if (values[i] >= amountToSubtract) + { + values[i] = (uint32) (values[i] - amountToSubtract); + amountToSubtract = 0; + } + else + { + const int64 n = ((int64) values[i] + (((int64) 1) << 32)) - amountToSubtract; + values[i] = (uint32) n; + amountToSubtract = 1; + } + } + + return *this; +} + +BigInteger& BigInteger::operator*= (const BigInteger& other) +{ + BigInteger total; + highestBit = getHighestBit(); + const bool wasNegative = isNegative(); + setNegative (false); + + for (int i = 0; i <= highestBit; ++i) + { + if (operator[](i)) + { + BigInteger n (other); + n.setNegative (false); + n <<= i; + total += n; + } + } + + total.setNegative (wasNegative ^ other.isNegative()); + swapWith (total); + return *this; +} + +void BigInteger::divideBy (const BigInteger& divisor, BigInteger& remainder) +{ + jassert (this != &remainder); // (can't handle passing itself in to get the remainder) + + const int divHB = divisor.getHighestBit(); + const int ourHB = getHighestBit(); + + if (divHB < 0 || ourHB < 0) + { + // division by zero + remainder.clear(); + clear(); + } + else + { + const bool wasNegative = isNegative(); + + swapWith (remainder); + remainder.setNegative (false); + clear(); + + BigInteger temp (divisor); + temp.setNegative (false); + + int leftShift = ourHB - divHB; + temp <<= leftShift; + + while (leftShift >= 0) + { + if (remainder.compareAbsolute (temp) >= 0) + { + remainder -= temp; + setBit (leftShift); + } + + if (--leftShift >= 0) + temp >>= 1; + } + + negative = wasNegative ^ divisor.isNegative(); + remainder.setNegative (wasNegative); + } +} + +BigInteger& BigInteger::operator/= (const BigInteger& other) +{ + BigInteger remainder; + divideBy (other, remainder); + return *this; +} + +BigInteger& BigInteger::operator|= (const BigInteger& other) +{ + // this operation doesn't take into account negative values.. + jassert (isNegative() == other.isNegative()); + + if (other.highestBit >= 0) + { + ensureSize (bitToIndex (other.highestBit)); + + int n = (int) bitToIndex (other.highestBit) + 1; + + while (--n >= 0) + values[n] |= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator&= (const BigInteger& other) +{ + // this operation doesn't take into account negative values.. + jassert (isNegative() == other.isNegative()); + + int n = (int) numValues; + + while (n > (int) other.numValues) + values[--n] = 0; + + while (--n >= 0) + values[n] &= other.values[n]; + + if (other.highestBit < highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + return *this; +} + +BigInteger& BigInteger::operator^= (const BigInteger& other) +{ + // this operation will only work with the absolute values + jassert (isNegative() == other.isNegative()); + + if (other.highestBit >= 0) + { + ensureSize (bitToIndex (other.highestBit)); + + int n = (int) bitToIndex (other.highestBit) + 1; + + while (--n >= 0) + values[n] ^= other.values[n]; + + if (other.highestBit > highestBit) + highestBit = other.highestBit; + + highestBit = getHighestBit(); + } + + return *this; +} + +BigInteger& BigInteger::operator%= (const BigInteger& divisor) +{ + BigInteger remainder; + divideBy (divisor, remainder); + swapWith (remainder); + return *this; +} + +BigInteger& BigInteger::operator++() { return operator+= (1); } +BigInteger& BigInteger::operator--() { return operator-= (1); } +BigInteger BigInteger::operator++ (int) { const BigInteger old (*this); operator+= (1); return old; } +BigInteger BigInteger::operator-- (int) { const BigInteger old (*this); operator-= (1); return old; } + +BigInteger BigInteger::operator-() const { BigInteger b (*this); b.negate(); return b; } +BigInteger BigInteger::operator+ (const BigInteger& other) const { BigInteger b (*this); return b += other; } +BigInteger BigInteger::operator- (const BigInteger& other) const { BigInteger b (*this); return b -= other; } +BigInteger BigInteger::operator* (const BigInteger& other) const { BigInteger b (*this); return b *= other; } +BigInteger BigInteger::operator/ (const BigInteger& other) const { BigInteger b (*this); return b /= other; } +BigInteger BigInteger::operator| (const BigInteger& other) const { BigInteger b (*this); return b |= other; } +BigInteger BigInteger::operator& (const BigInteger& other) const { BigInteger b (*this); return b &= other; } +BigInteger BigInteger::operator^ (const BigInteger& other) const { BigInteger b (*this); return b ^= other; } +BigInteger BigInteger::operator% (const BigInteger& other) const { BigInteger b (*this); return b %= other; } +BigInteger BigInteger::operator<< (const int numBits) const { BigInteger b (*this); return b <<= numBits; } +BigInteger BigInteger::operator>> (const int numBits) const { BigInteger b (*this); return b >>= numBits; } +BigInteger& BigInteger::operator<<= (const int numBits) { shiftBits (numBits, 0); return *this; } +BigInteger& BigInteger::operator>>= (const int numBits) { shiftBits (-numBits, 0); return *this; } + +//============================================================================== +int BigInteger::compare (const BigInteger& other) const noexcept +{ + if (isNegative() == other.isNegative()) + { + const int absComp = compareAbsolute (other); + return isNegative() ? -absComp : absComp; + } + else + { + return isNegative() ? -1 : 1; + } +} + +int BigInteger::compareAbsolute (const BigInteger& other) const noexcept +{ + const int h1 = getHighestBit(); + const int h2 = other.getHighestBit(); + + if (h1 > h2) + return 1; + else if (h1 < h2) + return -1; + + for (int i = (int) bitToIndex (h1) + 1; --i >= 0;) + if (values[i] != other.values[i]) + return (values[i] > other.values[i]) ? 1 : -1; + + return 0; +} + +bool BigInteger::operator== (const BigInteger& other) const noexcept { return compare (other) == 0; } +bool BigInteger::operator!= (const BigInteger& other) const noexcept { return compare (other) != 0; } +bool BigInteger::operator< (const BigInteger& other) const noexcept { return compare (other) < 0; } +bool BigInteger::operator<= (const BigInteger& other) const noexcept { return compare (other) <= 0; } +bool BigInteger::operator> (const BigInteger& other) const noexcept { return compare (other) > 0; } +bool BigInteger::operator>= (const BigInteger& other) const noexcept { return compare (other) >= 0; } + +//============================================================================== +void BigInteger::shiftLeft (int bits, const int startBit) +{ + if (startBit > 0) + { + for (int i = highestBit + 1; --i >= startBit;) + setBit (i + bits, operator[] (i)); + + while (--bits >= 0) + clearBit (bits + startBit); + } + else + { + ensureSize (bitToIndex (highestBit + bits) + 1); + + const size_t wordsToMove = bitToIndex (bits); + size_t top = 1 + bitToIndex (highestBit); + highestBit += bits; + + if (wordsToMove > 0) + { + for (int i = (int) top; --i >= 0;) + values [(size_t) i + wordsToMove] = values [i]; + + for (size_t j = 0; j < wordsToMove; ++j) + values [j] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + for (size_t i = top + 1 + wordsToMove; --i > wordsToMove;) + values[i] = (values[i] << bits) | (values [i - 1] >> invBits); + + values [wordsToMove] = values [wordsToMove] << bits; + } + + highestBit = getHighestBit(); + } +} + +void BigInteger::shiftRight (int bits, const int startBit) +{ + if (startBit > 0) + { + for (int i = startBit; i <= highestBit; ++i) + setBit (i, operator[] (i + bits)); + + highestBit = getHighestBit(); + } + else + { + if (bits > highestBit) + { + clear(); + } + else + { + const size_t wordsToMove = bitToIndex (bits); + size_t top = 1 + bitToIndex (highestBit) - wordsToMove; + highestBit -= bits; + + if (wordsToMove > 0) + { + size_t i; + for (i = 0; i < top; ++i) + values [i] = values [i + wordsToMove]; + + for (i = 0; i < wordsToMove; ++i) + values [top + i] = 0; + + bits &= 31; + } + + if (bits != 0) + { + const int invBits = 32 - bits; + + --top; + for (size_t i = 0; i < top; ++i) + values[i] = (values[i] >> bits) | (values [i + 1] << invBits); + + values[top] = (values[top] >> bits); + } + + highestBit = getHighestBit(); + } + } +} + +void BigInteger::shiftBits (int bits, const int startBit) +{ + if (highestBit >= 0) + { + if (bits < 0) + shiftRight (-bits, startBit); + else if (bits > 0) + shiftLeft (bits, startBit); + } +} + +//============================================================================== +static BigInteger simpleGCD (BigInteger* m, BigInteger* n) +{ + while (! m->isZero()) + { + if (n->compareAbsolute (*m) > 0) + std::swap (m, n); + + *m -= *n; + } + + return *n; +} + +BigInteger BigInteger::findGreatestCommonDivisor (BigInteger n) const +{ + BigInteger m (*this); + + while (! n.isZero()) + { + if (abs (m.getHighestBit() - n.getHighestBit()) <= 16) + return simpleGCD (&m, &n); + + BigInteger temp2; + m.divideBy (n, temp2); + + m.swapWith (n); + n.swapWith (temp2); + } + + return m; +} + +void BigInteger::exponentModulo (const BigInteger& exponent, const BigInteger& modulus) +{ + BigInteger exp (exponent); + exp %= modulus; + + BigInteger value (1); + swapWith (value); + value %= modulus; + + while (! exp.isZero()) + { + if (exp [0]) + { + operator*= (value); + operator%= (modulus); + } + + value *= value; + value %= modulus; + exp >>= 1; + } +} + +void BigInteger::inverseModulo (const BigInteger& modulus) +{ + if (modulus.isOne() || modulus.isNegative()) + { + clear(); + return; + } + + if (isNegative() || compareAbsolute (modulus) >= 0) + operator%= (modulus); + + if (isOne()) + return; + + if (! (*this)[0]) + { + // not invertible + clear(); + return; + } + + BigInteger a1 (modulus); + BigInteger a2 (*this); + BigInteger b1 (modulus); + BigInteger b2 (1); + + while (! a2.isOne()) + { + BigInteger temp1, multiplier (a1); + multiplier.divideBy (a2, temp1); + + temp1 = a2; + temp1 *= multiplier; + BigInteger temp2 (a1); + temp2 -= temp1; + a1 = a2; + a2 = temp2; + + temp1 = b2; + temp1 *= multiplier; + temp2 = b1; + temp2 -= temp1; + b1 = b2; + b2 = temp2; + } + + while (b2.isNegative()) + b2 += modulus; + + b2 %= modulus; + swapWith (b2); +} + +//============================================================================== +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value) +{ + return stream << value.toString (10); +} + +String BigInteger::toString (const int base, const int minimumNumCharacters) const +{ + String s; + BigInteger v (*this); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + static const char hexDigits[] = "0123456789abcdef"; + + for (;;) + { + const uint32 remainder = v.getBitRangeAsInt (0, bits); + + v >>= bits; + + if (remainder == 0 && v.isZero()) + break; + + s = String::charToString ((juce_wchar) (uint8) hexDigits [remainder]) + s; + } + } + else if (base == 10) + { + const BigInteger ten (10); + BigInteger remainder; + + for (;;) + { + v.divideBy (ten, remainder); + + if (remainder.isZero() && v.isZero()) + break; + + s = String (remainder.getBitRangeAsInt (0, 8)) + s; + } + } + else + { + jassertfalse; // can't do the specified base! + return String(); + } + + s = s.paddedLeft ('0', minimumNumCharacters); + + return isNegative() ? "-" + s : s; +} + +void BigInteger::parseString (StringRef text, const int base) +{ + clear(); + String::CharPointerType t (text.text.findEndOfWhitespace()); + + setNegative (*t == (juce_wchar) '-'); + + if (base == 2 || base == 8 || base == 16) + { + const int bits = (base == 2) ? 1 : (base == 8 ? 3 : 4); + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + const int digit = CharacterFunctions::getHexDigitValue (c); + + if (((uint32) digit) < (uint32) base) + { + operator<<= (bits); + operator+= (digit); + } + else if (c == 0) + { + break; + } + } + } + else if (base == 10) + { + const BigInteger ten ((uint32) 10); + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + + if (c >= '0' && c <= '9') + { + operator*= (ten); + operator+= ((int) (c - '0')); + } + else if (c == 0) + { + break; + } + } + } +} + +MemoryBlock BigInteger::toMemoryBlock() const +{ + const int numBytes = (getHighestBit() + 8) >> 3; + MemoryBlock mb ((size_t) numBytes); + + for (int i = 0; i < numBytes; ++i) + mb[i] = (char) getBitRangeAsInt (i << 3, 8); + + return mb; +} + +void BigInteger::loadFromMemoryBlock (const MemoryBlock& data) +{ + clear(); + + for (int i = (int) data.getSize(); --i >= 0;) + this->setBitRangeAsInt (i << 3, 8, (uint32) data [i]); +} diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.h b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.h new file mode 100644 index 0000000..4c6c25c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.h @@ -0,0 +1,334 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BIGINTEGER_H_INCLUDED +#define JUCE_BIGINTEGER_H_INCLUDED + + +//============================================================================== +/** + An arbitrarily large integer class. + + A BigInteger can be used in a similar way to a normal integer, but has no size + limit (except for memory and performance constraints). + + Negative values are possible, but the value isn't stored as 2s-complement, so + be careful if you use negative values and look at the values of individual bits. +*/ +class JUCE_API BigInteger +{ +public: + //============================================================================== + /** Creates an empty BigInteger */ + BigInteger(); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 32 bits of the number are initialised with this value. + */ + BigInteger (uint32 value); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 32 bits of the number are initialised with the absolute value + passed in, and its sign is set to reflect the sign of the number. + */ + BigInteger (int32 value); + + /** Creates a BigInteger containing an integer value in its low bits. + The low 64 bits of the number are initialised with the absolute value + passed in, and its sign is set to reflect the sign of the number. + */ + BigInteger (int64 value); + + /** Creates a copy of another BigInteger. */ + BigInteger (const BigInteger&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + BigInteger (BigInteger&&) noexcept; + BigInteger& operator= (BigInteger&&) noexcept; + #endif + + /** Destructor. */ + ~BigInteger(); + + //============================================================================== + /** Copies another BigInteger onto this one. */ + BigInteger& operator= (const BigInteger&); + + /** Swaps the internal contents of this with another object. */ + void swapWith (BigInteger&) noexcept; + + //============================================================================== + /** Returns the value of a specified bit in the number. + If the index is out-of-range, the result will be false. + */ + bool operator[] (int bit) const noexcept; + + /** Returns true if no bits are set. */ + bool isZero() const noexcept; + + /** Returns true if the value is 1. */ + bool isOne() const noexcept; + + /** Attempts to get the lowest 32 bits of the value as an integer. + If the value is bigger than the integer limits, this will return only the lower bits. + */ + int toInteger() const noexcept; + + /** Attempts to get the lowest 64 bits of the value as an integer. + If the value is bigger than the integer limits, this will return only the lower bits. + */ + int64 toInt64() const noexcept; + + //============================================================================== + /** Resets the value to 0. */ + void clear(); + + /** Clears a particular bit in the number. */ + void clearBit (int bitNumber) noexcept; + + /** Sets a specified bit to 1. */ + void setBit (int bitNumber); + + /** Sets or clears a specified bit. */ + void setBit (int bitNumber, bool shouldBeSet); + + /** Sets a range of bits to be either on or off. + + @param startBit the first bit to change + @param numBits the number of bits to change + @param shouldBeSet whether to turn these bits on or off + */ + void setRange (int startBit, int numBits, bool shouldBeSet); + + /** Inserts a bit an a given position, shifting up any bits above it. */ + void insertBit (int bitNumber, bool shouldBeSet); + + /** Returns a range of bits as a new BigInteger. + + e.g. getBitRangeAsInt (0, 64) would return the lowest 64 bits. + @see getBitRangeAsInt + */ + BigInteger getBitRange (int startBit, int numBits) const; + + /** Returns a range of bits as an integer value. + + e.g. getBitRangeAsInt (0, 32) would return the lowest 32 bits. + + Asking for more than 32 bits isn't allowed (obviously) - for that, use + getBitRange(). + */ + uint32 getBitRangeAsInt (int startBit, int numBits) const noexcept; + + /** Sets a range of bits to an integer value. + + Copies the given integer onto a range of bits, starting at startBit, + and using up to numBits of the available bits. + */ + void setBitRangeAsInt (int startBit, int numBits, uint32 valueToSet); + + /** Shifts a section of bits left or right. + + @param howManyBitsLeft how far to move the bits (+ve numbers shift it left, -ve numbers shift it right). + @param startBit the first bit to affect - if this is > 0, only bits above that index will be affected. + */ + void shiftBits (int howManyBitsLeft, int startBit); + + /** Returns the total number of set bits in the value. */ + int countNumberOfSetBits() const noexcept; + + /** Looks for the index of the next set bit after a given starting point. + + This searches from startIndex (inclusive) upwards for the first set bit, + and returns its index. If no set bits are found, it returns -1. + */ + int findNextSetBit (int startIndex) const noexcept; + + /** Looks for the index of the next clear bit after a given starting point. + + This searches from startIndex (inclusive) upwards for the first clear bit, + and returns its index. + */ + int findNextClearBit (int startIndex) const noexcept; + + /** Returns the index of the highest set bit in the number. + If the value is zero, this will return -1. + */ + int getHighestBit() const noexcept; + + //============================================================================== + // All the standard arithmetic ops... + + BigInteger& operator+= (const BigInteger&); + BigInteger& operator-= (const BigInteger&); + BigInteger& operator*= (const BigInteger&); + BigInteger& operator/= (const BigInteger&); + BigInteger& operator|= (const BigInteger&); + BigInteger& operator&= (const BigInteger&); + BigInteger& operator^= (const BigInteger&); + BigInteger& operator%= (const BigInteger&); + BigInteger& operator<<= (int numBitsToShift); + BigInteger& operator>>= (int numBitsToShift); + BigInteger& operator++(); + BigInteger& operator--(); + BigInteger operator++ (int); + BigInteger operator-- (int); + + BigInteger operator-() const; + BigInteger operator+ (const BigInteger&) const; + BigInteger operator- (const BigInteger&) const; + BigInteger operator* (const BigInteger&) const; + BigInteger operator/ (const BigInteger&) const; + BigInteger operator| (const BigInteger&) const; + BigInteger operator& (const BigInteger&) const; + BigInteger operator^ (const BigInteger&) const; + BigInteger operator% (const BigInteger&) const; + BigInteger operator<< (int numBitsToShift) const; + BigInteger operator>> (int numBitsToShift) const; + + bool operator== (const BigInteger&) const noexcept; + bool operator!= (const BigInteger&) const noexcept; + bool operator< (const BigInteger&) const noexcept; + bool operator<= (const BigInteger&) const noexcept; + bool operator> (const BigInteger&) const noexcept; + bool operator>= (const BigInteger&) const noexcept; + + //============================================================================== + /** Does a signed comparison of two BigIntegers. + + Return values are: + - 0 if the numbers are the same + - < 0 if this number is smaller than the other + - > 0 if this number is bigger than the other + */ + int compare (const BigInteger& other) const noexcept; + + /** Compares the magnitudes of two BigIntegers, ignoring their signs. + + Return values are: + - 0 if the numbers are the same + - < 0 if this number is smaller than the other + - > 0 if this number is bigger than the other + */ + int compareAbsolute (const BigInteger& other) const noexcept; + + /** Divides this value by another one and returns the remainder. + + This number is divided by other, leaving the quotient in this number, + with the remainder being copied to the other BigInteger passed in. + */ + void divideBy (const BigInteger& divisor, BigInteger& remainder); + + /** Returns the largest value that will divide both this value and the one passed-in. */ + BigInteger findGreatestCommonDivisor (BigInteger other) const; + + /** Performs a combined exponent and modulo operation. + This BigInteger's value becomes (this ^ exponent) % modulus. + */ + void exponentModulo (const BigInteger& exponent, const BigInteger& modulus); + + /** Performs an inverse modulo on the value. + i.e. the result is (this ^ -1) mod (modulus). + */ + void inverseModulo (const BigInteger& modulus); + + //============================================================================== + /** Returns true if the value is less than zero. + @see setNegative, negate + */ + bool isNegative() const noexcept; + + /** Changes the sign of the number to be positive or negative. + @see isNegative, negate + */ + void setNegative (bool shouldBeNegative) noexcept; + + /** Inverts the sign of the number. + @see isNegative, setNegative + */ + void negate() noexcept; + + //============================================================================== + /** Converts the number to a string. + + Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + If minimumNumCharacters is greater than 0, the returned string will be + padded with leading zeros to reach at least that length. + */ + String toString (int base, int minimumNumCharacters = 1) const; + + /** Reads the numeric value from a string. + + Specify a base such as 2 (binary), 8 (octal), 10 (decimal), 16 (hex). + Any invalid characters will be ignored. + */ + void parseString (StringRef text, int base); + + //============================================================================== + /** Turns the number into a block of binary data. + + The data is arranged as little-endian, so the first byte of data is the low 8 bits + of the number, and so on. + + @see loadFromMemoryBlock + */ + MemoryBlock toMemoryBlock() const; + + /** Converts a block of raw data into a number. + + The data is arranged as little-endian, so the first byte of data is the low 8 bits + of the number, and so on. + + @see toMemoryBlock + */ + void loadFromMemoryBlock (const MemoryBlock& data); + +private: + //============================================================================== + HeapBlock values; + size_t numValues; + int highestBit; + bool negative; + + void ensureSize (size_t); + void shiftLeft (int bits, int startBit); + void shiftRight (int bits, int startBit); + + JUCE_LEAK_DETECTOR (BigInteger) +}; + +/** Writes a BigInteger to an OutputStream as a UTF8 decimal string. */ +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const BigInteger& value); + +//============================================================================== +#ifndef DOXYGEN + // For backwards compatibility, BitArray is defined as an alias for BigInteger. + typedef BigInteger BitArray; +#endif + + +#endif // JUCE_BIGINTEGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp new file mode 100644 index 0000000..632b90a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.cpp @@ -0,0 +1,1183 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class Expression::Term : public SingleThreadedReferenceCountedObject +{ +public: + Term() {} + virtual ~Term() {} + + virtual Type getType() const noexcept = 0; + virtual Term* clone() const = 0; + virtual ReferenceCountedObjectPtr resolve (const Scope&, int recursionDepth) = 0; + virtual String toString() const = 0; + virtual double toDouble() const { return 0; } + virtual int getInputIndexFor (const Term*) const { return -1; } + virtual int getOperatorPrecedence() const { return 0; } + virtual int getNumInputs() const { return 0; } + virtual Term* getInput (int) const { return nullptr; } + virtual ReferenceCountedObjectPtr negated(); + + virtual ReferenceCountedObjectPtr createTermToEvaluateInput (const Scope&, const Term* /*inputTerm*/, + double /*overallTarget*/, Term* /*topLevelTerm*/) const + { + jassertfalse; + return ReferenceCountedObjectPtr(); + } + + virtual String getName() const + { + jassertfalse; // You shouldn't call this for an expression that's not actually a function! + return String(); + } + + virtual void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) + { + for (int i = getNumInputs(); --i >= 0;) + getInput (i)->renameSymbol (oldSymbol, newName, scope, recursionDepth); + } + + class SymbolVisitor + { + public: + virtual ~SymbolVisitor() {} + virtual void useSymbol (const Symbol&) = 0; + }; + + virtual void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + for (int i = getNumInputs(); --i >= 0;) + getInput(i)->visitAllSymbols (visitor, scope, recursionDepth); + } + +private: + JUCE_DECLARE_NON_COPYABLE (Term) +}; + + +//============================================================================== +struct Expression::Helpers +{ + typedef ReferenceCountedObjectPtr TermPtr; + + static void checkRecursionDepth (const int depth) + { + if (depth > 256) + throw EvaluationError ("Recursive symbol references"); + } + + friend class Expression::Term; + + //============================================================================== + /** An exception that can be thrown by Expression::evaluate(). */ + class EvaluationError : public std::exception + { + public: + EvaluationError (const String& desc) : description (desc) + { + DBG ("Expression::EvaluationError: " + description); + } + + String description; + }; + + //============================================================================== + class Constant : public Term + { + public: + Constant (const double val, const bool resolutionTarget) + : value (val), isResolutionTarget (resolutionTarget) {} + + Type getType() const noexcept { return constantType; } + Term* clone() const { return new Constant (value, isResolutionTarget); } + TermPtr resolve (const Scope&, int) { return this; } + double toDouble() const { return value; } + TermPtr negated() { return new Constant (-value, isResolutionTarget); } + + String toString() const + { + String s (value); + if (isResolutionTarget) + s = "@" + s; + + return s; + } + + double value; + bool isResolutionTarget; + }; + + //============================================================================== + class BinaryTerm : public Term + { + public: + BinaryTerm (Term* const l, Term* const r) : left (l), right (r) + { + jassert (l != nullptr && r != nullptr); + } + + int getInputIndexFor (const Term* possibleInput) const + { + return possibleInput == left ? 0 : (possibleInput == right ? 1 : -1); + } + + Type getType() const noexcept { return operatorType; } + int getNumInputs() const { return 2; } + Term* getInput (int index) const { return index == 0 ? left.get() : (index == 1 ? right.get() : 0); } + + virtual double performFunction (double left, double right) const = 0; + virtual void writeOperator (String& dest) const = 0; + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + return new Constant (performFunction (left ->resolve (scope, recursionDepth)->toDouble(), + right->resolve (scope, recursionDepth)->toDouble()), false); + } + + String toString() const + { + String s; + + const int ourPrecendence = getOperatorPrecedence(); + if (left->getOperatorPrecedence() > ourPrecendence) + s << '(' << left->toString() << ')'; + else + s = left->toString(); + + writeOperator (s); + + if (right->getOperatorPrecedence() >= ourPrecendence) + s << '(' << right->toString() << ')'; + else + s << right->toString(); + + return s; + } + + protected: + const TermPtr left, right; + + TermPtr createDestinationTerm (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + jassert (input == left || input == right); + if (input != left && input != right) + return TermPtr(); + + if (const Term* const dest = findDestinationFor (topLevelTerm, this)) + return dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm); + + return new Constant (overallTarget, false); + } + }; + + //============================================================================== + class SymbolTerm : public Term + { + public: + explicit SymbolTerm (const String& sym) : symbol (sym) {} + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + return scope.getSymbolValue (symbol).term->resolve (scope, recursionDepth + 1); + } + + Type getType() const noexcept { return symbolType; } + Term* clone() const { return new SymbolTerm (symbol); } + String toString() const { return symbol; } + String getName() const { return symbol; } + + void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + visitor.useSymbol (Symbol (scope.getScopeUID(), symbol)); + scope.getSymbolValue (symbol).term->visitAllSymbols (visitor, scope, recursionDepth + 1); + } + + void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int /*recursionDepth*/) + { + if (oldSymbol.symbolName == symbol && scope.getScopeUID() == oldSymbol.scopeUID) + symbol = newName; + } + + String symbol; + }; + + //============================================================================== + class Function : public Term + { + public: + explicit Function (const String& name) : functionName (name) {} + + Function (const String& name, const Array& params) + : functionName (name), parameters (params) + {} + + Type getType() const noexcept { return functionType; } + Term* clone() const { return new Function (functionName, parameters); } + int getNumInputs() const { return parameters.size(); } + Term* getInput (int i) const { return parameters.getReference(i).term; } + String getName() const { return functionName; } + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + double result = 0; + const int numParams = parameters.size(); + if (numParams > 0) + { + HeapBlock params ((size_t) numParams); + for (int i = 0; i < numParams; ++i) + params[i] = parameters.getReference(i).term->resolve (scope, recursionDepth + 1)->toDouble(); + + result = scope.evaluateFunction (functionName, params, numParams); + } + else + { + result = scope.evaluateFunction (functionName, nullptr, 0); + } + + return new Constant (result, false); + } + + int getInputIndexFor (const Term* possibleInput) const + { + for (int i = 0; i < parameters.size(); ++i) + if (parameters.getReference(i).term == possibleInput) + return i; + + return -1; + } + + String toString() const + { + if (parameters.size() == 0) + return functionName + "()"; + + String s (functionName + " ("); + + for (int i = 0; i < parameters.size(); ++i) + { + s << parameters.getReference(i).term->toString(); + + if (i < parameters.size() - 1) + s << ", "; + } + + s << ')'; + return s; + } + + const String functionName; + Array parameters; + }; + + //============================================================================== + class DotOperator : public BinaryTerm + { + public: + DotOperator (SymbolTerm* const l, Term* const r) : BinaryTerm (l, r) {} + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + + EvaluationVisitor visitor (right, recursionDepth + 1); + scope.visitRelativeScope (getSymbol()->symbol, visitor); + return visitor.output; + } + + Term* clone() const { return new DotOperator (getSymbol(), right); } + String getName() const { return "."; } + int getOperatorPrecedence() const { return 1; } + void writeOperator (String& dest) const { dest << '.'; } + double performFunction (double, double) const { return 0.0; } + + void visitAllSymbols (SymbolVisitor& visitor, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + visitor.useSymbol (Symbol (scope.getScopeUID(), getSymbol()->symbol)); + + SymbolVisitingVisitor v (right, visitor, recursionDepth + 1); + + try + { + scope.visitRelativeScope (getSymbol()->symbol, v); + } + catch (...) {} + } + + void renameSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope, int recursionDepth) + { + checkRecursionDepth (recursionDepth); + getSymbol()->renameSymbol (oldSymbol, newName, scope, recursionDepth); + + SymbolRenamingVisitor visitor (right, oldSymbol, newName, recursionDepth + 1); + + try + { + scope.visitRelativeScope (getSymbol()->symbol, visitor); + } + catch (...) {} + } + + private: + //============================================================================== + class EvaluationVisitor : public Scope::Visitor + { + public: + EvaluationVisitor (const TermPtr& t, const int recursion) + : input (t), output (t), recursionCount (recursion) {} + + void visit (const Scope& scope) { output = input->resolve (scope, recursionCount); } + + const TermPtr input; + TermPtr output; + const int recursionCount; + + private: + JUCE_DECLARE_NON_COPYABLE (EvaluationVisitor) + }; + + class SymbolVisitingVisitor : public Scope::Visitor + { + public: + SymbolVisitingVisitor (const TermPtr& t, SymbolVisitor& v, const int recursion) + : input (t), visitor (v), recursionCount (recursion) {} + + void visit (const Scope& scope) { input->visitAllSymbols (visitor, scope, recursionCount); } + + private: + const TermPtr input; + SymbolVisitor& visitor; + const int recursionCount; + + JUCE_DECLARE_NON_COPYABLE (SymbolVisitingVisitor) + }; + + class SymbolRenamingVisitor : public Scope::Visitor + { + public: + SymbolRenamingVisitor (const TermPtr& t, const Expression::Symbol& symbol_, const String& newName_, const int recursionCount_) + : input (t), symbol (symbol_), newName (newName_), recursionCount (recursionCount_) {} + + void visit (const Scope& scope) { input->renameSymbol (symbol, newName, scope, recursionCount); } + + private: + const TermPtr input; + const Symbol& symbol; + const String newName; + const int recursionCount; + + JUCE_DECLARE_NON_COPYABLE (SymbolRenamingVisitor) + }; + + SymbolTerm* getSymbol() const { return static_cast (left.get()); } + + JUCE_DECLARE_NON_COPYABLE (DotOperator) + }; + + //============================================================================== + class Negate : public Term + { + public: + explicit Negate (const TermPtr& t) : input (t) + { + jassert (t != nullptr); + } + + Type getType() const noexcept { return operatorType; } + int getInputIndexFor (const Term* possibleInput) const { return possibleInput == input ? 0 : -1; } + int getNumInputs() const { return 1; } + Term* getInput (int index) const { return index == 0 ? input.get() : nullptr; } + Term* clone() const { return new Negate (input->clone()); } + + TermPtr resolve (const Scope& scope, int recursionDepth) + { + return new Constant (-input->resolve (scope, recursionDepth)->toDouble(), false); + } + + String getName() const { return "-"; } + TermPtr negated() { return input; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* t, double overallTarget, Term* topLevelTerm) const + { + (void) t; + jassert (t == input); + + const Term* const dest = findDestinationFor (topLevelTerm, this); + + return new Negate (dest == nullptr ? new Constant (overallTarget, false) + : dest->createTermToEvaluateInput (scope, this, overallTarget, topLevelTerm)); + } + + String toString() const + { + if (input->getOperatorPrecedence() > 0) + return "-(" + input->toString() + ")"; + + return "-" + input->toString(); + } + + private: + const TermPtr input; + }; + + //============================================================================== + class Add : public BinaryTerm + { + public: + Add (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Add (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs + rhs; } + int getOperatorPrecedence() const { return 3; } + String getName() const { return "+"; } + void writeOperator (String& dest) const { dest << " + "; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + return new Subtract (newDest, (input == left ? right : left)->clone()); + } + + private: + JUCE_DECLARE_NON_COPYABLE (Add) + }; + + //============================================================================== + class Subtract : public BinaryTerm + { + public: + Subtract (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Subtract (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs - rhs; } + int getOperatorPrecedence() const { return 3; } + String getName() const { return "-"; } + void writeOperator (String& dest) const { dest << " - "; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + if (input == left) + return new Add (newDest, right->clone()); + + return new Subtract (left->clone(), newDest); + } + + private: + JUCE_DECLARE_NON_COPYABLE (Subtract) + }; + + //============================================================================== + class Multiply : public BinaryTerm + { + public: + Multiply (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Multiply (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs * rhs; } + String getName() const { return "*"; } + void writeOperator (String& dest) const { dest << " * "; } + int getOperatorPrecedence() const { return 2; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + return new Divide (newDest, (input == left ? right : left)->clone()); + } + + private: + JUCE_DECLARE_NON_COPYABLE (Multiply) + }; + + //============================================================================== + class Divide : public BinaryTerm + { + public: + Divide (Term* const l, Term* const r) : BinaryTerm (l, r) {} + + Term* clone() const { return new Divide (left->clone(), right->clone()); } + double performFunction (double lhs, double rhs) const { return lhs / rhs; } + String getName() const { return "/"; } + void writeOperator (String& dest) const { dest << " / "; } + int getOperatorPrecedence() const { return 2; } + + TermPtr createTermToEvaluateInput (const Scope& scope, const Term* input, double overallTarget, Term* topLevelTerm) const + { + const TermPtr newDest (createDestinationTerm (scope, input, overallTarget, topLevelTerm)); + if (newDest == nullptr) + return TermPtr(); + + if (input == left) + return new Multiply (newDest, right->clone()); + + return new Divide (left->clone(), newDest); + } + + private: + JUCE_DECLARE_NON_COPYABLE (Divide) + }; + + //============================================================================== + static Term* findDestinationFor (Term* const topLevel, const Term* const inputTerm) + { + const int inputIndex = topLevel->getInputIndexFor (inputTerm); + if (inputIndex >= 0) + return topLevel; + + for (int i = topLevel->getNumInputs(); --i >= 0;) + { + Term* const t = findDestinationFor (topLevel->getInput (i), inputTerm); + + if (t != nullptr) + return t; + } + + return nullptr; + } + + static Constant* findTermToAdjust (Term* const term, const bool mustBeFlagged) + { + jassert (term != nullptr); + + if (term->getType() == constantType) + { + Constant* const c = static_cast (term); + if (c->isResolutionTarget || ! mustBeFlagged) + return c; + } + + if (term->getType() == functionType) + return nullptr; + + const int numIns = term->getNumInputs(); + + for (int i = 0; i < numIns; ++i) + { + Term* const input = term->getInput (i); + + if (input->getType() == constantType) + { + Constant* const c = static_cast (input); + + if (c->isResolutionTarget || ! mustBeFlagged) + return c; + } + } + + for (int i = 0; i < numIns; ++i) + { + Constant* const c = findTermToAdjust (term->getInput (i), mustBeFlagged); + if (c != nullptr) + return c; + } + + return nullptr; + } + + static bool containsAnySymbols (const Term* const t) + { + if (t->getType() == Expression::symbolType) + return true; + + for (int i = t->getNumInputs(); --i >= 0;) + if (containsAnySymbols (t->getInput (i))) + return true; + + return false; + } + + //============================================================================== + class SymbolCheckVisitor : public Term::SymbolVisitor + { + public: + SymbolCheckVisitor (const Symbol& symbol_) : wasFound (false), symbol (symbol_) {} + void useSymbol (const Symbol& s) { wasFound = wasFound || s == symbol; } + + bool wasFound; + + private: + const Symbol& symbol; + + JUCE_DECLARE_NON_COPYABLE (SymbolCheckVisitor) + }; + + //============================================================================== + class SymbolListVisitor : public Term::SymbolVisitor + { + public: + SymbolListVisitor (Array& list_) : list (list_) {} + void useSymbol (const Symbol& s) { list.addIfNotAlreadyThere (s); } + + private: + Array& list; + + JUCE_DECLARE_NON_COPYABLE (SymbolListVisitor) + }; + + //============================================================================== + class Parser + { + public: + //============================================================================== + Parser (String::CharPointerType& stringToParse) + : text (stringToParse) + { + } + + TermPtr readUpToComma() + { + if (text.isEmpty()) + return new Constant (0.0, false); + + const TermPtr e (readExpression()); + + if (e == nullptr || ((! readOperator (",")) && ! text.isEmpty())) + throw ParseError ("Syntax error: \"" + String (text) + "\""); + + return e; + } + + private: + String::CharPointerType& text; + + //============================================================================== + static inline bool isDecimalDigit (const juce_wchar c) noexcept + { + return c >= '0' && c <= '9'; + } + + bool readChar (const juce_wchar required) noexcept + { + if (*text == required) + { + ++text; + return true; + } + + return false; + } + + bool readOperator (const char* ops, char* const opType = nullptr) noexcept + { + text = text.findEndOfWhitespace(); + + while (*ops != 0) + { + if (readChar ((juce_wchar) (uint8) *ops)) + { + if (opType != nullptr) + *opType = *ops; + + return true; + } + + ++ops; + } + + return false; + } + + bool readIdentifier (String& identifier) noexcept + { + text = text.findEndOfWhitespace(); + String::CharPointerType t (text); + int numChars = 0; + + if (t.isLetter() || *t == '_') + { + ++t; + ++numChars; + + while (t.isLetterOrDigit() || *t == '_') + { + ++t; + ++numChars; + } + } + + if (numChars > 0) + { + identifier = String (text, (size_t) numChars); + text = t; + return true; + } + + return false; + } + + Term* readNumber() noexcept + { + text = text.findEndOfWhitespace(); + String::CharPointerType t (text); + + const bool isResolutionTarget = (*t == '@'); + if (isResolutionTarget) + { + ++t; + t = t.findEndOfWhitespace(); + text = t; + } + + if (*t == '-') + { + ++t; + t = t.findEndOfWhitespace(); + } + + if (isDecimalDigit (*t) || (*t == '.' && isDecimalDigit (t[1]))) + return new Constant (CharacterFunctions::readDoubleValue (text), isResolutionTarget); + + return nullptr; + } + + TermPtr readExpression() + { + TermPtr lhs (readMultiplyOrDivideExpression()); + + char opType; + while (lhs != nullptr && readOperator ("+-", &opType)) + { + TermPtr rhs (readMultiplyOrDivideExpression()); + + if (rhs == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((juce_wchar) (uint8) opType) + "\""); + + if (opType == '+') + lhs = new Add (lhs, rhs); + else + lhs = new Subtract (lhs, rhs); + } + + return lhs; + } + + TermPtr readMultiplyOrDivideExpression() + { + TermPtr lhs (readUnaryExpression()); + + char opType; + while (lhs != nullptr && readOperator ("*/", &opType)) + { + TermPtr rhs (readUnaryExpression()); + + if (rhs == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((juce_wchar) (uint8) opType) + "\""); + + if (opType == '*') + lhs = new Multiply (lhs, rhs); + else + lhs = new Divide (lhs, rhs); + } + + return lhs; + } + + TermPtr readUnaryExpression() + { + char opType; + if (readOperator ("+-", &opType)) + { + TermPtr e (readUnaryExpression()); + + if (e == nullptr) + throw ParseError ("Expected expression after \"" + String::charToString ((juce_wchar) (uint8) opType) + "\""); + + if (opType == '-') + e = e->negated(); + + return e; + } + + return readPrimaryExpression(); + } + + TermPtr readPrimaryExpression() + { + TermPtr e (readParenthesisedExpression()); + if (e != nullptr) + return e; + + e = readNumber(); + if (e != nullptr) + return e; + + return readSymbolOrFunction(); + } + + TermPtr readSymbolOrFunction() + { + String identifier; + if (readIdentifier (identifier)) + { + if (readOperator ("(")) // method call... + { + Function* const f = new Function (identifier); + ScopedPointer func (f); // (can't use ScopedPointer in MSVC) + + TermPtr param (readExpression()); + + if (param == nullptr) + { + if (readOperator (")")) + return func.release(); + + throw ParseError ("Expected parameters after \"" + identifier + " (\""); + } + + f->parameters.add (Expression (param)); + + while (readOperator (",")) + { + param = readExpression(); + + if (param == nullptr) + throw ParseError ("Expected expression after \",\""); + + f->parameters.add (Expression (param)); + } + + if (readOperator (")")) + return func.release(); + + throw ParseError ("Expected \")\""); + } + + if (readOperator (".")) + { + TermPtr rhs (readSymbolOrFunction()); + + if (rhs == nullptr) + throw ParseError ("Expected symbol or function after \".\""); + + if (identifier == "this") + return rhs; + + return new DotOperator (new SymbolTerm (identifier), rhs); + } + + // just a symbol.. + jassert (identifier.trim() == identifier); + return new SymbolTerm (identifier); + } + + return TermPtr(); + } + + TermPtr readParenthesisedExpression() + { + if (! readOperator ("(")) + return TermPtr(); + + const TermPtr e (readExpression()); + if (e == nullptr || ! readOperator (")")) + return TermPtr(); + + return e; + } + + JUCE_DECLARE_NON_COPYABLE (Parser) + }; +}; + +//============================================================================== +Expression::Expression() + : term (new Expression::Helpers::Constant (0, false)) +{ +} + +Expression::~Expression() +{ +} + +Expression::Expression (Term* const term_) + : term (term_) +{ + jassert (term != nullptr); +} + +Expression::Expression (const double constant) + : term (new Expression::Helpers::Constant (constant, false)) +{ +} + +Expression::Expression (const Expression& other) + : term (other.term) +{ +} + +Expression& Expression::operator= (const Expression& other) +{ + term = other.term; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Expression::Expression (Expression&& other) noexcept + : term (static_cast &&> (other.term)) +{ +} + +Expression& Expression::operator= (Expression&& other) noexcept +{ + term = static_cast &&> (other.term); + return *this; +} +#endif + +Expression::Expression (const String& stringToParse) +{ + String::CharPointerType text (stringToParse.getCharPointer()); + Helpers::Parser parser (text); + term = parser.readUpToComma(); +} + +Expression Expression::parse (String::CharPointerType& stringToParse) +{ + Helpers::Parser parser (stringToParse); + return Expression (parser.readUpToComma()); +} + +double Expression::evaluate() const +{ + return evaluate (Expression::Scope()); +} + +double Expression::evaluate (const Expression::Scope& scope) const +{ + try + { + return term->resolve (scope, 0)->toDouble(); + } + catch (Helpers::EvaluationError&) + {} + + return 0; +} + +double Expression::evaluate (const Scope& scope, String& evaluationError) const +{ + try + { + return term->resolve (scope, 0)->toDouble(); + } + catch (Helpers::EvaluationError& e) + { + evaluationError = e.description; + } + + return 0; +} + +Expression Expression::operator+ (const Expression& other) const { return Expression (new Helpers::Add (term, other.term)); } +Expression Expression::operator- (const Expression& other) const { return Expression (new Helpers::Subtract (term, other.term)); } +Expression Expression::operator* (const Expression& other) const { return Expression (new Helpers::Multiply (term, other.term)); } +Expression Expression::operator/ (const Expression& other) const { return Expression (new Helpers::Divide (term, other.term)); } +Expression Expression::operator-() const { return Expression (term->negated()); } +Expression Expression::symbol (const String& symbol) { return Expression (new Helpers::SymbolTerm (symbol)); } + +Expression Expression::function (const String& functionName, const Array& parameters) +{ + return Expression (new Helpers::Function (functionName, parameters)); +} + +Expression Expression::adjustedToGiveNewResult (const double targetValue, const Expression::Scope& scope) const +{ + ScopedPointer newTerm (term->clone()); + + Helpers::Constant* termToAdjust = Helpers::findTermToAdjust (newTerm, true); + + if (termToAdjust == nullptr) + termToAdjust = Helpers::findTermToAdjust (newTerm, false); + + if (termToAdjust == nullptr) + { + newTerm = new Helpers::Add (newTerm.release(), new Helpers::Constant (0, false)); + termToAdjust = Helpers::findTermToAdjust (newTerm, false); + } + + jassert (termToAdjust != nullptr); + + const Term* const parent = Helpers::findDestinationFor (newTerm, termToAdjust); + + if (parent == nullptr) + { + termToAdjust->value = targetValue; + } + else + { + const Helpers::TermPtr reverseTerm (parent->createTermToEvaluateInput (scope, termToAdjust, targetValue, newTerm)); + + if (reverseTerm == nullptr) + return Expression (targetValue); + + termToAdjust->value = reverseTerm->resolve (scope, 0)->toDouble(); + } + + return Expression (newTerm.release()); +} + +Expression Expression::withRenamedSymbol (const Expression::Symbol& oldSymbol, const String& newName, const Scope& scope) const +{ + jassert (newName.toLowerCase().containsOnly ("abcdefghijklmnopqrstuvwxyz0123456789_")); + + if (oldSymbol.symbolName == newName) + return *this; + + Expression e (term->clone()); + e.term->renameSymbol (oldSymbol, newName, scope, 0); + return e; +} + +bool Expression::referencesSymbol (const Expression::Symbol& symbolToCheck, const Scope& scope) const +{ + Helpers::SymbolCheckVisitor visitor (symbolToCheck); + + try + { + term->visitAllSymbols (visitor, scope, 0); + } + catch (Helpers::EvaluationError&) + {} + + return visitor.wasFound; +} + +void Expression::findReferencedSymbols (Array& results, const Scope& scope) const +{ + try + { + Helpers::SymbolListVisitor visitor (results); + term->visitAllSymbols (visitor, scope, 0); + } + catch (Helpers::EvaluationError&) + {} +} + +String Expression::toString() const { return term->toString(); } +bool Expression::usesAnySymbols() const { return Helpers::containsAnySymbols (term); } +Expression::Type Expression::getType() const noexcept { return term->getType(); } +String Expression::getSymbolOrFunction() const { return term->getName(); } +int Expression::getNumInputs() const { return term->getNumInputs(); } +Expression Expression::getInput (int index) const { return Expression (term->getInput (index)); } + +//============================================================================== +ReferenceCountedObjectPtr Expression::Term::negated() +{ + return new Helpers::Negate (this); +} + +//============================================================================== +Expression::ParseError::ParseError (const String& message) + : description (message) +{ + DBG ("Expression::ParseError: " + message); +} + +//============================================================================== +Expression::Symbol::Symbol (const String& scopeUID_, const String& symbolName_) + : scopeUID (scopeUID_), symbolName (symbolName_) +{ +} + +bool Expression::Symbol::operator== (const Symbol& other) const noexcept +{ + return symbolName == other.symbolName && scopeUID == other.scopeUID; +} + +bool Expression::Symbol::operator!= (const Symbol& other) const noexcept +{ + return ! operator== (other); +} + +//============================================================================== +Expression::Scope::Scope() {} +Expression::Scope::~Scope() {} + +Expression Expression::Scope::getSymbolValue (const String& symbol) const +{ + if (symbol.isNotEmpty()) + throw Helpers::EvaluationError ("Unknown symbol: " + symbol); + + return Expression(); +} + +double Expression::Scope::evaluateFunction (const String& functionName, const double* parameters, int numParams) const +{ + if (numParams > 0) + { + if (functionName == "min") + { + double v = parameters[0]; + for (int i = 1; i < numParams; ++i) + v = jmin (v, parameters[i]); + + return v; + } + + if (functionName == "max") + { + double v = parameters[0]; + for (int i = 1; i < numParams; ++i) + v = jmax (v, parameters[i]); + + return v; + } + + if (numParams == 1) + { + if (functionName == "sin") return std::sin (parameters[0]); + if (functionName == "cos") return std::cos (parameters[0]); + if (functionName == "tan") return std::tan (parameters[0]); + if (functionName == "abs") return std::abs (parameters[0]); + } + } + + throw Helpers::EvaluationError ("Unknown function: \"" + functionName + "\""); +} + +void Expression::Scope::visitRelativeScope (const String& scopeName, Visitor&) const +{ + throw Helpers::EvaluationError ("Unknown symbol: " + scopeName); +} + +String Expression::Scope::getScopeUID() const +{ + return String(); +} diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Expression.h b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.h new file mode 100644 index 0000000..8939129 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Expression.h @@ -0,0 +1,270 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_EXPRESSION_H_INCLUDED +#define JUCE_EXPRESSION_H_INCLUDED + + +//============================================================================== +/** + A class for dynamically evaluating simple numeric expressions. + + This class can parse a simple C-style string expression involving floating point + numbers, named symbols and functions. The basic arithmetic operations of +, -, *, / + are supported, as well as parentheses, and any alphanumeric identifiers are + assumed to be named symbols which will be resolved when the expression is + evaluated. + + Expressions which use identifiers and functions require a subclass of + Expression::Scope to be supplied when evaluating them, and this object + is expected to be able to resolve the symbol names and perform the functions that + are used. +*/ +class JUCE_API Expression +{ +public: + //============================================================================== + /** Creates a simple expression with a value of 0. */ + Expression(); + + /** Destructor. */ + ~Expression(); + + /** Creates a simple expression with a specified constant value. */ + explicit Expression (double constant); + + /** Creates a copy of an expression. */ + Expression (const Expression&); + + /** Copies another expression. */ + Expression& operator= (const Expression&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Expression (Expression&&) noexcept; + Expression& operator= (Expression&&) noexcept; + #endif + + /** Creates an expression by parsing a string. + If there's a syntax error in the string, this will throw a ParseError exception. + @throws ParseError + */ + explicit Expression (const String& stringToParse); + + /** Returns a string version of the expression. */ + String toString() const; + + /** Returns an expression which is an addition operation of two existing expressions. */ + Expression operator+ (const Expression&) const; + /** Returns an expression which is a subtraction operation of two existing expressions. */ + Expression operator- (const Expression&) const; + /** Returns an expression which is a multiplication operation of two existing expressions. */ + Expression operator* (const Expression&) const; + /** Returns an expression which is a division operation of two existing expressions. */ + Expression operator/ (const Expression&) const; + /** Returns an expression which performs a negation operation on an existing expression. */ + Expression operator-() const; + + /** Returns an Expression which is an identifier reference. */ + static Expression symbol (const String& symbol); + + /** Returns an Expression which is a function call. */ + static Expression function (const String& functionName, const Array& parameters); + + /** Returns an Expression which parses a string from a character pointer, and updates the pointer + to indicate where it finished. + + The pointer is incremented so that on return, it indicates the character that follows + the end of the expression that was parsed. + + If there's a syntax error in the string, this will throw a ParseError exception. + @throws ParseError + */ + static Expression parse (String::CharPointerType& stringToParse); + + //============================================================================== + /** When evaluating an Expression object, this class is used to resolve symbols and + perform functions that the expression uses. + */ + class JUCE_API Scope + { + public: + Scope(); + virtual ~Scope(); + + /** Returns some kind of globally unique ID that identifies this scope. */ + virtual String getScopeUID() const; + + /** Returns the value of a symbol. + If the symbol is unknown, this can throw an Expression::EvaluationError exception. + The member value is set to the part of the symbol that followed the dot, if there is + one, e.g. for "foo.bar", symbol = "foo" and member = "bar". + @throws Expression::EvaluationError + */ + virtual Expression getSymbolValue (const String& symbol) const; + + /** Executes a named function. + If the function name is unknown, this can throw an Expression::EvaluationError exception. + @throws Expression::EvaluationError + */ + virtual double evaluateFunction (const String& functionName, + const double* parameters, int numParameters) const; + + /** Used as a callback by the Scope::visitRelativeScope() method. + You should never create an instance of this class yourself, it's used by the + expression evaluation code. + */ + class Visitor + { + public: + virtual ~Visitor() {} + virtual void visit (const Scope&) = 0; + }; + + /** Creates a Scope object for a named scope, and then calls a visitor + to do some kind of processing with this new scope. + + If the name is valid, this method must create a suitable (temporary) Scope + object to represent it, and must call the Visitor::visit() method with this + new scope. + */ + virtual void visitRelativeScope (const String& scopeName, Visitor& visitor) const; + }; + + /** Evaluates this expression, without using a Scope. + Without a Scope, no symbols can be used, and only basic functions such as sin, cos, tan, + min, max are available. + To find out about any errors during evaluation, use the other version of this method which + takes a String parameter. + */ + double evaluate() const; + + /** Evaluates this expression, providing a scope that should be able to evaluate any symbols + or functions that it uses. + To find out about any errors during evaluation, use the other version of this method which + takes a String parameter. + */ + double evaluate (const Scope& scope) const; + + /** Evaluates this expression, providing a scope that should be able to evaluate any symbols + or functions that it uses. + */ + double evaluate (const Scope& scope, String& evaluationError) const; + + /** Attempts to return an expression which is a copy of this one, but with a constant adjusted + to make the expression resolve to a target value. + + E.g. if the expression is "x + 10" and x is 5, then asking for a target value of 8 will return + the expression "x + 3". Obviously some expressions can't be reversed in this way, in which + case they might just be adjusted by adding a constant to the original expression. + + @throws Expression::EvaluationError + */ + Expression adjustedToGiveNewResult (double targetValue, const Scope& scope) const; + + /** Represents a symbol that is used in an Expression. */ + struct Symbol + { + Symbol (const String& scopeUID, const String& symbolName); + bool operator== (const Symbol&) const noexcept; + bool operator!= (const Symbol&) const noexcept; + + String scopeUID; /**< The unique ID of the Scope that contains this symbol. */ + String symbolName; /**< The name of the symbol. */ + }; + + /** Returns a copy of this expression in which all instances of a given symbol have been renamed. */ + Expression withRenamedSymbol (const Symbol& oldSymbol, const String& newName, const Scope& scope) const; + + /** Returns true if this expression makes use of the specified symbol. + If a suitable scope is supplied, the search will dereference and recursively check + all symbols, so that it can be determined whether this expression relies on the given + symbol at any level in its evaluation. If the scope parameter is null, this just checks + whether the expression contains any direct references to the symbol. + + @throws Expression::EvaluationError + */ + bool referencesSymbol (const Symbol& symbol, const Scope& scope) const; + + /** Returns true if this expression contains any symbols. */ + bool usesAnySymbols() const; + + /** Returns a list of all symbols that may be needed to resolve this expression in the given scope. */ + void findReferencedSymbols (Array& results, const Scope& scope) const; + + //============================================================================== + /** An exception that can be thrown by Expression::parse(). */ + class ParseError : public std::exception + { + public: + ParseError (const String& message); + + String description; + }; + + //============================================================================== + /** Expression type. + @see Expression::getType() + */ + enum Type + { + constantType, + functionType, + operatorType, + symbolType + }; + + /** Returns the type of this expression. */ + Type getType() const noexcept; + + /** If this expression is a symbol, function or operator, this returns its identifier. */ + String getSymbolOrFunction() const; + + /** Returns the number of inputs to this expression. + @see getInput + */ + int getNumInputs() const; + + /** Retrieves one of the inputs to this expression. + @see getNumInputs + */ + Expression getInput (int index) const; + +private: + //============================================================================== + class Term; + struct Helpers; + friend class Term; + friend struct Helpers; + friend struct ContainerDeletePolicy; + friend class ReferenceCountedObjectPtr; + ReferenceCountedObjectPtr term; + + explicit Expression (Term*); +}; + +#endif // JUCE_EXPRESSION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h new file mode 100644 index 0000000..d2d02b9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h @@ -0,0 +1,549 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MATHSFUNCTIONS_H_INCLUDED +#define JUCE_MATHSFUNCTIONS_H_INCLUDED + +//============================================================================== +/* + This file sets up some handy mathematical typdefs and functions. +*/ + +//============================================================================== +// Definitions for the int8, int16, int32, int64 and pointer_sized_int types. + +/** A platform-independent 8-bit signed integer type. */ +typedef signed char int8; +/** A platform-independent 8-bit unsigned integer type. */ +typedef unsigned char uint8; +/** A platform-independent 16-bit signed integer type. */ +typedef signed short int16; +/** A platform-independent 16-bit unsigned integer type. */ +typedef unsigned short uint16; +/** A platform-independent 32-bit signed integer type. */ +typedef signed int int32; +/** A platform-independent 32-bit unsigned integer type. */ +typedef unsigned int uint32; + +#if JUCE_MSVC + /** A platform-independent 64-bit integer type. */ + typedef __int64 int64; + /** A platform-independent 64-bit unsigned integer type. */ + typedef unsigned __int64 uint64; +#else + /** A platform-independent 64-bit integer type. */ + typedef long long int64; + /** A platform-independent 64-bit unsigned integer type. */ + typedef unsigned long long uint64; +#endif + +#ifndef DOXYGEN + /** A macro for creating 64-bit literals. + Historically, this was needed to support portability with MSVC6, and is kept here + so that old code will still compile, but nowadays every compiler will support the + LL and ULL suffixes, so you should use those in preference to this macro. + */ + #define literal64bit(longLiteral) (longLiteral##LL) +#endif + +#if JUCE_64BIT + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int64 pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef uint64 pointer_sized_uint; +#elif JUCE_MSVC + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef _W64 int pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef _W64 unsigned int pointer_sized_uint; +#else + /** A signed integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef int pointer_sized_int; + /** An unsigned integer type that's guaranteed to be large enough to hold a pointer without truncating it. */ + typedef unsigned int pointer_sized_uint; +#endif + +#if JUCE_MSVC + typedef pointer_sized_int ssize_t; +#endif + +//============================================================================== +// Some indispensible min/max functions + +/** Returns the larger of two values. */ +template +inline Type jmax (const Type a, const Type b) { return (a < b) ? b : a; } + +/** Returns the larger of three values. */ +template +inline Type jmax (const Type a, const Type b, const Type c) { return (a < b) ? ((b < c) ? c : b) : ((a < c) ? c : a); } + +/** Returns the larger of four values. */ +template +inline Type jmax (const Type a, const Type b, const Type c, const Type d) { return jmax (a, jmax (b, c, d)); } + +/** Returns the smaller of two values. */ +template +inline Type jmin (const Type a, const Type b) { return (b < a) ? b : a; } + +/** Returns the smaller of three values. */ +template +inline Type jmin (const Type a, const Type b, const Type c) { return (b < a) ? ((c < b) ? c : b) : ((c < a) ? c : a); } + +/** Returns the smaller of four values. */ +template +inline Type jmin (const Type a, const Type b, const Type c, const Type d) { return jmin (a, jmin (b, c, d)); } + +/** Scans an array of values, returning the minimum value that it contains. */ +template +const Type findMinimum (const Type* data, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*data++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *data++; + if (v < result) result = v; + } + + return result; +} + +/** Scans an array of values, returning the maximum value that it contains. */ +template +const Type findMaximum (const Type* values, int numValues) +{ + if (numValues <= 0) + return Type(); + + Type result (*values++); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + if (result < v) result = v; + } + + return result; +} + +/** Scans an array of values, returning the minimum and maximum values that it contains. */ +template +void findMinAndMax (const Type* values, int numValues, Type& lowest, Type& highest) +{ + if (numValues <= 0) + { + lowest = Type(); + highest = Type(); + } + else + { + Type mn (*values++); + Type mx (mn); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const Type& v = *values++; + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + lowest = mn; + highest = mx; + } +} + + +//============================================================================== +/** Constrains a value to keep it within a given range. + + This will check that the specified value lies between the lower and upper bounds + specified, and if not, will return the nearest value that would be in-range. Effectively, + it's like calling jmax (lowerLimit, jmin (upperLimit, value)). + + Note that it expects that lowerLimit <= upperLimit. If this isn't true, + the results will be unpredictable. + + @param lowerLimit the minimum value to return + @param upperLimit the maximum value to return + @param valueToConstrain the value to try to return + @returns the closest value to valueToConstrain which lies between lowerLimit + and upperLimit (inclusive) + @see jlimit0To, jmin, jmax +*/ +template +inline Type jlimit (const Type lowerLimit, + const Type upperLimit, + const Type valueToConstrain) noexcept +{ + jassert (lowerLimit <= upperLimit); // if these are in the wrong order, results are unpredictable.. + + return (valueToConstrain < lowerLimit) ? lowerLimit + : ((upperLimit < valueToConstrain) ? upperLimit + : valueToConstrain); +} + +/** Returns true if a value is at least zero, and also below a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest < upperLimit + @endcode +*/ +template +inline bool isPositiveAndBelow (Type valueToTest, Type upperLimit) noexcept +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest < upperLimit; +} + +template <> +inline bool isPositiveAndBelow (const int valueToTest, const int upperLimit) noexcept +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) < static_cast (upperLimit); +} + +/** Returns true if a value is at least zero, and also less than or equal to a specified upper limit. + This is basically a quicker way to write: + @code valueToTest >= 0 && valueToTest <= upperLimit + @endcode +*/ +template +inline bool isPositiveAndNotGreaterThan (Type valueToTest, Type upperLimit) noexcept +{ + jassert (Type() <= upperLimit); // makes no sense to call this if the upper limit is itself below zero.. + return Type() <= valueToTest && valueToTest <= upperLimit; +} + +template <> +inline bool isPositiveAndNotGreaterThan (const int valueToTest, const int upperLimit) noexcept +{ + jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero.. + return static_cast (valueToTest) <= static_cast (upperLimit); +} + +//============================================================================== +/** Handy function to swap two values. */ +template +inline void swapVariables (Type& variable1, Type& variable2) +{ + std::swap (variable1, variable2); +} + +/** Handy function for getting the number of elements in a simple const C array. + E.g. + @code + static int myArray[] = { 1, 2, 3 }; + + int numElements = numElementsInArray (myArray) // returns 3 + @endcode +*/ +template +inline int numElementsInArray (Type (&array)[N]) +{ + (void) array; // (required to avoid a spurious warning in MS compilers) + (void) sizeof (0[array]); // This line should cause an error if you pass an object with a user-defined subscript operator + return N; +} + +//============================================================================== +// Some useful maths functions that aren't always present with all compilers and build settings. + +/** Using juce_hypot is easier than dealing with the different types of hypot function + that are provided by the various platforms and compilers. */ +template +inline Type juce_hypot (Type a, Type b) noexcept +{ + #if JUCE_MSVC + return static_cast (_hypot (a, b)); + #else + return static_cast (hypot (a, b)); + #endif +} + +/** 64-bit abs function. */ +inline int64 abs64 (const int64 n) noexcept +{ + return (n >= 0) ? n : -n; +} + +#if JUCE_MSVC && ! defined (DOXYGEN) // The MSVC libraries omit these functions for some reason... + template Type asinh (Type x) noexcept { return std::log (x + std::sqrt (x * x + (Type) 1)); } + template Type acosh (Type x) noexcept { return std::log (x + std::sqrt (x * x - (Type) 1)); } + template Type atanh (Type x) noexcept { return (std::log (x + (Type) 1) - std::log (((Type) 1) - x)) / (Type) 2; } +#endif + +//============================================================================== +/** A predefined value for Pi, at double-precision. + @see float_Pi +*/ +const double double_Pi = 3.1415926535897932384626433832795; + +/** A predefined value for Pi, at single-precision. + @see double_Pi +*/ +const float float_Pi = 3.14159265358979323846f; + + +//============================================================================== +/** The isfinite() method seems to vary between platforms, so this is a + platform-independent function for it. +*/ +template +inline bool juce_isfinite (FloatingPointType value) +{ + #if JUCE_WINDOWS + return _finite (value); + #elif JUCE_ANDROID + return isfinite (value); + #else + return std::isfinite (value); + #endif +} + +//============================================================================== +#if JUCE_MSVC + #pragma optimize ("t", off) + #ifndef __INTEL_COMPILER + #pragma float_control (precise, on, push) + #endif +#endif + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +template +inline int roundToInt (const FloatType value) noexcept +{ + #ifdef __INTEL_COMPILER + #pragma float_control (precise, on, push) + #endif + + union { int asInt[2]; double asDouble; } n; + n.asDouble = ((double) value) + 6755399441055744.0; + + #if JUCE_BIG_ENDIAN + return n.asInt [1]; + #else + return n.asInt [0]; + #endif +} + +inline int roundToInt (int value) noexcept +{ + return value; +} + +#if JUCE_MSVC + #ifndef __INTEL_COMPILER + #pragma float_control (pop) + #endif + #pragma optimize ("", on) // resets optimisations to the project defaults +#endif + +/** Fast floating-point-to-integer conversion. + + This is a slightly slower and slightly more accurate version of roundDoubleToInt(). It works + fine for values above zero, but negative numbers are rounded the wrong way. +*/ +inline int roundToIntAccurate (const double value) noexcept +{ + #ifdef __INTEL_COMPILER + #pragma float_control (pop) + #endif + + return roundToInt (value + 1.5e-8); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a double to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. For a more accurate conversion, + see roundDoubleToIntAccurate(). +*/ +inline int roundDoubleToInt (const double value) noexcept +{ + return roundToInt (value); +} + +/** Fast floating-point-to-integer conversion. + + This is faster than using the normal c++ cast to convert a float to an int, and + it will round the value to the nearest integer, rather than rounding it down + like the normal cast does. + + Note that this routine gets its speed at the expense of some accuracy, and when + rounding values whose floating point component is exactly 0.5, odd numbers and + even numbers will be rounded up or down differently. +*/ +inline int roundFloatToInt (const float value) noexcept +{ + return roundToInt (value); +} + +//============================================================================== +/** Returns true if the specified integer is a power-of-two. +*/ +template +bool isPowerOfTwo (IntegerType value) +{ + return (value & (value - 1)) == 0; +} + +/** Returns the smallest power-of-two which is equal to or greater than the given integer. +*/ +inline int nextPowerOfTwo (int n) noexcept +{ + --n; + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return n + 1; +} + +/** Returns the number of bits in a 32-bit integer. */ +inline int countNumberOfBits (uint32 n) noexcept +{ + n -= ((n >> 1) & 0x55555555); + n = (((n >> 2) & 0x33333333) + (n & 0x33333333)); + n = (((n >> 4) + n) & 0x0f0f0f0f); + n += (n >> 8); + n += (n >> 16); + return (int) (n & 0x3f); +} + +/** Returns the number of bits in a 64-bit integer. */ +inline int countNumberOfBits (uint64 n) noexcept +{ + return countNumberOfBits ((uint32) n) + countNumberOfBits ((uint32) (n >> 32)); +} + +/** Performs a modulo operation, but can cope with the dividend being negative. + The divisor must be greater than zero. +*/ +template +IntegerType negativeAwareModulo (IntegerType dividend, const IntegerType divisor) noexcept +{ + jassert (divisor > 0); + dividend %= divisor; + return (dividend < 0) ? (dividend + divisor) : dividend; +} + +/** Returns the square of its argument. */ +template +NumericType square (NumericType n) noexcept +{ + return n * n; +} + +//============================================================================== +#if (JUCE_INTEL && JUCE_32BIT) || defined (DOXYGEN) + /** This macro can be applied to a float variable to check whether it contains a denormalised + value, and to normalise it if necessary. + On CPUs that aren't vulnerable to denormalisation problems, this will have no effect. + */ + #define JUCE_UNDENORMALISE(x) x += 1.0f; x -= 1.0f; +#else + #define JUCE_UNDENORMALISE(x) +#endif + +//============================================================================== +/** This namespace contains a few template classes for helping work out class type variations. +*/ +namespace TypeHelpers +{ + #if JUCE_VC8_OR_EARLIER + #define PARAMETER_TYPE(type) const type& + #else + /** The ParameterType struct is used to find the best type to use when passing some kind + of object as a parameter. + + Of course, this is only likely to be useful in certain esoteric template situations. + + Because "typename TypeHelpers::ParameterType::type" is a bit of a mouthful, there's + a PARAMETER_TYPE(SomeClass) macro that you can use to get the same effect. + + E.g. "myFunction (PARAMETER_TYPE (int), PARAMETER_TYPE (MyObject))" + would evaluate to "myfunction (int, const MyObject&)", keeping any primitive types as + pass-by-value, but passing objects as a const reference, to avoid copying. + */ + template struct ParameterType { typedef const Type& type; }; + + #if ! DOXYGEN + template struct ParameterType { typedef Type& type; }; + template struct ParameterType { typedef Type* type; }; + template <> struct ParameterType { typedef char type; }; + template <> struct ParameterType { typedef unsigned char type; }; + template <> struct ParameterType { typedef short type; }; + template <> struct ParameterType { typedef unsigned short type; }; + template <> struct ParameterType { typedef int type; }; + template <> struct ParameterType { typedef unsigned int type; }; + template <> struct ParameterType { typedef long type; }; + template <> struct ParameterType { typedef unsigned long type; }; + template <> struct ParameterType { typedef int64 type; }; + template <> struct ParameterType { typedef uint64 type; }; + template <> struct ParameterType { typedef bool type; }; + template <> struct ParameterType { typedef float type; }; + template <> struct ParameterType { typedef double type; }; + #endif + + /** A helpful macro to simplify the use of the ParameterType template. + @see ParameterType + */ + #define PARAMETER_TYPE(a) typename TypeHelpers::ParameterType::type + #endif + + + /** These templates are designed to take a type, and if it's a double, they return a double + type; for anything else, they return a float type. + */ + template struct SmallestFloatType { typedef float type; }; + template <> struct SmallestFloatType { typedef double type; }; +} + + +//============================================================================== + +#endif // JUCE_MATHSFUNCTIONS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h b/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h new file mode 100644 index 0000000..56424a2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NORMALISABLERANGE_H_INCLUDED +#define JUCE_NORMALISABLERANGE_H_INCLUDED + + +//============================================================================== +/** + Represents a mapping between an arbitrary range of values and a + normalised 0->1 range. + + The properties of the mapping also include an optional snapping interval + and skew-factor. + + @see Range +*/ +template +class NormalisableRange +{ +public: + /** Creates a continuous range that performs a dummy mapping. */ + NormalisableRange() noexcept : start(), end (1), interval(), skew (static_cast (1)) {} + + /** Creates a copy of another range. */ + NormalisableRange (const NormalisableRange& other) noexcept + : start (other.start), end (other.end), + interval (other.interval), skew (other.skew) + { + checkInvariants(); + } + + /** Creates a copy of another range. */ + NormalisableRange& operator= (const NormalisableRange& other) noexcept + { + start = other.start; + end = other.end; + interval = other.interval; + skew = other.skew; + checkInvariants(); + return *this; + } + + /** Creates a NormalisableRange with a given range, interval and skew factor. */ + NormalisableRange (ValueType rangeStart, + ValueType rangeEnd, + ValueType intervalValue, + ValueType skewFactor) noexcept + : start (rangeStart), end (rangeEnd), + interval (intervalValue), skew (skewFactor) + { + checkInvariants(); + } + + /** Creates a NormalisableRange with a given range and interval, but a dummy skew-factor. */ + NormalisableRange (ValueType rangeStart, + ValueType rangeEnd, + ValueType intervalValue) noexcept + : start (rangeStart), end (rangeEnd), + interval (intervalValue), skew (static_cast (1)) + { + checkInvariants(); + } + + /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */ + NormalisableRange (ValueType rangeStart, + ValueType rangeEnd) noexcept + : start (rangeStart), end (rangeEnd), + interval(), skew (static_cast (1)) + { + checkInvariants(); + } + + /** Uses the properties of this mapping to convert a non-normalised value to + its 0->1 representation. + */ + ValueType convertTo0to1 (ValueType v) const noexcept + { + ValueType proportion = (v - start) / (end - start); + + if (skew != static_cast (1)) + proportion = std::pow (proportion, skew); + + return proportion; + } + + /** Uses the properties of this mapping to convert a normalised 0->1 value to + its full-range representation. + */ + ValueType convertFrom0to1 (ValueType proportion) const noexcept + { + if (skew != static_cast (1) && proportion > ValueType()) + proportion = std::exp (std::log (proportion) / skew); + + return start + (end - start) * proportion; + } + + /** Takes a non-normalised value and snaps it based on the interval property of + this NormalisedRange. */ + ValueType snapToLegalValue (ValueType v) const noexcept + { + if (interval > ValueType()) + v = start + interval * std::floor ((v - start) / interval + static_cast (0.5)); + + if (v <= start || end <= start) + return start; + + if (v >= end) + return end; + + return v; + } + + /** The start of the non-normalised range. */ + ValueType start; + + /** The end of the non-normalised range. */ + ValueType end; + + /** The snapping interval that should be used (in non-normalised value). Use 0 for a continuous range. */ + ValueType interval; + + /** An optional skew factor that alters the way values are distribute across the range. + + The skew factor lets you skew the mapping logarithmically so that larger or smaller + values are given a larger proportion of the avilable space. + + A factor of 1.0 has no skewing effect at all. If the factor is < 1.0, the lower end + of the range will fill more of the slider's length; if the factor is > 1.0, the upper + end of the range will be expanded. + */ + ValueType skew; + +private: + void checkInvariants() const + { + jassert (end > start); + jassert (interval >= ValueType()); + jassert (skew > ValueType()); + } +}; + + +#endif // JUCE_NORMALISABLERANGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp new file mode 100644 index 0000000..a196256 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Random::Random (const int64 seedValue) noexcept : seed (seedValue) +{ +} + +Random::Random() : seed (1) +{ + setSeedRandomly(); +} + +Random::~Random() noexcept +{ +} + +void Random::setSeed (const int64 newSeed) noexcept +{ + seed = newSeed; +} + +void Random::combineSeed (const int64 seedValue) noexcept +{ + seed ^= nextInt64() ^ seedValue; +} + +void Random::setSeedRandomly() +{ + static int64 globalSeed = 0; + + combineSeed (globalSeed ^ (int64) (pointer_sized_int) this); + combineSeed (Time::getMillisecondCounter()); + combineSeed (Time::getHighResolutionTicks()); + combineSeed (Time::getHighResolutionTicksPerSecond()); + combineSeed (Time::currentTimeMillis()); + globalSeed ^= seed; +} + +Random& Random::getSystemRandom() noexcept +{ + static Random sysRand; + return sysRand; +} + +//============================================================================== +int Random::nextInt() noexcept +{ + seed = (seed * 0x5deece66dLL + 11) & 0xffffffffffffLL; + + return (int) (seed >> 16); +} + +int Random::nextInt (const int maxValue) noexcept +{ + jassert (maxValue > 0); + return (int) ((((unsigned int) nextInt()) * (uint64) maxValue) >> 32); +} + +int Random::nextInt (Range range) noexcept +{ + return range.getStart() + nextInt (range.getLength()); +} + +int64 Random::nextInt64() noexcept +{ + return (((int64) nextInt()) << 32) | (int64) (uint64) (uint32) nextInt(); +} + +bool Random::nextBool() noexcept +{ + return (nextInt() & 0x40000000) != 0; +} + +float Random::nextFloat() noexcept +{ + return static_cast (nextInt()) / (std::numeric_limits::max() + 1.0f); +} + +double Random::nextDouble() noexcept +{ + return static_cast (nextInt()) / (std::numeric_limits::max() + 1.0); +} + +BigInteger Random::nextLargeNumber (const BigInteger& maximumValue) +{ + BigInteger n; + + do + { + fillBitsRandomly (n, 0, maximumValue.getHighestBit() + 1); + } + while (n >= maximumValue); + + return n; +} + +void Random::fillBitsRandomly (void* const buffer, size_t bytes) +{ + int* d = static_cast (buffer); + + for (; bytes >= sizeof (int); bytes -= sizeof (int)) + *d++ = nextInt(); + + if (bytes > 0) + { + const int lastBytes = nextInt(); + memcpy (d, &lastBytes, bytes); + } +} + +void Random::fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits) +{ + arrayToChange.setBit (startBit + numBits - 1, true); // to force the array to pre-allocate space + + while ((startBit & 31) != 0 && numBits > 0) + { + arrayToChange.setBit (startBit++, nextBool()); + --numBits; + } + + while (numBits >= 32) + { + arrayToChange.setBitRangeAsInt (startBit, 32, (unsigned int) nextInt()); + startBit += 32; + numBits -= 32; + } + + while (--numBits >= 0) + arrayToChange.setBit (startBit + numBits, nextBool()); +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class RandomTests : public UnitTest +{ +public: + RandomTests() : UnitTest ("Random") {} + + void runTest() + { + beginTest ("Random"); + + Random r = getRandom(); + + for (int i = 2000; --i >= 0;) + { + expect (r.nextDouble() >= 0.0 && r.nextDouble() < 1.0); + expect (r.nextFloat() >= 0.0f && r.nextFloat() < 1.0f); + expect (r.nextInt (5) >= 0 && r.nextInt (5) < 5); + expect (r.nextInt (1) == 0); + + int n = r.nextInt (50) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + + n = r.nextInt (0x7ffffffe) + 1; + expect (r.nextInt (n) >= 0 && r.nextInt (n) < n); + } + } +}; + +static RandomTests randomTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Random.h b/JuceLibraryCode/modules/juce_core/maths/juce_Random.h new file mode 100644 index 0000000..19e6d6e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Random.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RANDOM_H_INCLUDED +#define JUCE_RANDOM_H_INCLUDED + + +//============================================================================== +/** + A random number generator. + + You can create a Random object and use it to generate a sequence of random numbers. +*/ +class JUCE_API Random +{ +public: + //============================================================================== + /** Creates a Random object based on a seed value. + + For a given seed value, the subsequent numbers generated by this object + will be predictable, so a good idea is to set this value based + on the time, e.g. + + new Random (Time::currentTimeMillis()) + */ + explicit Random (int64 seedValue) noexcept; + + /** Creates a Random object using a random seed value. + Internally, this calls setSeedRandomly() to randomise the seed. + */ + Random(); + + /** Destructor. */ + ~Random() noexcept; + + /** Returns the next random 32 bit integer. + @returns a random integer from the full range 0x80000000 to 0x7fffffff + */ + int nextInt() noexcept; + + /** Returns the next random number, limited to a given range. + The maxValue parameter may not be negative, or zero. + @returns a random integer between 0 (inclusive) and maxValue (exclusive). + */ + int nextInt (int maxValue) noexcept; + + /** Returns the next random number, limited to a given range. + @returns a random integer between the range start (inclusive) and its end (exclusive). + */ + int nextInt (Range range) noexcept; + + /** Returns the next 64-bit random number. + @returns a random integer from the full range 0x8000000000000000 to 0x7fffffffffffffff + */ + int64 nextInt64() noexcept; + + /** Returns the next random floating-point number. + @returns a random value in the range 0 to 1.0 + */ + float nextFloat() noexcept; + + /** Returns the next random floating-point number. + @returns a random value in the range 0 to 1.0 + */ + double nextDouble() noexcept; + + /** Returns the next random boolean value. */ + bool nextBool() noexcept; + + /** Returns a BigInteger containing a random number. + @returns a random value in the range 0 to (maximumValue - 1). + */ + BigInteger nextLargeNumber (const BigInteger& maximumValue); + + /** Fills a block of memory with random values. */ + void fillBitsRandomly (void* bufferToFill, size_t sizeInBytes); + + /** Sets a range of bits in a BigInteger to random values. */ + void fillBitsRandomly (BigInteger& arrayToChange, int startBit, int numBits); + + //============================================================================== + /** Resets this Random object to a given seed value. */ + void setSeed (int64 newSeed) noexcept; + + /** Returns the RNG's current seed. */ + int64 getSeed() const noexcept { return seed; } + + /** Merges this object's seed with another value. + This sets the seed to be a value created by combining the current seed and this + new value. + */ + void combineSeed (int64 seedValue) noexcept; + + /** Reseeds this generator using a value generated from various semi-random system + properties like the current time, etc. + + Because this function convolves the time with the last seed value, calling + it repeatedly will increase the randomness of the final result. + */ + void setSeedRandomly(); + + /** The overhead of creating a new Random object is fairly small, but if you want to avoid + it, you can call this method to get a global shared Random object. + + It's not thread-safe though, so threads should use their own Random object, otherwise + you run the risk of your random numbers becoming.. erm.. randomly corrupted.. + */ + static Random& getSystemRandom() noexcept; + +private: + //============================================================================== + int64 seed; + + JUCE_LEAK_DETECTOR (Random) +}; + + +#endif // JUCE_RANDOM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Range.h b/JuceLibraryCode/modules/juce_core/maths/juce_Range.h new file mode 100644 index 0000000..6add152 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Range.h @@ -0,0 +1,304 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RANGE_H_INCLUDED +#define JUCE_RANGE_H_INCLUDED + + +//============================================================================== +/** A general-purpose range object, that simply represents any linear range with + a start and end point. + + Note that when checking whether values fall within the range, the start value is + considered to be inclusive, and the end of the range exclusive. + + The templated parameter is expected to be a primitive integer or floating point + type, though class types could also be used if they behave in a number-like way. +*/ +template +class Range +{ +public: + //============================================================================== + /** Constructs an empty range. */ + Range() noexcept : start(), end() + { + } + + /** Constructs a range with given start and end values. */ + Range (const ValueType startValue, const ValueType endValue) noexcept + : start (startValue), end (jmax (startValue, endValue)) + { + } + + /** Constructs a copy of another range. */ + Range (const Range& other) noexcept + : start (other.start), end (other.end) + { + } + + /** Copies another range object. */ + Range& operator= (Range other) noexcept + { + start = other.start; + end = other.end; + return *this; + } + + /** Returns the range that lies between two positions (in either order). */ + static Range between (const ValueType position1, const ValueType position2) noexcept + { + return position1 < position2 ? Range (position1, position2) + : Range (position2, position1); + } + + /** Returns a range with a given start and length. */ + static Range withStartAndLength (const ValueType startValue, const ValueType length) noexcept + { + jassert (length >= ValueType()); + return Range (startValue, startValue + length); + } + + /** Returns a range with the specified start position and a length of zero. */ + static Range emptyRange (const ValueType start) noexcept + { + return Range (start, start); + } + + //============================================================================== + /** Returns the start of the range. */ + inline ValueType getStart() const noexcept { return start; } + + /** Returns the length of the range. */ + inline ValueType getLength() const noexcept { return end - start; } + + /** Returns the end of the range. */ + inline ValueType getEnd() const noexcept { return end; } + + /** Returns true if the range has a length of zero. */ + inline bool isEmpty() const noexcept { return start == end; } + + //============================================================================== + /** Changes the start position of the range, leaving the end position unchanged. + If the new start position is higher than the current end of the range, the end point + will be pushed along to equal it, leaving an empty range at the new position. + */ + void setStart (const ValueType newStart) noexcept + { + start = newStart; + if (end < newStart) + end = newStart; + } + + /** Returns a range with the same end as this one, but a different start. + If the new start position is higher than the current end of the range, the end point + will be pushed along to equal it, returning an empty range at the new position. + */ + Range withStart (const ValueType newStart) const noexcept + { + return Range (newStart, jmax (newStart, end)); + } + + /** Returns a range with the same length as this one, but moved to have the given start position. */ + Range movedToStartAt (const ValueType newStart) const noexcept + { + return Range (newStart, end + (newStart - start)); + } + + /** Changes the end position of the range, leaving the start unchanged. + If the new end position is below the current start of the range, the start point + will be pushed back to equal the new end point. + */ + void setEnd (const ValueType newEnd) noexcept + { + end = newEnd; + if (newEnd < start) + start = newEnd; + } + + /** Returns a range with the same start position as this one, but a different end. + If the new end position is below the current start of the range, the start point + will be pushed back to equal the new end point. + */ + Range withEnd (const ValueType newEnd) const noexcept + { + return Range (jmin (start, newEnd), newEnd); + } + + /** Returns a range with the same length as this one, but moved to have the given end position. */ + Range movedToEndAt (const ValueType newEnd) const noexcept + { + return Range (start + (newEnd - end), newEnd); + } + + /** Changes the length of the range. + Lengths less than zero are treated as zero. + */ + void setLength (const ValueType newLength) noexcept + { + end = start + jmax (ValueType(), newLength); + } + + /** Returns a range with the same start as this one, but a different length. + Lengths less than zero are treated as zero. + */ + Range withLength (const ValueType newLength) const noexcept + { + return Range (start, start + newLength); + } + + //============================================================================== + /** Adds an amount to the start and end of the range. */ + inline Range operator+= (const ValueType amountToAdd) noexcept + { + start += amountToAdd; + end += amountToAdd; + return *this; + } + + /** Subtracts an amount from the start and end of the range. */ + inline Range operator-= (const ValueType amountToSubtract) noexcept + { + start -= amountToSubtract; + end -= amountToSubtract; + return *this; + } + + /** Returns a range that is equal to this one with an amount added to its + start and end. + */ + Range operator+ (const ValueType amountToAdd) const noexcept + { + return Range (start + amountToAdd, end + amountToAdd); + } + + /** Returns a range that is equal to this one with the specified amount + subtracted from its start and end. */ + Range operator- (const ValueType amountToSubtract) const noexcept + { + return Range (start - amountToSubtract, end - amountToSubtract); + } + + bool operator== (Range other) const noexcept { return start == other.start && end == other.end; } + bool operator!= (Range other) const noexcept { return start != other.start || end != other.end; } + + //============================================================================== + /** Returns true if the given position lies inside this range. */ + bool contains (const ValueType position) const noexcept + { + return start <= position && position < end; + } + + /** Returns the nearest value to the one supplied, which lies within the range. */ + ValueType clipValue (const ValueType value) const noexcept + { + return jlimit (start, end, value); + } + + /** Returns true if the given range lies entirely inside this range. + When making this comparison, the start value is considered to be inclusive, + and the end of the range exclusive. + */ + bool contains (Range other) const noexcept + { + return start <= other.start && end >= other.end; + } + + /** Returns true if the given range intersects this one. */ + bool intersects (Range other) const noexcept + { + return other.start < end && start < other.end; + } + + /** Returns the range that is the intersection of the two ranges, or an empty range + with an undefined start position if they don't overlap. */ + Range getIntersectionWith (Range other) const noexcept + { + return Range (jmax (start, other.start), + jmin (end, other.end)); + } + + /** Returns the smallest range that contains both this one and the other one. */ + Range getUnionWith (Range other) const noexcept + { + return Range (jmin (start, other.start), + jmax (end, other.end)); + } + + /** Returns the smallest range that contains both this one and the given value. */ + Range getUnionWith (const ValueType valueToInclude) const noexcept + { + return Range (jmin (valueToInclude, start), + jmax (valueToInclude, end)); + } + + /** Returns a given range, after moving it forwards or backwards to fit it + within this range. + + If the supplied range has a greater length than this one, the return value + will be this range. + + Otherwise, if the supplied range is smaller than this one, the return value + will be the new range, shifted forwards or backwards so that it doesn't extend + beyond this one, but keeping its original length. + */ + Range constrainRange (Range rangeToConstrain) const noexcept + { + const ValueType otherLen = rangeToConstrain.getLength(); + return getLength() <= otherLen + ? *this + : rangeToConstrain.movedToStartAt (jlimit (start, end - otherLen, rangeToConstrain.getStart())); + } + + /** Scans an array of values for its min and max, and returns these as a Range. */ + static Range findMinAndMax (const ValueType* values, int numValues) noexcept + { + if (numValues <= 0) + return Range(); + + const ValueType first (*values++); + Range r (first, first); + + while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) + { + const ValueType v (*values++); + + if (r.end < v) r.end = v; + if (v < r.start) r.start = v; + } + + return r; + } + +private: + //============================================================================== + ValueType start, end; +}; + + +#endif // JUCE_RANGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_Atomic.h b/JuceLibraryCode/modules/juce_core/memory/juce_Atomic.h new file mode 100644 index 0000000..5e9d30c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_Atomic.h @@ -0,0 +1,399 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ATOMIC_H_INCLUDED +#define JUCE_ATOMIC_H_INCLUDED + + +//============================================================================== +/** + Simple class to hold a primitive value and perform atomic operations on it. + + The type used must be a 32 or 64 bit primitive, like an int, pointer, etc. + There are methods to perform most of the basic atomic operations. +*/ +template +class Atomic +{ +public: + /** Creates a new value, initialised to zero. */ + inline Atomic() noexcept + : value (0) + { + } + + /** Creates a new value, with a given initial value. */ + inline explicit Atomic (const Type initialValue) noexcept + : value (initialValue) + { + } + + /** Copies another value (atomically). */ + inline Atomic (const Atomic& other) noexcept + : value (other.get()) + { + } + + /** Destructor. */ + inline ~Atomic() noexcept + { + // This class can only be used for types which are 32 or 64 bits in size. + static_jassert (sizeof (Type) == 4 || sizeof (Type) == 8); + } + + /** Atomically reads and returns the current value. */ + Type get() const noexcept; + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Atomic& other) noexcept { exchange (other.get()); return *this; } + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Type newValue) noexcept { exchange (newValue); return *this; } + + /** Atomically sets the current value. */ + void set (Type newValue) noexcept { exchange (newValue); } + + /** Atomically sets the current value, returning the value that was replaced. */ + Type exchange (Type value) noexcept; + + /** Atomically adds a number to this value, returning the new value. */ + Type operator+= (Type amountToAdd) noexcept; + + /** Atomically subtracts a number from this value, returning the new value. */ + Type operator-= (Type amountToSubtract) noexcept; + + /** Atomically increments this value, returning the new value. */ + Type operator++() noexcept; + + /** Atomically decrements this value, returning the new value. */ + Type operator--() noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + bool compareAndSetBool (Type newValue, Type valueToCompare) + { + if (get() == valueToCompare) + { + set (newValue); + return true; + } + + return false; + } + @endcode + + @returns true if the comparison was true and the value was replaced; false if + the comparison failed and the value was left unchanged. + @see compareAndSetValue + */ + bool compareAndSetBool (Type newValue, Type valueToCompare) noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + Type compareAndSetValue (Type newValue, Type valueToCompare) + { + Type oldValue = get(); + if (oldValue == valueToCompare) + set (newValue); + + return oldValue; + } + @endcode + + @returns the old value before it was changed. + @see compareAndSetBool + */ + Type compareAndSetValue (Type newValue, Type valueToCompare) noexcept; + + /** Implements a memory read/write barrier. */ + static void memoryBarrier() noexcept; + + //============================================================================== + #if JUCE_64BIT + JUCE_ALIGN (8) + #else + JUCE_ALIGN (4) + #endif + + /** The raw value that this class operates on. + This is exposed publically in case you need to manipulate it directly + for performance reasons. + */ + volatile Type value; + +private: + template + static inline Dest castTo (Source value) noexcept { union { Dest d; Source s; } u; u.s = value; return u.d; } + + static inline Type castFrom32Bit (int32 value) noexcept { return castTo (value); } + static inline Type castFrom64Bit (int64 value) noexcept { return castTo (value); } + static inline int32 castTo32Bit (Type value) noexcept { return castTo (value); } + static inline int64 castTo64Bit (Type value) noexcept { return castTo (value); } + + Type operator++ (int); // better to just use pre-increment with atomics.. + Type operator-- (int); + + /** This templated negate function will negate pointers as well as integers */ + template + inline ValueType negateValue (ValueType n) noexcept + { + return sizeof (ValueType) == 1 ? (ValueType) -(signed char) n + : (sizeof (ValueType) == 2 ? (ValueType) -(short) n + : (sizeof (ValueType) == 4 ? (ValueType) -(int) n + : ((ValueType) -(int64) n))); + } + + /** This templated negate function will negate pointers as well as integers */ + template + inline PointerType* negateValue (PointerType* n) noexcept + { + return reinterpret_cast (-reinterpret_cast (n)); + } +}; + + +//============================================================================== +/* + The following code is in the header so that the atomics can be inlined where possible... +*/ +#if JUCE_MAC && (JUCE_PPC || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2)) + #define JUCE_ATOMICS_MAC_LEGACY 1 // Older OSX builds using gcc4.1 or earlier + + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #define JUCE_MAC_ATOMICS_VOLATILE + #else + #define JUCE_MAC_ATOMICS_VOLATILE volatile + #endif + + #if JUCE_PPC + // None of these atomics are available for PPC or for iOS 3.1 or earlier!! + template static Type OSAtomicAdd64Barrier (Type b, JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return *a += b; } + template static Type OSAtomicIncrement64Barrier (JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return ++*a; } + template static Type OSAtomicDecrement64Barrier (JUCE_MAC_ATOMICS_VOLATILE Type* a) noexcept { jassertfalse; return --*a; } + template static bool OSAtomicCompareAndSwap64Barrier (Type old, Type newValue, JUCE_MAC_ATOMICS_VOLATILE Type* value) noexcept + { jassertfalse; if (old == *value) { *value = newValue; return true; } return false; } + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#elif JUCE_GCC || JUCE_CLANG + #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics + + #if JUCE_IOS || JUCE_ANDROID // (64-bit ops will compile but not link on these mobile OSes) + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif + +//============================================================================== +#else + #define JUCE_ATOMICS_WINDOWS 1 // Windows with intrinsics + + #if JUCE_USE_MSVC_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchange, _InterlockedIncrement, _InterlockedDecrement, _InterlockedCompareExchange, \ + _InterlockedCompareExchange64, _InterlockedExchangeAdd, _ReadWriteBarrier) + #endif + #define juce_InterlockedExchange(a, b) _InterlockedExchange(a, b) + #define juce_InterlockedIncrement(a) _InterlockedIncrement(a) + #define juce_InterlockedDecrement(a) _InterlockedDecrement(a) + #define juce_InterlockedExchangeAdd(a, b) _InterlockedExchangeAdd(a, b) + #define juce_InterlockedCompareExchange(a, b, c) _InterlockedCompareExchange(a, b, c) + #define juce_InterlockedCompareExchange64(a, b, c) _InterlockedCompareExchange64(a, b, c) + #define juce_MemoryBarrier _ReadWriteBarrier + #else + long juce_InterlockedExchange (volatile long* a, long b) noexcept; + long juce_InterlockedIncrement (volatile long* a) noexcept; + long juce_InterlockedDecrement (volatile long* a) noexcept; + long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept; + long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept; + __int64 juce_InterlockedCompareExchange64 (volatile __int64* a, __int64 b, __int64 c) noexcept; + inline void juce_MemoryBarrier() noexcept { long x = 0; juce_InterlockedIncrement (&x); } + #endif + + #if JUCE_64BIT + #ifndef __INTEL_COMPILER + #pragma intrinsic (_InterlockedExchangeAdd64, _InterlockedExchange64, _InterlockedIncrement64, _InterlockedDecrement64) + #endif + #define juce_InterlockedExchangeAdd64(a, b) _InterlockedExchangeAdd64(a, b) + #define juce_InterlockedExchange64(a, b) _InterlockedExchange64(a, b) + #define juce_InterlockedIncrement64(a) _InterlockedIncrement64(a) + #define juce_InterlockedDecrement64(a) _InterlockedDecrement64(a) + #else + // None of these atomics are available in a 32-bit Windows build!! + template static Type juce_InterlockedExchangeAdd64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a += b; return old; } + template static Type juce_InterlockedExchange64 (volatile Type* a, Type b) noexcept { jassertfalse; Type old = *a; *a = b; return old; } + template static Type juce_InterlockedIncrement64 (volatile Type* a) noexcept { jassertfalse; return ++*a; } + template static Type juce_InterlockedDecrement64 (volatile Type* a) noexcept { jassertfalse; return --*a; } + #define JUCE_64BIT_ATOMICS_UNAVAILABLE 1 + #endif +#endif + + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4311) // (truncation warning) +#endif + +//============================================================================== +template +inline Type Atomic::get() const noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + return sizeof (Type) == 4 ? castFrom32Bit ((int32) OSAtomicAdd32Barrier ((int32_t) 0, (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value)) + : castFrom64Bit ((int64) OSAtomicAdd64Barrier ((int64_t) 0, (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value)); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedExchangeAdd ((volatile long*) &value, (long) 0)) + : castFrom64Bit ((int64) juce_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) 0)); + #elif JUCE_ATOMICS_GCC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_add_and_fetch ((volatile int32*) &value, 0)) + : castFrom64Bit ((int64) __sync_add_and_fetch ((volatile int64*) &value, 0)); + #endif +} + +template +inline Type Atomic::exchange (const Type newValue) noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY || JUCE_ATOMICS_GCC + Type currentVal = value; + while (! compareAndSetBool (newValue, currentVal)) { currentVal = value; } + return currentVal; + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedExchange ((volatile long*) &value, (long) castTo32Bit (newValue))) + : castFrom64Bit ((int64) juce_InterlockedExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue))); + #endif +} + +template +inline Type Atomic::operator+= (const Type amountToAdd) noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + return sizeof (Type) == 4 ? (Type) OSAtomicAdd32Barrier ((int32_t) castTo32Bit (amountToAdd), (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicAdd64Barrier ((int64_t) amountToAdd, (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) (juce_InterlockedExchangeAdd ((volatile long*) &value, (long) amountToAdd) + (long) amountToAdd) + : (Type) (juce_InterlockedExchangeAdd64 ((volatile __int64*) &value, (__int64) amountToAdd) + (__int64) amountToAdd); + #elif JUCE_ATOMICS_GCC + return (Type) __sync_add_and_fetch (&value, amountToAdd); + #endif +} + +template +inline Type Atomic::operator-= (const Type amountToSubtract) noexcept +{ + return operator+= (negateValue (amountToSubtract)); +} + +template +inline Type Atomic::operator++() noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + return sizeof (Type) == 4 ? (Type) OSAtomicIncrement32Barrier ((JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicIncrement64Barrier ((JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) juce_InterlockedIncrement ((volatile long*) &value) + : (Type) juce_InterlockedIncrement64 ((volatile __int64*) &value); + #elif JUCE_ATOMICS_GCC + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) 1) + : (Type) __sync_add_and_fetch ((int64_t*) &value, 1); + #endif +} + +template +inline Type Atomic::operator--() noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + return sizeof (Type) == 4 ? (Type) OSAtomicDecrement32Barrier ((JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : (Type) OSAtomicDecrement64Barrier ((JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? (Type) juce_InterlockedDecrement ((volatile long*) &value) + : (Type) juce_InterlockedDecrement64 ((volatile __int64*) &value); + #elif JUCE_ATOMICS_GCC + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) -1) + : (Type) __sync_add_and_fetch ((int64_t*) &value, -1); + #endif +} + +template +inline bool Atomic::compareAndSetBool (const Type newValue, const Type valueToCompare) noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + return sizeof (Type) == 4 ? OSAtomicCompareAndSwap32Barrier ((int32_t) castTo32Bit (valueToCompare), (int32_t) castTo32Bit (newValue), (JUCE_MAC_ATOMICS_VOLATILE int32_t*) &value) + : OSAtomicCompareAndSwap64Barrier ((int64_t) castTo64Bit (valueToCompare), (int64_t) castTo64Bit (newValue), (JUCE_MAC_ATOMICS_VOLATILE int64_t*) &value); + #elif JUCE_ATOMICS_WINDOWS + return compareAndSetValue (newValue, valueToCompare) == valueToCompare; + #elif JUCE_ATOMICS_GCC + return sizeof (Type) == 4 ? __sync_bool_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue)) + : __sync_bool_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue)); + #endif +} + +template +inline Type Atomic::compareAndSetValue (const Type newValue, const Type valueToCompare) noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + for (;;) // Annoying workaround for only having a bool CAS operation.. + { + if (compareAndSetBool (newValue, valueToCompare)) + return valueToCompare; + + const Type result = value; + if (result != valueToCompare) + return result; + } + + #elif JUCE_ATOMICS_WINDOWS + return sizeof (Type) == 4 ? castFrom32Bit ((int32) juce_InterlockedCompareExchange ((volatile long*) &value, (long) castTo32Bit (newValue), (long) castTo32Bit (valueToCompare))) + : castFrom64Bit ((int64) juce_InterlockedCompareExchange64 ((volatile __int64*) &value, (__int64) castTo64Bit (newValue), (__int64) castTo64Bit (valueToCompare))); + #elif JUCE_ATOMICS_GCC + return sizeof (Type) == 4 ? castFrom32Bit ((int32) __sync_val_compare_and_swap ((volatile int32*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue))) + : castFrom64Bit ((int64) __sync_val_compare_and_swap ((volatile int64*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue))); + #endif +} + +template +inline void Atomic::memoryBarrier() noexcept +{ + #if JUCE_ATOMICS_MAC_LEGACY + OSMemoryBarrier(); + #elif JUCE_ATOMICS_GCC + __sync_synchronize(); + #elif JUCE_ATOMICS_WINDOWS + juce_MemoryBarrier(); + #endif +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_ATOMIC_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_ByteOrder.h b/JuceLibraryCode/modules/juce_core/memory/juce_ByteOrder.h new file mode 100644 index 0000000..fcd56bc --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_ByteOrder.h @@ -0,0 +1,196 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BYTEORDER_H_INCLUDED +#define JUCE_BYTEORDER_H_INCLUDED + + +//============================================================================== +/** Contains static methods for converting the byte order between different + endiannesses. +*/ +class JUCE_API ByteOrder +{ +public: + //============================================================================== + /** Swaps the upper and lower bytes of a 16-bit integer. */ + static uint16 swap (uint16 value) noexcept; + + /** Reverses the order of the 4 bytes in a 32-bit integer. */ + static uint32 swap (uint32 value) noexcept; + + /** Reverses the order of the 8 bytes in a 64-bit integer. */ + static uint64 swap (uint64 value) noexcept; + + //============================================================================== + /** Swaps the byte order of a 16-bit int if the CPU is big-endian */ + static uint16 swapIfBigEndian (uint16 value) noexcept; + + /** Swaps the byte order of a 32-bit int if the CPU is big-endian */ + static uint32 swapIfBigEndian (uint32 value) noexcept; + + /** Swaps the byte order of a 64-bit int if the CPU is big-endian */ + static uint64 swapIfBigEndian (uint64 value) noexcept; + + /** Swaps the byte order of a 16-bit int if the CPU is little-endian */ + static uint16 swapIfLittleEndian (uint16 value) noexcept; + + /** Swaps the byte order of a 32-bit int if the CPU is little-endian */ + static uint32 swapIfLittleEndian (uint32 value) noexcept; + + /** Swaps the byte order of a 64-bit int if the CPU is little-endian */ + static uint64 swapIfLittleEndian (uint64 value) noexcept; + + //============================================================================== + /** Turns 4 bytes into a little-endian integer. */ + static uint32 littleEndianInt (const void* bytes) noexcept; + + /** Turns 8 bytes into a little-endian integer. */ + static uint64 littleEndianInt64 (const void* bytes) noexcept; + + /** Turns 2 bytes into a little-endian integer. */ + static uint16 littleEndianShort (const void* bytes) noexcept; + + /** Turns 4 bytes into a big-endian integer. */ + static uint32 bigEndianInt (const void* bytes) noexcept; + + /** Turns 8 bytes into a big-endian integer. */ + static uint64 bigEndianInt64 (const void* bytes) noexcept; + + /** Turns 2 bytes into a big-endian integer. */ + static uint16 bigEndianShort (const void* bytes) noexcept; + + //============================================================================== + /** Converts 3 little-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int littleEndian24Bit (const void* bytes) noexcept; + + /** Converts 3 big-endian bytes into a signed 24-bit value (which is sign-extended to 32 bits). */ + static int bigEndian24Bit (const void* bytes) noexcept; + + /** Copies a 24-bit number to 3 little-endian bytes. */ + static void littleEndian24BitToChars (int value, void* destBytes) noexcept; + + /** Copies a 24-bit number to 3 big-endian bytes. */ + static void bigEndian24BitToChars (int value, void* destBytes) noexcept; + + //============================================================================== + /** Returns true if the current CPU is big-endian. */ + static bool isBigEndian() noexcept; + +private: + ByteOrder() JUCE_DELETED_FUNCTION; + + JUCE_DECLARE_NON_COPYABLE (ByteOrder) +}; + + +//============================================================================== +#if JUCE_USE_MSVC_INTRINSICS && ! defined (__INTEL_COMPILER) + #pragma intrinsic (_byteswap_ulong) +#endif + +inline uint16 ByteOrder::swap (uint16 n) noexcept +{ + #if JUCE_USE_MSVC_INTRINSICSxxx // agh - the MS compiler has an internal error when you try to use this intrinsic! + return static_cast (_byteswap_ushort (n)); + #else + return static_cast ((n << 8) | (n >> 8)); + #endif +} + +inline uint32 ByteOrder::swap (uint32 n) noexcept +{ + #if JUCE_MAC || JUCE_IOS + return OSSwapInt32 (n); + #elif JUCE_GCC && JUCE_INTEL && ! JUCE_NO_INLINE_ASM + asm("bswap %%eax" : "=a"(n) : "a"(n)); + return n; + #elif JUCE_USE_MSVC_INTRINSICS + return _byteswap_ulong (n); + #elif JUCE_MSVC && ! JUCE_NO_INLINE_ASM + __asm { + mov eax, n + bswap eax + mov n, eax + } + return n; + #elif JUCE_ANDROID + return bswap_32 (n); + #else + return (n << 24) | (n >> 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8); + #endif +} + +inline uint64 ByteOrder::swap (uint64 value) noexcept +{ + #if JUCE_MAC || JUCE_IOS + return OSSwapInt64 (value); + #elif JUCE_USE_MSVC_INTRINSICS + return _byteswap_uint64 (value); + #else + return (((int64) swap ((uint32) value)) << 32) | swap ((uint32) (value >> 32)); + #endif +} + +#if JUCE_LITTLE_ENDIAN + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return v; } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return v; } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return v; } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return swap (v); } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return swap (v); } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return swap (v); } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint64 ByteOrder::bigEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline bool ByteOrder::isBigEndian() noexcept { return false; } +#else + inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return swap (v); } + inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return swap (v); } + inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return swap (v); } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return v; } + inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return v; } + inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return v; } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } + inline uint32 ByteOrder::bigEndianInt (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint64 ByteOrder::bigEndianInt64 (const void* const bytes) noexcept { return *static_cast (bytes); } + inline uint16 ByteOrder::bigEndianShort (const void* const bytes) noexcept { return *static_cast (bytes); } + inline bool ByteOrder::isBigEndian() noexcept { return true; } +#endif + +inline int ByteOrder::littleEndian24Bit (const void* const bytes) noexcept { return (((int) static_cast (bytes)[2]) << 16) | (((int) static_cast (bytes)[1]) << 8) | ((int) static_cast (bytes)[0]); } +inline int ByteOrder::bigEndian24Bit (const void* const bytes) noexcept { return (((int) static_cast (bytes)[0]) << 16) | (((int) static_cast (bytes)[1]) << 8) | ((int) static_cast (bytes)[2]); } +inline void ByteOrder::littleEndian24BitToChars (const int value, void* const destBytes) noexcept { static_cast (destBytes)[0] = (uint8) value; static_cast (destBytes)[1] = (uint8) (value >> 8); static_cast (destBytes)[2] = (uint8) (value >> 16); } +inline void ByteOrder::bigEndian24BitToChars (const int value, void* const destBytes) noexcept { static_cast (destBytes)[0] = (uint8) (value >> 16); static_cast (destBytes)[1] = (uint8) (value >> 8); static_cast (destBytes)[2] = (uint8) value; } + + +#endif // JUCE_BYTEORDER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_ContainerDeletePolicy.h b/JuceLibraryCode/modules/juce_core/memory/juce_ContainerDeletePolicy.h new file mode 100644 index 0000000..60cddaa --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_ContainerDeletePolicy.h @@ -0,0 +1,53 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CONTAINERDELETEPOLICY_H_INCLUDED +#define JUCE_CONTAINERDELETEPOLICY_H_INCLUDED + +//============================================================================== +/** + Used by container classes as an indirect way to delete an object of a + particular type. + + The generic implementation of this class simply calls 'delete', but you can + create a specialised version of it for a particular class if you need to + delete that type of object in a more appropriate way. + + @see ScopedPointer, OwnedArray +*/ +template +struct ContainerDeletePolicy +{ + static void destroy (ObjectType* object) + { + delete object; + } +}; + + +#endif // JUCE_CONTAINERDELETEPOLICY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_HeapBlock.h b/JuceLibraryCode/modules/juce_core/memory/juce_HeapBlock.h new file mode 100644 index 0000000..3731f0b --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_HeapBlock.h @@ -0,0 +1,308 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HEAPBLOCK_H_INCLUDED +#define JUCE_HEAPBLOCK_H_INCLUDED + +#ifndef DOXYGEN +namespace HeapBlockHelper +{ + template + struct ThrowOnFail { static void check (void*) {} }; + + template<> + struct ThrowOnFail { static void check (void* data) { if (data == nullptr) throw std::bad_alloc(); } }; +} +#endif + +//============================================================================== +/** + Very simple container class to hold a pointer to some data on the heap. + + When you need to allocate some heap storage for something, always try to use + this class instead of allocating the memory directly using malloc/free. + + A HeapBlock object can be treated in pretty much exactly the same way + as an char*, but as long as you allocate it on the stack or as a class member, + it's almost impossible for it to leak memory. + + It also makes your code much more concise and readable than doing the same thing + using direct allocations, + + E.g. instead of this: + @code + int* temp = (int*) malloc (1024 * sizeof (int)); + memcpy (temp, xyz, 1024 * sizeof (int)); + free (temp); + temp = (int*) calloc (2048 * sizeof (int)); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + free (temp); + @endcode + + ..you could just write this: + @code + HeapBlock temp (1024); + memcpy (temp, xyz, 1024 * sizeof (int)); + temp.calloc (2048); + temp[0] = 1234; + memcpy (foobar, temp, 2048 * sizeof (int)); + @endcode + + The class is extremely lightweight, containing only a pointer to the + data, and exposes malloc/realloc/calloc/free methods that do the same jobs + as their less object-oriented counterparts. Despite adding safety, you probably + won't sacrifice any performance by using this in place of normal pointers. + + The throwOnFailure template parameter can be set to true if you'd like the class + to throw a std::bad_alloc exception when an allocation fails. If this is false, + then a failed allocation will just leave the heapblock with a null pointer (assuming + that the system's malloc() function doesn't throw). + + @see Array, OwnedArray, MemoryBlock +*/ +template +class HeapBlock +{ +public: + //============================================================================== + /** Creates a HeapBlock which is initially just a null pointer. + + After creation, you can resize the array using the malloc(), calloc(), + or realloc() methods. + */ + HeapBlock() noexcept : data (nullptr) + { + } + + /** Creates a HeapBlock containing a number of elements. + + The contents of the block are undefined, as it will have been created by a + malloc call. + + If you want an array of zero values, you can use the calloc() method or the + other constructor that takes an InitialisationState parameter. + */ + explicit HeapBlock (const size_t numElements) + : data (static_cast (std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Creates a HeapBlock containing a number of elements. + + The initialiseToZero parameter determines whether the new memory should be cleared, + or left uninitialised. + */ + HeapBlock (const size_t numElements, const bool initialiseToZero) + : data (static_cast (initialiseToZero + ? std::calloc (numElements, sizeof (ElementType)) + : std::malloc (numElements * sizeof (ElementType)))) + { + throwOnAllocationFailure(); + } + + /** Destructor. + This will free the data, if any has been allocated. + */ + ~HeapBlock() + { + std::free (data); + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + HeapBlock (HeapBlock&& other) noexcept + : data (other.data) + { + other.data = nullptr; + } + + HeapBlock& operator= (HeapBlock&& other) noexcept + { + std::swap (data, other.data); + return *this; + } + #endif + + //============================================================================== + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator ElementType*() const noexcept { return data; } + + /** Returns a raw pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline ElementType* getData() const noexcept { return data; } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator void*() const noexcept { return static_cast (data); } + + /** Returns a void pointer to the allocated data. + This may be a null pointer if the data hasn't yet been allocated, or if it has been + freed by calling the free() method. + */ + inline operator const void*() const noexcept { return static_cast (data); } + + /** Lets you use indirect calls to the first element in the array. + Obviously this will cause problems if the array hasn't been initialised, because it'll + be referencing a null pointer. + */ + inline ElementType* operator->() const noexcept { return data; } + + /** Returns a reference to one of the data elements. + Obviously there's no bounds-checking here, as this object is just a dumb pointer and + has no idea of the size it currently has allocated. + */ + template + inline ElementType& operator[] (IndexType index) const noexcept { return data [index]; } + + /** Returns a pointer to a data element at an offset from the start of the array. + This is the same as doing pointer arithmetic on the raw pointer itself. + */ + template + inline ElementType* operator+ (IndexType index) const noexcept { return data + index; } + + //============================================================================== + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator== (const ElementType* const otherPointer) const noexcept { return otherPointer == data; } + + /** Compares the pointer with another pointer. + This can be handy for checking whether this is a null pointer. + */ + inline bool operator!= (const ElementType* const otherPointer) const noexcept { return otherPointer != data; } + + //============================================================================== + /** Allocates a specified amount of memory. + + This uses the normal malloc to allocate an amount of memory for this object. + Any previously allocated memory will be freed by this method. + + The number of bytes allocated will be (newNumElements * elementSize). Normally + you wouldn't need to specify the second parameter, but it can be handy if you need + to allocate a size in bytes rather than in terms of the number of elements. + + The data that is allocated will be freed when this object is deleted, or when you + call free() or any of the allocation methods. + */ + void malloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::malloc (newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and clears it. + This does the same job as the malloc() method, but clears the memory that it allocates. + */ + void calloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + std::free (data); + data = static_cast (std::calloc (newNumElements, elementSize)); + throwOnAllocationFailure(); + } + + /** Allocates a specified amount of memory and optionally clears it. + This does the same job as either malloc() or calloc(), depending on the + initialiseToZero parameter. + */ + void allocate (const size_t newNumElements, bool initialiseToZero) + { + std::free (data); + data = static_cast (initialiseToZero + ? std::calloc (newNumElements, sizeof (ElementType)) + : std::malloc (newNumElements * sizeof (ElementType))); + throwOnAllocationFailure(); + } + + /** Re-allocates a specified amount of memory. + + The semantics of this method are the same as malloc() and calloc(), but it + uses realloc() to keep as much of the existing data as possible. + */ + void realloc (const size_t newNumElements, const size_t elementSize = sizeof (ElementType)) + { + data = static_cast (data == nullptr ? std::malloc (newNumElements * elementSize) + : std::realloc (data, newNumElements * elementSize)); + throwOnAllocationFailure(); + } + + /** Frees any currently-allocated data. + This will free the data and reset this object to be a null pointer. + */ + void free() + { + std::free (data); + data = nullptr; + } + + /** Swaps this object's data with the data of another HeapBlock. + The two objects simply exchange their data pointers. + */ + template + void swapWith (HeapBlock & other) noexcept + { + std::swap (data, other.data); + } + + /** This fills the block with zeros, up to the number of elements specified. + Since the block has no way of knowing its own size, you must make sure that the number of + elements you specify doesn't exceed the allocated size. + */ + void clear (size_t numElements) noexcept + { + zeromem (data, sizeof (ElementType) * numElements); + } + + /** This typedef can be used to get the type of the heapblock's elements. */ + typedef ElementType Type; + +private: + //============================================================================== + ElementType* data; + + void throwOnAllocationFailure() const + { + HeapBlockHelper::ThrowOnFail::check (data); + } + + #if ! (defined (JUCE_DLL) || defined (JUCE_DLL_BUILD)) + JUCE_DECLARE_NON_COPYABLE (HeapBlock) + JUCE_PREVENT_HEAP_ALLOCATION // Creating a 'new HeapBlock' would be missing the point! + #endif +}; + + +#endif // JUCE_HEAPBLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_LeakedObjectDetector.h b/JuceLibraryCode/modules/juce_core/memory/juce_LeakedObjectDetector.h new file mode 100644 index 0000000..248e7bc --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_LeakedObjectDetector.h @@ -0,0 +1,146 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED +#define JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED + + +//============================================================================== +/** + Embedding an instance of this class inside another class can be used as a low-overhead + way of detecting leaked instances. + + This class keeps an internal static count of the number of instances that are + active, so that when the app is shutdown and the static destructors are called, + it can check whether there are any left-over instances that may have been leaked. + + To use it, use the JUCE_LEAK_DETECTOR macro as a simple way to put one in your + class declaration. Have a look through the juce codebase for examples, it's used + in most of the classes. +*/ +template +class LeakedObjectDetector +{ +public: + //============================================================================== + LeakedObjectDetector() noexcept { ++(getCounter().numObjects); } + LeakedObjectDetector (const LeakedObjectDetector&) noexcept { ++(getCounter().numObjects); } + + ~LeakedObjectDetector() + { + if (--(getCounter().numObjects) < 0) + { + DBG ("*** Dangling pointer deletion! Class: " << getLeakedObjectClassName()); + + /** If you hit this, then you've managed to delete more instances of this class than you've + created.. That indicates that you're deleting some dangling pointers. + + Note that although this assertion will have been triggered during a destructor, it might + not be this particular deletion that's at fault - the incorrect one may have happened + at an earlier point in the program, and simply not been detected until now. + + Most errors like this are caused by using old-fashioned, non-RAII techniques for + your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, + ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! + */ + jassertfalse; + } + } + +private: + //============================================================================== + class LeakCounter + { + public: + LeakCounter() noexcept {} + + ~LeakCounter() + { + if (numObjects.value > 0) + { + DBG ("*** Leaked objects detected: " << numObjects.value << " instance(s) of class " << getLeakedObjectClassName()); + + /** If you hit this, then you've leaked one or more objects of the type specified by + the 'OwnerClass' template parameter - the name should have been printed by the line above. + + If you're leaking, it's probably because you're using old-fashioned, non-RAII techniques for + your object management. Tut, tut. Always, always use ScopedPointers, OwnedArrays, + ReferenceCountedObjects, etc, and avoid the 'delete' operator at all costs! + */ + jassertfalse; + } + } + + Atomic numObjects; + }; + + static const char* getLeakedObjectClassName() + { + return OwnerClass::getLeakedObjectClassName(); + } + + static LeakCounter& getCounter() noexcept + { + static LeakCounter counter; + return counter; + } +}; + +//============================================================================== +#if DOXYGEN || ! defined (JUCE_LEAK_DETECTOR) + #if (DOXYGEN || JUCE_CHECK_MEMORY_LEAKS) + /** This macro lets you embed a leak-detecting object inside a class. + + To use it, simply declare a JUCE_LEAK_DETECTOR(YourClassName) inside a private section + of the class declaration. E.g. + + @code + class MyClass + { + public: + MyClass(); + void blahBlah(); + + private: + JUCE_LEAK_DETECTOR (MyClass) + }; + @endcode + + @see JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR, LeakedObjectDetector + */ + #define JUCE_LEAK_DETECTOR(OwnerClass) \ + friend class juce::LeakedObjectDetector; \ + static const char* getLeakedObjectClassName() noexcept { return #OwnerClass; } \ + juce::LeakedObjectDetector JUCE_JOIN_MACRO (leakDetector, __LINE__); + #else + #define JUCE_LEAK_DETECTOR(OwnerClass) + #endif +#endif + + +#endif // JUCE_LEAKEDOBJECTDETECTOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h b/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h new file mode 100644 index 0000000..57d1644 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h @@ -0,0 +1,126 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORY_H_INCLUDED +#define JUCE_MEMORY_H_INCLUDED + +//============================================================================== +/** Fills a block of memory with zeros. */ +inline void zeromem (void* memory, size_t numBytes) noexcept { memset (memory, 0, numBytes); } + +/** Overwrites a structure or object with zeros. */ +template +inline void zerostruct (Type& structure) noexcept { memset (&structure, 0, sizeof (structure)); } + +/** Delete an object pointer, and sets the pointer to null. + + Remember that it's not good c++ practice to use delete directly - always try to use a ScopedPointer + or other automatic lifetime-management system rather than resorting to deleting raw pointers! +*/ +template +inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } + +/** A handy function which adds a number of bytes to any type of pointer and returns the result. + This can be useful to avoid casting pointers to a char* and back when you want to move them by + a specific number of bytes, +*/ +template +inline Type* addBytesToPointer (Type* pointer, IntegerType bytes) noexcept { return (Type*) (((char*) pointer) + bytes); } + +/** A handy function which returns the difference between any two pointers, in bytes. + The address of the second pointer is subtracted from the first, and the difference in bytes is returned. +*/ +template +inline int getAddressDifference (Type1* pointer1, Type2* pointer2) noexcept { return (int) (((const char*) pointer1) - (const char*) pointer2); } + +/** If a pointer is non-null, this returns a new copy of the object that it points to, or safely returns + nullptr if the pointer is null. +*/ +template +inline Type* createCopyIfNotNull (const Type* pointer) { return pointer != nullptr ? new Type (*pointer) : nullptr; } + +//============================================================================== +#if JUCE_MAC || JUCE_IOS || DOXYGEN + + /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. + You should use the JUCE_AUTORELEASEPOOL macro to create a local auto-release pool on the stack. + */ + class JUCE_API ScopedAutoReleasePool + { + public: + ScopedAutoReleasePool(); + ~ScopedAutoReleasePool(); + + private: + void* pool; + + JUCE_DECLARE_NON_COPYABLE (ScopedAutoReleasePool) + }; + + /** A macro that can be used to easily declare a local ScopedAutoReleasePool + object for RAII-based obj-C autoreleasing. + Because this may use the \@autoreleasepool syntax, you must follow the macro with + a set of braces to mark the scope of the pool. + */ +#if (JUCE_COMPILER_SUPPORTS_ARC && defined (__OBJC__)) || DOXYGEN + #define JUCE_AUTORELEASEPOOL @autoreleasepool +#else + #define JUCE_AUTORELEASEPOOL const juce::ScopedAutoReleasePool JUCE_JOIN_MACRO (autoReleasePool_, __LINE__); +#endif + +#else + #define JUCE_AUTORELEASEPOOL +#endif + +//============================================================================== +/* In a Windows DLL build, we'll expose some malloc/free functions that live inside the DLL, and use these for + allocating all the objects - that way all juce objects in the DLL and in the host will live in the same heap, + avoiding problems when an object is created in one module and passed across to another where it is deleted. + By piggy-backing on the JUCE_LEAK_DETECTOR macro, these allocators can be injected into most juce classes. +*/ +#if JUCE_MSVC && (defined (JUCE_DLL) || defined (JUCE_DLL_BUILD)) && ! (JUCE_DISABLE_DLL_ALLOCATORS || DOXYGEN) + extern JUCE_API void* juceDLL_malloc (size_t); + extern JUCE_API void juceDLL_free (void*); + + #define JUCE_LEAK_DETECTOR(OwnerClass) public:\ + static void* operator new (size_t sz) { return juce::juceDLL_malloc (sz); } \ + static void* operator new (size_t, void* p) { return p; } \ + static void operator delete (void* p) { juce::juceDLL_free (p); } \ + static void operator delete (void*, void*) {} +#endif + +//============================================================================== +/** (Deprecated) This was a Windows-specific way of checking for object leaks - now please + use the JUCE_LEAK_DETECTOR instead. +*/ +#ifndef juce_UseDebuggingNewOperator + #define juce_UseDebuggingNewOperator +#endif + + +#endif // JUCE_MEMORY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.cpp b/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.cpp new file mode 100644 index 0000000..6097c7e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.cpp @@ -0,0 +1,416 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +MemoryBlock::MemoryBlock() noexcept + : size (0) +{ +} + +MemoryBlock::MemoryBlock (const size_t initialSize, const bool initialiseToZero) +{ + if (initialSize > 0) + { + size = initialSize; + data.allocate (initialSize, initialiseToZero); + } + else + { + size = 0; + } +} + +MemoryBlock::MemoryBlock (const MemoryBlock& other) + : size (other.size) +{ + if (size > 0) + { + jassert (other.data != nullptr); + data.malloc (size); + memcpy (data, other.data, size); + } +} + +MemoryBlock::MemoryBlock (const void* const dataToInitialiseFrom, const size_t sizeInBytes) + : size (sizeInBytes) +{ + jassert (((ssize_t) sizeInBytes) >= 0); + + if (size > 0) + { + jassert (dataToInitialiseFrom != nullptr); // non-zero size, but a zero pointer passed-in? + + data.malloc (size); + + if (dataToInitialiseFrom != nullptr) + memcpy (data, dataToInitialiseFrom, size); + } +} + +MemoryBlock::~MemoryBlock() noexcept +{ +} + +MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) +{ + if (this != &other) + { + setSize (other.size, false); + memcpy (data, other.data, size); + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +MemoryBlock::MemoryBlock (MemoryBlock&& other) noexcept + : data (static_cast&&> (other.data)), + size (other.size) +{ +} + +MemoryBlock& MemoryBlock::operator= (MemoryBlock&& other) noexcept +{ + data = static_cast&&> (other.data); + size = other.size; + return *this; +} +#endif + + +//============================================================================== +bool MemoryBlock::operator== (const MemoryBlock& other) const noexcept +{ + return matches (other.data, other.size); +} + +bool MemoryBlock::operator!= (const MemoryBlock& other) const noexcept +{ + return ! operator== (other); +} + +bool MemoryBlock::matches (const void* dataToCompare, size_t dataSize) const noexcept +{ + return size == dataSize + && memcmp (data, dataToCompare, size) == 0; +} + +//============================================================================== +// this will resize the block to this size +void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) +{ + if (size != newSize) + { + if (newSize <= 0) + { + reset(); + } + else + { + if (data != nullptr) + { + data.realloc (newSize); + + if (initialiseToZero && (newSize > size)) + zeromem (data + size, newSize - size); + } + else + { + data.allocate (newSize, initialiseToZero); + } + + size = newSize; + } + } +} + +void MemoryBlock::reset() +{ + data.free(); + size = 0; +} + +void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) +{ + if (size < minimumSize) + setSize (minimumSize, initialiseToZero); +} + +void MemoryBlock::swapWith (MemoryBlock& other) noexcept +{ + std::swap (size, other.size); + data.swapWith (other.data); +} + +//============================================================================== +void MemoryBlock::fillWith (const uint8 value) noexcept +{ + memset (data, (int) value, size); +} + +void MemoryBlock::append (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + const size_t oldSize = size; + setSize (size + numBytes); + memcpy (data + oldSize, srcData, numBytes); + } +} + +void MemoryBlock::replaceWith (const void* const srcData, const size_t numBytes) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + setSize (numBytes); + memcpy (data, srcData, numBytes); + } +} + +void MemoryBlock::insert (const void* const srcData, const size_t numBytes, size_t insertPosition) +{ + if (numBytes > 0) + { + jassert (srcData != nullptr); // this must not be null! + insertPosition = jmin (size, insertPosition); + const size_t trailingDataSize = size - insertPosition; + setSize (size + numBytes, false); + + if (trailingDataSize > 0) + memmove (data + insertPosition + numBytes, + data + insertPosition, + trailingDataSize); + + memcpy (data + insertPosition, srcData, numBytes); + } +} + +void MemoryBlock::removeSection (const size_t startByte, const size_t numBytesToRemove) +{ + if (startByte + numBytesToRemove >= size) + { + setSize (startByte); + } + else if (numBytesToRemove > 0) + { + memmove (data + startByte, + data + startByte + numBytesToRemove, + size - (startByte + numBytesToRemove)); + + setSize (size - numBytesToRemove); + } +} + +void MemoryBlock::copyFrom (const void* const src, int offset, size_t num) noexcept +{ + const char* d = static_cast (src); + + if (offset < 0) + { + d -= offset; + num += (size_t) -offset; + offset = 0; + } + + if ((size_t) offset + num > size) + num = size - (size_t) offset; + + if (num > 0) + memcpy (data + offset, d, num); +} + +void MemoryBlock::copyTo (void* const dst, int offset, size_t num) const noexcept +{ + char* d = static_cast (dst); + + if (offset < 0) + { + zeromem (d, (size_t) -offset); + d -= offset; + num -= (size_t) -offset; + offset = 0; + } + + if ((size_t) offset + num > size) + { + const size_t newNum = size - (size_t) offset; + zeromem (d + newNum, num - newNum); + num = newNum; + } + + if (num > 0) + memcpy (d, data + offset, num); +} + +String MemoryBlock::toString() const +{ + return String::fromUTF8 (data, (int) size); +} + +//============================================================================== +int MemoryBlock::getBitRange (const size_t bitRangeStart, size_t numBits) const noexcept +{ + int res = 0; + + size_t byte = bitRangeStart >> 3; + size_t offsetInByte = bitRangeStart & 7; + size_t bitsSoFar = 0; + + while (numBits > 0 && (size_t) byte < size) + { + const size_t bitsThisTime = jmin (numBits, 8 - offsetInByte); + const int mask = (0xff >> (8 - bitsThisTime)) << offsetInByte; + + res |= (((data[byte] & mask) >> offsetInByte) << bitsSoFar); + + bitsSoFar += bitsThisTime; + numBits -= bitsThisTime; + ++byte; + offsetInByte = 0; + } + + return res; +} + +void MemoryBlock::setBitRange (const size_t bitRangeStart, size_t numBits, int bitsToSet) noexcept +{ + size_t byte = bitRangeStart >> 3; + size_t offsetInByte = bitRangeStart & 7; + uint32 mask = ~((((uint32) 0xffffffff) << (32 - numBits)) >> (32 - numBits)); + + while (numBits > 0 && (size_t) byte < size) + { + const size_t bitsThisTime = jmin (numBits, 8 - offsetInByte); + + const uint32 tempMask = (mask << offsetInByte) | ~((((uint32) 0xffffffff) >> offsetInByte) << offsetInByte); + const uint32 tempBits = (uint32) bitsToSet << offsetInByte; + + data[byte] = (char) (((uint32) data[byte] & tempMask) | tempBits); + + ++byte; + numBits -= bitsThisTime; + bitsToSet >>= bitsThisTime; + mask >>= bitsThisTime; + offsetInByte = 0; + } +} + +//============================================================================== +void MemoryBlock::loadFromHexString (StringRef hex) +{ + ensureSize ((size_t) hex.length() >> 1); + char* dest = data; + String::CharPointerType t (hex.text); + + for (;;) + { + int byte = 0; + + for (int loop = 2; --loop >= 0;) + { + byte <<= 4; + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + + if (c >= '0' && c <= '9') { byte |= c - '0'; break; } + if (c >= 'a' && c <= 'z') { byte |= c - ('a' - 10); break; } + if (c >= 'A' && c <= 'Z') { byte |= c - ('A' - 10); break; } + + if (c == 0) + { + setSize (static_cast (dest - data)); + return; + } + } + } + + *dest++ = (char) byte; + } +} + +//============================================================================== +static const char base64EncodingTable[] = ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; + +String MemoryBlock::toBase64Encoding() const +{ + const size_t numChars = ((size << 3) + 5) / 6; + + String destString ((unsigned int) size); // store the length, followed by a '.', and then the data. + const int initialLen = destString.length(); + destString.preallocateBytes (sizeof (String::CharPointerType::CharType) * (size_t) initialLen + 2 + numChars); + + String::CharPointerType d (destString.getCharPointer()); + d += initialLen; + d.write ('.'); + + for (size_t i = 0; i < numChars; ++i) + d.write ((juce_wchar) (uint8) base64EncodingTable [getBitRange (i * 6, 6)]); + + d.writeNull(); + return destString; +} + +static const char base64DecodingTable[] = +{ + 63, 0, 0, 0, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 0, 0, 0, 0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 +}; + +bool MemoryBlock::fromBase64Encoding (StringRef s) +{ + String::CharPointerType dot (CharacterFunctions::find (s.text, (juce_wchar) '.')); + + if (dot.isEmpty()) + return false; + + const int numBytesNeeded = String (s.text, dot).getIntValue(); + + setSize ((size_t) numBytesNeeded, true); + + String::CharPointerType srcChars (dot + 1); + int pos = 0; + + for (;;) + { + int c = (int) srcChars.getAndAdvance(); + + if (c == 0) + return true; + + c -= 43; + if (isPositiveAndBelow (c, numElementsInArray (base64DecodingTable))) + { + setBitRange ((size_t) pos, 6, base64DecodingTable [c]); + pos += 6; + } + } +} diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.h b/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.h new file mode 100644 index 0000000..30bb3ca --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_MemoryBlock.h @@ -0,0 +1,253 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYBLOCK_H_INCLUDED +#define JUCE_MEMORYBLOCK_H_INCLUDED + + +//============================================================================== +/** + A class to hold a resizable block of raw data. + +*/ +class JUCE_API MemoryBlock +{ +public: + //============================================================================== + /** Create an uninitialised block with 0 size. */ + MemoryBlock() noexcept; + + /** Creates a memory block with a given initial size. + + @param initialSize the size of block to create + @param initialiseToZero whether to clear the memory or just leave it uninitialised + */ + MemoryBlock (const size_t initialSize, + bool initialiseToZero = false); + + /** Creates a copy of another memory block. */ + MemoryBlock (const MemoryBlock&); + + /** Creates a memory block using a copy of a block of data. + + @param dataToInitialiseFrom some data to copy into this block + @param sizeInBytes how much space to use + */ + MemoryBlock (const void* dataToInitialiseFrom, size_t sizeInBytes); + + /** Destructor. */ + ~MemoryBlock() noexcept; + + /** Copies another memory block onto this one. + This block will be resized and copied to exactly match the other one. + */ + MemoryBlock& operator= (const MemoryBlock&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + MemoryBlock (MemoryBlock&&) noexcept; + MemoryBlock& operator= (MemoryBlock&&) noexcept; + #endif + + //============================================================================== + /** Compares two memory blocks. + @returns true only if the two blocks are the same size and have identical contents. + */ + bool operator== (const MemoryBlock& other) const noexcept; + + /** Compares two memory blocks. + @returns true if the two blocks are different sizes or have different contents. + */ + bool operator!= (const MemoryBlock& other) const noexcept; + + /** Returns true if the data in this MemoryBlock matches the raw bytes passed-in. */ + bool matches (const void* data, size_t dataSize) const noexcept; + + //============================================================================== + /** Returns a void pointer to the data. + + Note that the pointer returned will probably become invalid when the + block is resized. + */ + void* getData() const noexcept { return data; } + + /** Returns a byte from the memory block. + This returns a reference, so you can also use it to set a byte. + */ + template + char& operator[] (const Type offset) const noexcept { return data [offset]; } + + + //============================================================================== + /** Returns the block's current allocated size, in bytes. */ + size_t getSize() const noexcept { return size; } + + /** Resizes the memory block. + + Any data that is present in both the old and new sizes will be retained. + When enlarging the block, the new space that is allocated at the end can either be + cleared, or left uninitialised. + + @param newSize the new desired size for the block + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see ensureSize + */ + void setSize (const size_t newSize, + bool initialiseNewSpaceToZero = false); + + /** Increases the block's size only if it's smaller than a given size. + + @param minimumSize if the block is already bigger than this size, no action + will be taken; otherwise it will be increased to this size + @param initialiseNewSpaceToZero if the block gets enlarged, this determines + whether to clear the new section or just leave it + uninitialised + @see setSize + */ + void ensureSize (const size_t minimumSize, + bool initialiseNewSpaceToZero = false); + + /** Frees all the blocks data, setting its size to 0. */ + void reset(); + + //============================================================================== + /** Fills the entire memory block with a repeated byte value. + This is handy for clearing a block of memory to zero. + */ + void fillWith (uint8 valueToUse) noexcept; + + /** Adds another block of data to the end of this one. + The data pointer must not be null. This block's size will be increased accordingly. + */ + void append (const void* data, size_t numBytes); + + /** Resizes this block to the given size and fills its contents from the supplied buffer. + The data pointer must not be null. + */ + void replaceWith (const void* data, size_t numBytes); + + /** Inserts some data into the block. + The dataToInsert pointer must not be null. This block's size will be increased accordingly. + If the insert position lies outside the valid range of the block, it will be clipped to + within the range before being used. + */ + void insert (const void* dataToInsert, size_t numBytesToInsert, size_t insertPosition); + + /** Chops out a section of the block. + + This will remove a section of the memory block and close the gap around it, + shifting any subsequent data downwards and reducing the size of the block. + + If the range specified goes beyond the size of the block, it will be clipped. + */ + void removeSection (size_t startByte, size_t numBytesToRemove); + + //============================================================================== + /** Copies data into this MemoryBlock from a memory address. + + @param srcData the memory location of the data to copy into this block + @param destinationOffset the offset in this block at which the data being copied should begin + @param numBytes how much to copy in (if this goes beyond the size of the memory block, + it will be clipped so not to do anything nasty) + */ + void copyFrom (const void* srcData, + int destinationOffset, + size_t numBytes) noexcept; + + /** Copies data from this MemoryBlock to a memory address. + + @param destData the memory location to write to + @param sourceOffset the offset within this block from which the copied data will be read + @param numBytes how much to copy (if this extends beyond the limits of the memory block, + zeros will be used for that portion of the data) + */ + void copyTo (void* destData, + int sourceOffset, + size_t numBytes) const noexcept; + + //============================================================================== + /** Exchanges the contents of this and another memory block. + No actual copying is required for this, so it's very fast. + */ + void swapWith (MemoryBlock& other) noexcept; + + //============================================================================== + /** Attempts to parse the contents of the block as a zero-terminated UTF8 string. */ + String toString() const; + + //============================================================================== + /** Parses a string of hexadecimal numbers and writes this data into the memory block. + + The block will be resized to the number of valid bytes read from the string. + Non-hex characters in the string will be ignored. + + @see String::toHexString() + */ + void loadFromHexString (StringRef sourceHexString); + + //============================================================================== + /** Sets a number of bits in the memory block, treating it as a long binary sequence. */ + void setBitRange (size_t bitRangeStart, + size_t numBits, + int binaryNumberToApply) noexcept; + + /** Reads a number of bits from the memory block, treating it as one long binary sequence */ + int getBitRange (size_t bitRangeStart, + size_t numBitsToRead) const noexcept; + + //============================================================================== + /** Returns a string of characters that represent the binary contents of this block. + + Uses a 64-bit encoding system to allow binary data to be turned into a string + of simple non-extended characters, e.g. for storage in XML. + + @see fromBase64Encoding + */ + String toBase64Encoding() const; + + /** Takes a string of encoded characters and turns it into binary data. + + The string passed in must have been created by to64BitEncoding(), and this + block will be resized to recreate the original data block. + + @see toBase64Encoding + */ + bool fromBase64Encoding (StringRef encodedString); + + +private: + //============================================================================== + HeapBlock data; + size_t size; + + JUCE_LEAK_DETECTOR (MemoryBlock) +}; + + +#endif // JUCE_MEMORYBLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_OptionalScopedPointer.h b/JuceLibraryCode/modules/juce_core/memory/juce_OptionalScopedPointer.h new file mode 100644 index 0000000..a633dca --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_OptionalScopedPointer.h @@ -0,0 +1,186 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED +#define JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED + + +//============================================================================== +/** + Holds a pointer to an object which can optionally be deleted when this pointer + goes out of scope. + + This acts in many ways like a ScopedPointer, but allows you to specify whether or + not the object is deleted. + + @see ScopedPointer +*/ +template +class OptionalScopedPointer +{ +public: + //============================================================================== + /** Creates an empty OptionalScopedPointer. */ + OptionalScopedPointer() : shouldDelete (false) {} + + /** Creates an OptionalScopedPointer to point to a given object, and specifying whether + the OptionalScopedPointer will delete it. + + If takeOwnership is true, then the OptionalScopedPointer will act like a ScopedPointer, + deleting the object when it is itself deleted. If this parameter is false, then the + OptionalScopedPointer just holds a normal pointer to the object, and won't delete it. + */ + OptionalScopedPointer (ObjectType* objectToHold, bool takeOwnership) + : object (objectToHold), shouldDelete (takeOwnership) + { + } + + /** Takes ownership of the object that another OptionalScopedPointer holds. + + Like a normal ScopedPointer, the objectToTransferFrom object will become null, + as ownership of the managed object is transferred to this object. + + The flag to indicate whether or not to delete the managed object is also + copied from the source object. + */ + OptionalScopedPointer (OptionalScopedPointer& objectToTransferFrom) + : object (objectToTransferFrom.release()), + shouldDelete (objectToTransferFrom.shouldDelete) + { + } + + /** Takes ownership of the object that another OptionalScopedPointer holds. + + Like a normal ScopedPointer, the objectToTransferFrom object will become null, + as ownership of the managed object is transferred to this object. + + The ownership flag that says whether or not to delete the managed object is also + copied from the source object. + */ + OptionalScopedPointer& operator= (OptionalScopedPointer& objectToTransferFrom) + { + if (object != objectToTransferFrom.object) + { + clear(); + object = objectToTransferFrom.object; + } + + shouldDelete = objectToTransferFrom.shouldDelete; + return *this; + } + + /** The destructor may or may not delete the object that is being held, depending on the + takeOwnership flag that was specified when the object was first passed into an + OptionalScopedPointer constructor. + */ + ~OptionalScopedPointer() + { + clear(); + } + + //============================================================================== + /** Returns the object that this pointer is managing. */ + inline operator ObjectType*() const noexcept { return object; } + + /** Returns the object that this pointer is managing. */ + inline ObjectType* get() const noexcept { return object; } + + /** Returns the object that this pointer is managing. */ + inline ObjectType& operator*() const noexcept { return *object; } + + /** Lets you access methods and properties of the object that this pointer is holding. */ + inline ObjectType* operator->() const noexcept { return object; } + + //============================================================================== + /** Removes the current object from this OptionalScopedPointer without deleting it. + This will return the current object, and set this OptionalScopedPointer to a null pointer. + */ + ObjectType* release() noexcept { return object.release(); } + + /** Resets this pointer to null, possibly deleting the object that it holds, if it has + ownership of it. + */ + void clear() + { + if (! shouldDelete) + object.release(); + } + + /** Makes this OptionalScopedPointer point at a new object, specifying whether the + OptionalScopedPointer will take ownership of the object. + + If takeOwnership is true, then the OptionalScopedPointer will act like a ScopedPointer, + deleting the object when it is itself deleted. If this parameter is false, then the + OptionalScopedPointer just holds a normal pointer to the object, and won't delete it. + */ + void set (ObjectType* newObject, bool takeOwnership) + { + if (object != newObject) + { + clear(); + object = newObject; + } + + shouldDelete = takeOwnership; + } + + /** Makes this OptionalScopedPointer point at a new object, and take ownership of that object. */ + void setOwned (ObjectType* newObject) + { + set (newObject, true); + } + + /** Makes this OptionalScopedPointer point at a new object, but will not take ownership of that object. */ + void setNonOwned (ObjectType* newObject) + { + set (newObject, false); + } + + /** Returns true if the target object will be deleted when this pointer + object is deleted. + */ + bool willDeleteObject() const noexcept { return shouldDelete; } + + //============================================================================== + /** Swaps this object with another OptionalScopedPointer. + The two objects simply exchange their states. + */ + void swapWith (OptionalScopedPointer& other) noexcept + { + object.swapWith (other.object); + std::swap (shouldDelete, other.shouldDelete); + } + +private: + //============================================================================== + ScopedPointer object; + bool shouldDelete; +}; + + +#endif // JUCE_OPTIONALSCOPEDPOINTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_ReferenceCountedObject.h b/JuceLibraryCode/modules/juce_core/memory/juce_ReferenceCountedObject.h new file mode 100644 index 0000000..36ac5c3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_ReferenceCountedObject.h @@ -0,0 +1,415 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED +#define JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED + + +//============================================================================== +/** + A base class which provides methods for reference-counting. + + To add reference-counting to a class, derive it from this class, and + use the ReferenceCountedObjectPtr class to point to it. + + e.g. @code + class MyClass : public ReferenceCountedObject + { + void foo(); + + // This is a neat way of declaring a typedef for a pointer class, + // rather than typing out the full templated name each time.. + typedef ReferenceCountedObjectPtr Ptr; + }; + + MyClass::Ptr p = new MyClass(); + MyClass::Ptr p2 = p; + p = nullptr; + p2->foo(); + @endcode + + Once a new ReferenceCountedObject has been assigned to a pointer, be + careful not to delete the object manually. + + This class uses an Atomic value to hold the reference count, so that it + the pointers can be passed between threads safely. For a faster but non-thread-safe + version, use SingleThreadedReferenceCountedObject instead. + + @see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject +*/ +class JUCE_API ReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will be deleted. + */ + void decReferenceCount() noexcept + { + jassert (getReferenceCount() > 0); + + if (--refCount == 0) + delete this; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will not be deleted, but this method + will return true, allowing the caller to take care of deletion. + */ + bool decReferenceCountWithoutDeleting() noexcept + { + jassert (getReferenceCount() > 0); + return --refCount == 0; + } + + /** Returns the object's current reference count. */ + int getReferenceCount() const noexcept { return refCount.get(); } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + ReferenceCountedObject() {} + + /** Destructor. */ + virtual ~ReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + jassert (getReferenceCount() == 0); + } + + /** Resets the reference count to zero without deleting the object. + You should probably never need to use this! + */ + void resetReferenceCount() noexcept + { + refCount = 0; + } + +private: + //============================================================================== + Atomic refCount; + + friend struct ContainerDeletePolicy; + JUCE_DECLARE_NON_COPYABLE (ReferenceCountedObject) +}; + + +//============================================================================== +/** + Adds reference-counting to an object. + + This is effectively a version of the ReferenceCountedObject class, but which + uses a non-atomic counter, and so is not thread-safe (but which will be more + efficient). + For more details on how to use it, see the ReferenceCountedObject class notes. + + @see ReferenceCountedObject, ReferenceCountedObjectPtr, ReferenceCountedArray +*/ +class JUCE_API SingleThreadedReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will be deleted. + */ + void decReferenceCount() noexcept + { + jassert (getReferenceCount() > 0); + + if (--refCount == 0) + delete this; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will not be deleted, but this method + will return true, allowing the caller to take care of deletion. + */ + bool decReferenceCountWithoutDeleting() noexcept + { + jassert (getReferenceCount() > 0); + return --refCount == 0; + } + + /** Returns the object's current reference count. */ + int getReferenceCount() const noexcept { return refCount; } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + SingleThreadedReferenceCountedObject() : refCount (0) {} + + /** Destructor. */ + virtual ~SingleThreadedReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + jassert (getReferenceCount() == 0); + } + +private: + //============================================================================== + int refCount; + + friend struct ContainerDeletePolicy; + JUCE_DECLARE_NON_COPYABLE (SingleThreadedReferenceCountedObject) +}; + + +//============================================================================== +/** + A smart-pointer class which points to a reference-counted object. + + The template parameter specifies the class of the object you want to point to - the easiest + way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject + or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable + class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and + decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods + should behave. + + When using this class, you'll probably want to create a typedef to abbreviate the full + templated name - e.g. + @code + struct MyClass : public ReferenceCountedObject + { + typedef ReferenceCountedObjectPtr Ptr; + ... + @endcode + + @see ReferenceCountedObject, ReferenceCountedObjectArray +*/ +template +class ReferenceCountedObjectPtr +{ +public: + /** The class being referenced by this pointer. */ + typedef ReferenceCountedObjectClass ReferencedType; + + //============================================================================== + /** Creates a pointer to a null object. */ + ReferenceCountedObjectPtr() noexcept + : referencedObject (nullptr) + { + } + + /** Creates a pointer to an object. + This will increment the object's reference-count. + */ + ReferenceCountedObjectPtr (ReferencedType* refCountedObject) noexcept + : referencedObject (refCountedObject) + { + incIfNotNull (refCountedObject); + } + + /** Copies another pointer. + This will increment the object's reference-count. + */ + ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (other.referencedObject) + { + incIfNotNull (referencedObject); + } + + /** Copies another pointer. + This will increment the object's reference-count (if it is non-null). + */ + template + ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (static_cast (other.get())) + { + incIfNotNull (referencedObject); + } + + /** Changes this pointer to point at a different object. + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (other.referencedObject); + } + + /** Changes this pointer to point at a different object. + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + template + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (static_cast (other.get())); + } + + /** Changes this pointer to point at a different object. + + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (ReferencedType* const newObject) + { + if (referencedObject != newObject) + { + incIfNotNull (newObject); + ReferencedType* const oldObject = referencedObject; + referencedObject = newObject; + decIfNotNull (oldObject); + } + + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Takes-over the object from another pointer. */ + ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept + : referencedObject (other.referencedObject) + { + other.referencedObject = nullptr; + } + + /** Takes-over the object from another pointer. */ + ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) + { + std::swap (referencedObject, other.referencedObject); + return *this; + } + #endif + + /** Destructor. + This will decrement the object's reference-count, which will cause the + object to be deleted when the ref-count hits zero. + */ + ~ReferenceCountedObjectPtr() + { + decIfNotNull (referencedObject); + } + + //============================================================================== + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + operator ReferencedType*() const noexcept { return referencedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + ReferencedType* get() const noexcept { return referencedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + ReferencedType* getObject() const noexcept { return referencedObject; } + + // the -> operator is called on the referenced object + ReferencedType* operator->() const noexcept + { + jassert (referencedObject != nullptr); // null pointer method call! + return referencedObject; + } + +private: + //============================================================================== + ReferencedType* referencedObject; + + static void incIfNotNull (ReferencedType* o) noexcept + { + if (o != nullptr) + o->incReferenceCount(); + } + + static void decIfNotNull (ReferencedType* o) noexcept + { + if (o != nullptr && o->decReferenceCountWithoutDeleting()) + ContainerDeletePolicy::destroy (o); + } +}; + + +//============================================================================== +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectClass* const object2) noexcept +{ + return object1.get() == object2; +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() == object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 == object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectClass* object2) noexcept +{ + return object1.get() != object2; +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() != object2.get(); +} + +/** Compares two ReferenceCountedObjectPointers. */ +template +bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 != object2.get(); +} + + +#endif // JUCE_REFERENCECOUNTEDOBJECT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_ScopedPointer.h b/JuceLibraryCode/modules/juce_core/memory/juce_ScopedPointer.h new file mode 100644 index 0000000..1aa8031 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_ScopedPointer.h @@ -0,0 +1,253 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDPOINTER_H_INCLUDED +#define JUCE_SCOPEDPOINTER_H_INCLUDED + +//============================================================================== +/** + This class holds a pointer which is automatically deleted when this object goes + out of scope. + + Once a pointer has been passed to a ScopedPointer, it will make sure that the pointer + gets deleted when the ScopedPointer is deleted. Using the ScopedPointer on the stack or + as member variables is a good way to use RAII to avoid accidentally leaking dynamically + created objects. + + A ScopedPointer can be used in pretty much the same way that you'd use a normal pointer + to an object. If you use the assignment operator to assign a different object to a + ScopedPointer, the old one will be automatically deleted. + + Important note: The class is designed to hold a pointer to an object, NOT to an array! + It calls delete on its payload, not delete[], so do not give it an array to hold! For + that kind of purpose, you should be using HeapBlock or Array instead. + + A const ScopedPointer is guaranteed not to lose ownership of its object or change the + object to which it points during its lifetime. This means that making a copy of a const + ScopedPointer is impossible, as that would involve the new copy taking ownership from the + old one. + + If you need to get a pointer out of a ScopedPointer without it being deleted, you + can use the release() method. + + Something to note is the main difference between this class and the std::auto_ptr class, + which is that ScopedPointer provides a cast-to-object operator, wheras std::auto_ptr + requires that you always call get() to retrieve the pointer. The advantages of providing + the cast is that you don't need to call get(), so can use the ScopedPointer in pretty much + exactly the same way as a raw pointer. The disadvantage is that the compiler is free to + use the cast in unexpected and sometimes dangerous ways - in particular, it becomes difficult + to return a ScopedPointer as the result of a function. To avoid this causing errors, + ScopedPointer contains an overloaded constructor that should cause a syntax error in these + circumstances, but it does mean that instead of returning a ScopedPointer from a function, + you'd need to return a raw pointer (or use a std::auto_ptr instead). +*/ +template +class ScopedPointer +{ +public: + //============================================================================== + /** Creates a ScopedPointer containing a null pointer. */ + inline ScopedPointer() noexcept : object (nullptr) + { + } + + /** Creates a ScopedPointer that owns the specified object. */ + inline ScopedPointer (ObjectType* const objectToTakePossessionOf) noexcept + : object (objectToTakePossessionOf) + { + } + + /** Creates a ScopedPointer that takes its pointer from another ScopedPointer. + + Because a pointer can only belong to one ScopedPointer, this transfers + the pointer from the other object to this one, and the other object is reset to + be a null pointer. + */ + ScopedPointer (ScopedPointer& objectToTransferFrom) noexcept + : object (objectToTransferFrom.object) + { + objectToTransferFrom.object = nullptr; + } + + /** Destructor. + This will delete the object that this ScopedPointer currently refers to. + */ + inline ~ScopedPointer() { ContainerDeletePolicy::destroy (object); } + + /** Changes this ScopedPointer to point to a new object. + + Because a pointer can only belong to one ScopedPointer, this transfers + the pointer from the other object to this one, and the other object is reset to + be a null pointer. + + If this ScopedPointer already points to an object, that object + will first be deleted. + */ + ScopedPointer& operator= (ScopedPointer& objectToTransferFrom) + { + if (this != objectToTransferFrom.getAddress()) + { + // Two ScopedPointers should never be able to refer to the same object - if + // this happens, you must have done something dodgy! + jassert (object == nullptr || object != objectToTransferFrom.object); + + ObjectType* const oldObject = object; + object = objectToTransferFrom.object; + objectToTransferFrom.object = nullptr; + ContainerDeletePolicy::destroy (oldObject); + } + + return *this; + } + + /** Changes this ScopedPointer to point to a new object. + + If this ScopedPointer already points to an object, that object + will first be deleted. + + The pointer that you pass in may be a nullptr. + */ + ScopedPointer& operator= (ObjectType* const newObjectToTakePossessionOf) + { + if (object != newObjectToTakePossessionOf) + { + ObjectType* const oldObject = object; + object = newObjectToTakePossessionOf; + ContainerDeletePolicy::destroy (oldObject); + } + + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + ScopedPointer (ScopedPointer&& other) noexcept + : object (other.object) + { + other.object = nullptr; + } + + ScopedPointer& operator= (ScopedPointer&& other) noexcept + { + object = other.object; + other.object = nullptr; + return *this; + } + #endif + + //============================================================================== + /** Returns the object that this ScopedPointer refers to. */ + inline operator ObjectType*() const noexcept { return object; } + + /** Returns the object that this ScopedPointer refers to. */ + inline ObjectType* get() const noexcept { return object; } + + /** Returns the object that this ScopedPointer refers to. */ + inline ObjectType& operator*() const noexcept { return *object; } + + /** Lets you access methods and properties of the object that this ScopedPointer refers to. */ + inline ObjectType* operator->() const noexcept { return object; } + + //============================================================================== + /** Removes the current object from this ScopedPointer without deleting it. + This will return the current object, and set the ScopedPointer to a null pointer. + */ + ObjectType* release() noexcept { ObjectType* const o = object; object = nullptr; return o; } + + //============================================================================== + /** Swaps this object with that of another ScopedPointer. + The two objects simply exchange their pointers. + */ + void swapWith (ScopedPointer& other) noexcept + { + // Two ScopedPointers should never be able to refer to the same object - if + // this happens, you must have done something dodgy! + jassert (object != other.object || this == other.getAddress() || object == nullptr); + + std::swap (object, other.object); + } + + /** If the pointer is non-null, this will attempt to return a new copy of the object that is pointed to. + If the pointer is null, this will safely return a nullptr. + */ + inline ObjectType* createCopy() const { return createCopyIfNotNull (object); } + +private: + //============================================================================== + ObjectType* object; + + // (Required as an alternative to the overloaded & operator). + const ScopedPointer* getAddress() const noexcept { return this; } + + #if ! JUCE_MSVC // (MSVC can't deal with multiple copy constructors) + /* The copy constructors are private to stop people accidentally copying a const ScopedPointer + (the compiler would let you do so by implicitly casting the source to its raw object pointer). + + A side effect of this is that in a compiler that doesn't support C++11, you may hit an + error when you write something like this: + + ScopedPointer m = new MyClass(); // Compile error: copy constructor is private. + + Even though the compiler would normally ignore the assignment here, it can't do so when the + copy constructor is private. It's very easy to fix though - just write it like this: + + ScopedPointer m (new MyClass()); // Compiles OK + + It's probably best to use the latter form when writing your object declarations anyway, as + this is a better representation of the code that you actually want the compiler to produce. + */ + JUCE_DECLARE_NON_COPYABLE (ScopedPointer) + #endif +}; + +//============================================================================== +/** Compares a ScopedPointer with another pointer. + This can be handy for checking whether this is a null pointer. +*/ +template +bool operator== (const ScopedPointer& pointer1, ObjectType* const pointer2) noexcept +{ + return static_cast (pointer1) == pointer2; +} + +/** Compares a ScopedPointer with another pointer. + This can be handy for checking whether this is a null pointer. +*/ +template +bool operator!= (const ScopedPointer& pointer1, ObjectType* const pointer2) noexcept +{ + return static_cast (pointer1) != pointer2; +} + +//============================================================================== +#ifndef DOXYGEN +// NB: This is just here to prevent any silly attempts to call deleteAndZero() on a ScopedPointer. +template +void deleteAndZero (ScopedPointer&) { static_jassert (sizeof (Type) == 12345); } +#endif + +#endif // JUCE_SCOPEDPOINTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_SharedResourcePointer.h b/JuceLibraryCode/modules/juce_core/memory/juce_SharedResourcePointer.h new file mode 100644 index 0000000..53d314b --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_SharedResourcePointer.h @@ -0,0 +1,152 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SHAREDRESOURCEPOINTER_H_INCLUDED +#define JUCE_SHAREDRESOURCEPOINTER_H_INCLUDED + + +//============================================================================== +/** + A smart-pointer that automatically creates and manages the lifetime of a + shared static instance of a class. + + The SharedObjectType template type indicates the class to use for the shared + object - the only requirements on this class are that it must have a public + default constructor and destructor. + + The SharedResourcePointer offers a pattern that differs from using a singleton or + static instance of an object, because it uses reference-counting to make sure that + the underlying shared object is automatically created/destroyed according to the + number of SharedResourcePointer objects that exist. When the last one is deleted, + the underlying object is also immediately destroyed. This allows you to use scoping + to manage the lifetime of a shared resource. + + Note: the construction/deletion of the shared object must not involve any + code that makes recursive calls to a SharedResourcePointer, or you'll cause + a deadlock. + + Example: + @code + // An example of a class that contains the shared data you want to use. + struct MySharedData + { + // There's no need to ever create an instance of this class directly yourself, + // but it does need a public constructor that does the initialisation. + MySharedData() + { + sharedStuff = generateHeavyweightStuff(); + } + + Array sharedStuff; + }; + + struct DataUserClass + { + DataUserClass() + { + // Multiple instances of the DataUserClass will all have the same + // shared common instance of MySharedData referenced by their sharedData + // member variables. + useSharedStuff (sharedData->sharedStuff); + } + + // By keeping this pointer as a member variable, the shared resource + // is guaranteed to be available for as long as the DataUserClass object. + SharedResourcePointer sharedData; + }; + + @endcode + */ +template +class SharedResourcePointer +{ +public: + /** Creates an instance of the shared object. + If other SharedResourcePointer objects for this type already exist, then + this one will simply point to the same shared object that they are already + using. Otherwise, if this is the first SharedResourcePointer to be created, + then a shared object will be created automatically. + */ + SharedResourcePointer() + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (++(holder.refCount) == 1) + holder.sharedInstance = new SharedObjectType(); + + sharedObject = holder.sharedInstance; + } + + /** Destructor. + If no other SharedResourcePointer objects exist, this will also delete + the shared object to which it refers. + */ + ~SharedResourcePointer() + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (--(holder.refCount) == 0) + holder.sharedInstance = nullptr; + } + + /** Returns the shared object. */ + operator SharedObjectType*() const noexcept { return sharedObject; } + + /** Returns the shared object. */ + SharedObjectType& get() const noexcept { return *sharedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be zero, of course. + */ + SharedObjectType& getObject() const noexcept { return *sharedObject; } + + SharedObjectType* operator->() const noexcept { return sharedObject; } + +private: + struct SharedObjectHolder : public ReferenceCountedObject + { + SpinLock lock; + ScopedPointer sharedInstance; + int refCount; + }; + + static SharedObjectHolder& getSharedObjectHolder() noexcept + { + static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 }; + return *reinterpret_cast (holder); + } + + SharedObjectType* sharedObject; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedResourcePointer) +}; + + +#endif // JUCE_SHAREDRESOURCEPOINTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_Singleton.h b/JuceLibraryCode/modules/juce_core/memory/juce_Singleton.h new file mode 100644 index 0000000..00ea0af --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_Singleton.h @@ -0,0 +1,292 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SINGLETON_H_INCLUDED +#define JUCE_SINGLETON_H_INCLUDED + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + To use this, add the line juce_DeclareSingleton (MyClass, doNotRecreateAfterDeletion) + to the class's definition. + + Then put a macro juce_ImplementSingleton (MyClass) along with the class's + implementation code. + + It's also a very good idea to also add the call clearSingletonInstance() in your class's + destructor, in case it is deleted by other means than deleteInstance() + + Clients can then call the static method MyClass::getInstance() to get a pointer + to the singleton, or MyClass::getInstanceWithoutCreating() which will return 0 if + no instance currently exists. + + e.g. @code + + class MySingleton + { + public: + MySingleton() + { + } + + ~MySingleton() + { + // this ensures that no dangling pointers are left when the + // singleton is deleted. + clearSingletonInstance(); + } + + juce_DeclareSingleton (MySingleton, false) + }; + + juce_ImplementSingleton (MySingleton) + + + // example of usage: + MySingleton* m = MySingleton::getInstance(); // creates the singleton if there isn't already one. + + ... + + MySingleton::deleteInstance(); // safely deletes the singleton (if it's been created). + + @endcode + + If doNotRecreateAfterDeletion = true, it won't allow the object to be created more + than once during the process's lifetime - i.e. after you've created and deleted the + object, getInstance() will refuse to create another one. This can be useful to stop + objects being accidentally re-created during your app's shutdown code. + + If you know that your object will only be created and deleted by a single thread, you + can use the slightly more efficient juce_DeclareSingleton_SingleThreaded() macro instead + of this one. + + @see juce_ImplementSingleton, juce_DeclareSingleton_SingleThreaded +*/ +#define juce_DeclareSingleton(classname, doNotRecreateAfterDeletion) \ +\ + static classname* _singletonInstance; \ + static juce::CriticalSection _singletonLock; \ +\ + static classname* JUCE_CALLTYPE getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + {\ + const juce::ScopedLock sl (_singletonLock); \ +\ + if (_singletonInstance == nullptr) \ + { \ + static bool alreadyInside = false; \ + static bool createdOnceAlready = false; \ +\ + const bool problem = alreadyInside || ((doNotRecreateAfterDeletion) && createdOnceAlready); \ + jassert (! problem); \ + if (! problem) \ + { \ + createdOnceAlready = true; \ + alreadyInside = true; \ + classname* newObject = new classname(); /* (use a stack variable to avoid setting the newObject value before the class has finished its constructor) */ \ + alreadyInside = false; \ +\ + _singletonInstance = newObject; \ + } \ + } \ + } \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* JUCE_CALLTYPE getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void JUCE_CALLTYPE deleteInstance() \ + { \ + const juce::ScopedLock sl (_singletonLock); \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + + +//============================================================================== +/** This is a counterpart to the juce_DeclareSingleton macro. + + After adding the juce_DeclareSingleton to the class definition, this macro has + to be used in the cpp file. +*/ +#define juce_ImplementSingleton(classname) \ +\ + classname* classname::_singletonInstance = nullptr; \ + juce::CriticalSection classname::_singletonLock; + + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + This is exactly the same as juce_DeclareSingleton, but doesn't use a critical + section to make access to it thread-safe. If you know that your object will + only ever be created or deleted by a single thread, then this is a + more efficient version to use. + + If doNotRecreateAfterDeletion = true, it won't allow the object to be created more + than once during the process's lifetime - i.e. after you've created and deleted the + object, getInstance() will refuse to create another one. This can be useful to stop + objects being accidentally re-created during your app's shutdown code. + + See the documentation for juce_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + juce_ImplementSingleton_SingleThreaded instead of juce_ImplementSingleton. + + @see juce_ImplementSingleton_SingleThreaded, juce_DeclareSingleton, juce_DeclareSingleton_SingleThreaded_Minimal +*/ +#define juce_DeclareSingleton_SingleThreaded(classname, doNotRecreateAfterDeletion) \ +\ + static classname* _singletonInstance; \ +\ + static classname* getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + { \ + static bool alreadyInside = false; \ + static bool createdOnceAlready = false; \ +\ + const bool problem = alreadyInside || ((doNotRecreateAfterDeletion) && createdOnceAlready); \ + jassert (! problem); \ + if (! problem) \ + { \ + createdOnceAlready = true; \ + alreadyInside = true; \ + classname* newObject = new classname(); /* (use a stack variable to avoid setting the newObject value before the class has finished its constructor) */ \ + alreadyInside = false; \ +\ + _singletonInstance = newObject; \ + } \ + } \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void deleteInstance() \ + { \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + +//============================================================================== +/** + Macro to declare member variables and methods for a singleton class. + + This is like juce_DeclareSingleton_SingleThreaded, but doesn't do any checking + for recursion or repeated instantiation. It's intended for use as a lightweight + version of a singleton, where you're using it in very straightforward + circumstances and don't need the extra checking. + + Juce use the normal juce_ImplementSingleton_SingleThreaded as the counterpart + to this declaration, as you would with juce_DeclareSingleton_SingleThreaded. + + See the documentation for juce_DeclareSingleton for more information about + how to use it, the only difference being that you have to use + juce_ImplementSingleton_SingleThreaded instead of juce_ImplementSingleton. + + @see juce_ImplementSingleton_SingleThreaded, juce_DeclareSingleton +*/ +#define juce_DeclareSingleton_SingleThreaded_Minimal(classname) \ +\ + static classname* _singletonInstance; \ +\ + static classname* getInstance() \ + { \ + if (_singletonInstance == nullptr) \ + _singletonInstance = new classname(); \ +\ + return _singletonInstance; \ + } \ +\ + static inline classname* getInstanceWithoutCreating() noexcept\ + { \ + return _singletonInstance; \ + } \ +\ + static void deleteInstance() \ + { \ + if (_singletonInstance != nullptr) \ + { \ + classname* const old = _singletonInstance; \ + _singletonInstance = nullptr; \ + delete old; \ + } \ + } \ +\ + void clearSingletonInstance() noexcept\ + { \ + if (_singletonInstance == this) \ + _singletonInstance = nullptr; \ + } + + +//============================================================================== +/** This is a counterpart to the juce_DeclareSingleton_SingleThreaded macro. + + After adding juce_DeclareSingleton_SingleThreaded or juce_DeclareSingleton_SingleThreaded_Minimal + to the class definition, this macro has to be used somewhere in the cpp file. +*/ +#define juce_ImplementSingleton_SingleThreaded(classname) \ +\ + classname* classname::_singletonInstance = nullptr; + + + +#endif // JUCE_SINGLETON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_WeakReference.h b/JuceLibraryCode/modules/juce_core/memory/juce_WeakReference.h new file mode 100644 index 0000000..c11b19f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/memory/juce_WeakReference.h @@ -0,0 +1,212 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WEAKREFERENCE_H_INCLUDED +#define JUCE_WEAKREFERENCE_H_INCLUDED + + +//============================================================================== +/** + This class acts as a pointer which will automatically become null if the object + to which it points is deleted. + + To accomplish this, the source object needs to cooperate by performing a couple of simple tasks. + It must embed a WeakReference::Master object, which stores a shared pointer object, and must clear + this master pointer in its destructor. + + E.g. + @code + class MyObject + { + public: + MyObject() + { + // If you're planning on using your WeakReferences in a multi-threaded situation, you may choose + // to create a WeakReference to the object here in the constructor, which will pre-initialise the + // embedded object, avoiding an (extremely unlikely) race condition that could occur if multiple + // threads overlap while creating the first WeakReference to it. + } + + ~MyObject() + { + // This will zero all the references - you need to call this in your destructor. + masterReference.clear(); + } + + private: + // You need to embed a variable of this type, with the name "masterReference" inside your object. If the + // variable is not public, you should make your class a friend of WeakReference so that the + // WeakReference class can access it. + WeakReference::Master masterReference; + friend class WeakReference; + }; + + // Here's an example of using a pointer.. + + MyObject* n = new MyObject(); + WeakReference myObjectRef = n; + + MyObject* pointer1 = myObjectRef; // returns a valid pointer to 'n' + delete n; + MyObject* pointer2 = myObjectRef; // returns a null pointer + @endcode + + @see WeakReference::Master +*/ +template +class WeakReference +{ +public: + /** Creates a null SafePointer. */ + inline WeakReference() noexcept {} + + /** Creates a WeakReference that points at the given object. */ + WeakReference (ObjectType* const object) : holder (getRef (object)) {} + + /** Creates a copy of another WeakReference. */ + WeakReference (const WeakReference& other) noexcept : holder (other.holder) {} + + /** Copies another pointer to this one. */ + WeakReference& operator= (const WeakReference& other) { holder = other.holder; return *this; } + + /** Copies another pointer to this one. */ + WeakReference& operator= (ObjectType* const newObject) { holder = getRef (newObject); return *this; } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + WeakReference (WeakReference&& other) noexcept : holder (static_cast (other.holder)) {} + WeakReference& operator= (WeakReference&& other) noexcept { holder = static_cast (other.holder); return *this; } + #endif + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + ObjectType* get() const noexcept { return holder != nullptr ? holder->get() : nullptr; } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + operator ObjectType*() const noexcept { return get(); } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + ObjectType* operator->() noexcept { return get(); } + + /** Returns the object that this pointer refers to, or null if the object no longer exists. */ + const ObjectType* operator->() const noexcept { return get(); } + + /** This returns true if this reference has been pointing at an object, but that object has + since been deleted. + + If this reference was only ever pointing at a null pointer, this will return false. Using + operator=() to make this refer to a different object will reset this flag to match the status + of the reference from which you're copying. + */ + bool wasObjectDeleted() const noexcept { return holder != nullptr && holder->get() == nullptr; } + + bool operator== (ObjectType* const object) const noexcept { return get() == object; } + bool operator!= (ObjectType* const object) const noexcept { return get() != object; } + + //============================================================================== + /** This class is used internally by the WeakReference class - don't use it directly + in your code! + @see WeakReference + */ + class SharedPointer : public ReferenceCountingType + { + public: + explicit SharedPointer (ObjectType* const obj) noexcept : owner (obj) {} + + inline ObjectType* get() const noexcept { return owner; } + void clearPointer() noexcept { owner = nullptr; } + + private: + ObjectType* volatile owner; + + JUCE_DECLARE_NON_COPYABLE (SharedPointer) + }; + + typedef ReferenceCountedObjectPtr SharedRef; + + //============================================================================== + /** + This class is embedded inside an object to which you want to attach WeakReference pointers. + See the WeakReference class notes for an example of how to use this class. + @see WeakReference + */ + class Master + { + public: + Master() noexcept {} + + ~Master() noexcept + { + // You must remember to call clear() in your source object's destructor! See the notes + // for the WeakReference class for an example of how to do this. + jassert (sharedPointer == nullptr || sharedPointer->get() == nullptr); + } + + /** The first call to this method will create an internal object that is shared by all weak + references to the object. + */ + SharedPointer* getSharedPointer (ObjectType* const object) + { + if (sharedPointer == nullptr) + { + sharedPointer = new SharedPointer (object); + } + else + { + // You're trying to create a weak reference to an object that has already been deleted!! + jassert (sharedPointer->get() != nullptr); + } + + return sharedPointer; + } + + /** The object that owns this master pointer should call this before it gets destroyed, + to zero all the references to this object that may be out there. See the WeakReference + class notes for an example of how to do this. + */ + void clear() noexcept + { + if (sharedPointer != nullptr) + sharedPointer->clearPointer(); + } + + private: + SharedRef sharedPointer; + + JUCE_DECLARE_NON_COPYABLE (Master) + }; + +private: + SharedRef holder; + + static inline SharedPointer* getRef (ObjectType* const o) + { + return (o != nullptr) ? o->masterReference.getSharedPointer (o) : nullptr; + } +}; + + +#endif // JUCE_WEAKREFERENCE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Result.cpp b/JuceLibraryCode/modules/juce_core/misc/juce_Result.cpp new file mode 100644 index 0000000..1734907 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Result.cpp @@ -0,0 +1,83 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Result::Result() noexcept {} + +Result::Result (const String& message) noexcept + : errorMessage (message) +{ +} + +Result::Result (const Result& other) + : errorMessage (other.errorMessage) +{ +} + +Result& Result::operator= (const Result& other) +{ + errorMessage = other.errorMessage; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Result::Result (Result&& other) noexcept + : errorMessage (static_cast (other.errorMessage)) +{ +} + +Result& Result::operator= (Result&& other) noexcept +{ + errorMessage = static_cast (other.errorMessage); + return *this; +} +#endif + +bool Result::operator== (const Result& other) const noexcept +{ + return errorMessage == other.errorMessage; +} + +bool Result::operator!= (const Result& other) const noexcept +{ + return errorMessage != other.errorMessage; +} + +Result Result::fail (const String& errorMessage) noexcept +{ + return Result (errorMessage.isEmpty() ? "Unknown Error" : errorMessage); +} + +const String& Result::getErrorMessage() const noexcept +{ + return errorMessage; +} + +bool Result::wasOk() const noexcept { return errorMessage.isEmpty(); } +Result::operator bool() const noexcept { return errorMessage.isEmpty(); } +bool Result::failed() const noexcept { return errorMessage.isNotEmpty(); } +bool Result::operator!() const noexcept { return errorMessage.isNotEmpty(); } diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Result.h b/JuceLibraryCode/modules/juce_core/misc/juce_Result.h new file mode 100644 index 0000000..7c320ac --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Result.h @@ -0,0 +1,125 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RESULT_H_INCLUDED +#define JUCE_RESULT_H_INCLUDED + + +//============================================================================== +/** + Represents the 'success' or 'failure' of an operation, and holds an associated + error message to describe the error when there's a failure. + + E.g. + @code + Result myOperation() + { + if (doSomeKindOfFoobar()) + return Result::ok(); + else + return Result::fail ("foobar didn't work!"); + } + + const Result result (myOperation()); + + if (result.wasOk()) + { + ...it's all good... + } + else + { + warnUserAboutFailure ("The foobar operation failed! Error message was: " + + result.getErrorMessage()); + } + @endcode +*/ +class JUCE_API Result +{ +public: + //============================================================================== + /** Creates and returns a 'successful' result. */ + static Result ok() noexcept { return Result(); } + + /** Creates a 'failure' result. + If you pass a blank error message in here, a default "Unknown Error" message + will be used instead. + */ + static Result fail (const String& errorMessage) noexcept; + + //============================================================================== + /** Returns true if this result indicates a success. */ + bool wasOk() const noexcept; + + /** Returns true if this result indicates a failure. + You can use getErrorMessage() to retrieve the error message associated + with the failure. + */ + bool failed() const noexcept; + + /** Returns true if this result indicates a success. + This is equivalent to calling wasOk(). + */ + operator bool() const noexcept; + + /** Returns true if this result indicates a failure. + This is equivalent to calling failed(). + */ + bool operator!() const noexcept; + + /** Returns the error message that was set when this result was created. + For a successful result, this will be an empty string; + */ + const String& getErrorMessage() const noexcept; + + //============================================================================== + Result (const Result&); + Result& operator= (const Result&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Result (Result&&) noexcept; + Result& operator= (Result&&) noexcept; + #endif + + bool operator== (const Result& other) const noexcept; + bool operator!= (const Result& other) const noexcept; + +private: + String errorMessage; + + // The default constructor is not for public use! + // Instead, use Result::ok() or Result::fail() + Result() noexcept; + explicit Result (const String&) noexcept; + + // These casts are private to prevent people trying to use the Result object in numeric contexts + operator int() const; + operator void*() const; +}; + + +#endif // JUCE_RESULT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp new file mode 100644 index 0000000..eb74964 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.cpp @@ -0,0 +1,117 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Uuid::Uuid() +{ + Random r; + + for (size_t i = 0; i < sizeof (uuid); ++i) + uuid[i] = (uint8) (r.nextInt (256)); + + // To make it RFC 4122 compliant, need to force a few bits... + uuid[6] = (uuid[6] & 0x0f) | 0x40; + uuid[8] = (uuid[8] & 0x3f) | 0x80; +} + +Uuid::~Uuid() noexcept {} + +Uuid::Uuid (const Uuid& other) noexcept +{ + memcpy (uuid, other.uuid, sizeof (uuid)); +} + +Uuid& Uuid::operator= (const Uuid& other) noexcept +{ + memcpy (uuid, other.uuid, sizeof (uuid)); + return *this; +} + +bool Uuid::operator== (const Uuid& other) const noexcept { return memcmp (uuid, other.uuid, sizeof (uuid)) == 0; } +bool Uuid::operator!= (const Uuid& other) const noexcept { return ! operator== (other); } + +Uuid Uuid::null() noexcept +{ + return Uuid ((const uint8*) nullptr); +} + +bool Uuid::isNull() const noexcept +{ + for (size_t i = 0; i < sizeof (uuid); ++i) + if (uuid[i] != 0) + return false; + + return true; +} + +String Uuid::getHexRegion (int start, int length) const +{ + return String::toHexString (uuid + start, length, 0); +} + +String Uuid::toString() const +{ + return getHexRegion (0, 16); +} + +String Uuid::toDashedString() const +{ + return getHexRegion (0, 4) + + "-" + getHexRegion (4, 2) + + "-" + getHexRegion (6, 2) + + "-" + getHexRegion (8, 2) + + "-" + getHexRegion (10, 6); +} + +Uuid::Uuid (const String& uuidString) +{ + operator= (uuidString); +} + +Uuid& Uuid::operator= (const String& uuidString) +{ + MemoryBlock mb; + mb.loadFromHexString (uuidString); + mb.ensureSize (sizeof (uuid), true); + mb.copyTo (uuid, 0, sizeof (uuid)); + return *this; +} + +Uuid::Uuid (const uint8* const rawData) noexcept +{ + operator= (rawData); +} + +Uuid& Uuid::operator= (const uint8* const rawData) noexcept +{ + if (rawData != nullptr) + memcpy (uuid, rawData, sizeof (uuid)); + else + zeromem (uuid, sizeof (uuid)); + + return *this; +} diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.h b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.h new file mode 100644 index 0000000..cd7af0d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Uuid.h @@ -0,0 +1,121 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_UUID_H_INCLUDED +#define JUCE_UUID_H_INCLUDED + + +//============================================================================== +/** + A universally unique 128-bit identifier. + + This class generates very random unique numbers. It's vanishingly unlikely + that two identical UUIDs would ever be created by chance. The values are + formatted to meet the RFC 4122 version 4 standard. + + The class includes methods for saving the ID as a string or as raw binary data. +*/ +class JUCE_API Uuid +{ +public: + //============================================================================== + /** Creates a new unique ID, compliant with RFC 4122 version 4. */ + Uuid(); + + /** Destructor. */ + ~Uuid() noexcept; + + /** Creates a copy of another UUID. */ + Uuid (const Uuid&) noexcept; + + /** Copies another UUID. */ + Uuid& operator= (const Uuid&) noexcept; + + //============================================================================== + /** Returns true if the ID is zero. */ + bool isNull() const noexcept; + + /** Returns a null Uuid object. */ + static Uuid null() noexcept; + + bool operator== (const Uuid&) const noexcept; + bool operator!= (const Uuid&) const noexcept; + + //============================================================================== + /** Returns a stringified version of this UUID. + + A Uuid object can later be reconstructed from this string using operator= or + the constructor that takes a string parameter. + + @returns a 32 character hex string. + */ + String toString() const; + + /** Returns a stringified version of this UUID, separating it into sections with dashes. + @returns a string in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + */ + String toDashedString() const; + + /** Creates an ID from an encoded string version. + @see toString + */ + Uuid (const String& uuidString); + + /** Copies from a stringified UUID. + The string passed in should be one that was created with the toString() method. + */ + Uuid& operator= (const String& uuidString); + + + //============================================================================== + /** Returns a pointer to the internal binary representation of the ID. + + This is an array of 16 bytes. To reconstruct a Uuid from its data, use + the constructor or operator= method that takes an array of uint8s. + */ + const uint8* getRawData() const noexcept { return uuid; } + + /** Creates a UUID from a 16-byte array. + @see getRawData + */ + Uuid (const uint8* rawData) noexcept; + + /** Sets this UUID from 16-bytes of raw data. */ + Uuid& operator= (const uint8* rawData) noexcept; + + +private: + //============================================================================== + uint8 uuid[16]; + String getHexRegion (int, int) const; + + JUCE_LEAK_DETECTOR (Uuid) +}; + + +#endif // JUCE_UUID_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_WindowsRegistry.h b/JuceLibraryCode/modules/juce_core/misc/juce_WindowsRegistry.h new file mode 100644 index 0000000..a1fb2a2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_WindowsRegistry.h @@ -0,0 +1,141 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WINDOWSREGISTRY_H_INCLUDED +#define JUCE_WINDOWSREGISTRY_H_INCLUDED + +#if JUCE_WINDOWS || DOXYGEN + +/** + Contains some static helper functions for manipulating the MS Windows registry + (Only available on Windows, of course!) +*/ +class JUCE_API WindowsRegistry +{ +public: + /** These values can be used to specify whether the 32- or 64-bit registry should be used. + When running on a 32-bit OS, there is no 64-bit registry, so the mode will be ignored. + */ + enum WoW64Mode + { + /** Default handling: 32-bit apps will use the 32-bit registry, and 64-bit apps + will use the 64-bit registry. */ + WoW64_Default = 0, + + /** Always use the 64-bit registry store. (KEY_WOW64_64KEY). */ + WoW64_64bit = 0x100, + + /** Always use the 32-bit registry store. (KEY_WOW64_32KEY). */ + WoW64_32bit = 0x200 + }; + + //============================================================================== + /** Returns a string from the registry. + The path is a string for the entire path of a value in the registry, + e.g. "HKEY_CURRENT_USER\Software\foo\bar" + */ + static String JUCE_CALLTYPE getValue (const String& regValuePath, + const String& defaultValue = String::empty, + WoW64Mode mode = WoW64_Default); + + /** Reads a binary block from the registry. + The path is a string for the entire path of a value in the registry, + e.g. "HKEY_CURRENT_USER\Software\foo\bar" + @returns a DWORD indicating the type of the key. + */ + static uint32 JUCE_CALLTYPE getBinaryValue (const String& regValuePath, MemoryBlock& resultData, WoW64Mode mode = WoW64_Default); + + /** Sets a registry value as a string. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool JUCE_CALLTYPE setValue (const String& regValuePath, const String& value, WoW64Mode mode = WoW64_Default); + + /** Sets a registry value as a DWORD. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool JUCE_CALLTYPE setValue (const String& regValuePath, uint32 value, WoW64Mode mode = WoW64_Default); + + /** Sets a registry value as a QWORD. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool JUCE_CALLTYPE setValue (const String& regValuePath, uint64 value, WoW64Mode mode = WoW64_Default); + + /** Sets a registry value as a binary block. + This will take care of creating any groups needed to get to the given registry value. + */ + static bool JUCE_CALLTYPE setValue (const String& regValuePath, const MemoryBlock& value, WoW64Mode mode = WoW64_Default); + + /** Returns true if the given value exists in the registry. */ + static bool JUCE_CALLTYPE valueExists (const String& regValuePath, WoW64Mode mode = WoW64_Default); + + /** Returns true if the given key exists in the registry. */ + static bool JUCE_CALLTYPE keyExists (const String& regValuePath, WoW64Mode mode = WoW64_Default); + + /** Deletes a registry value. */ + static void JUCE_CALLTYPE deleteValue (const String& regValuePath, WoW64Mode mode = WoW64_Default); + + /** Deletes a registry key (which is registry-talk for 'folder'). */ + static void JUCE_CALLTYPE deleteKey (const String& regKeyPath, WoW64Mode mode = WoW64_Default); + + /** Creates a file association in the registry. + + This lets you set the executable that should be launched by a given file extension. + @param fileExtension the file extension to associate, including the + initial dot, e.g. ".txt" + @param symbolicDescription a space-free short token to identify the file type + @param fullDescription a human-readable description of the file type + @param targetExecutable the executable that should be launched + @param iconResourceNumber the icon that gets displayed for the file type will be + found by looking up this resource number in the + executable. Pass 0 here to not use an icon + @param registerForCurrentUserOnly if false, this will try to register the association + for all users (you might not have permission to do this + unless running in an installer). If true, it will register the + association in HKEY_CURRENT_USER. + @param mode the WoW64 mode to use for choosing the database + */ + static bool JUCE_CALLTYPE registerFileAssociation (const String& fileExtension, + const String& symbolicDescription, + const String& fullDescription, + const File& targetExecutable, + int iconResourceNumber, + bool registerForCurrentUserOnly, + WoW64Mode mode = WoW64_Default); + + // DEPRECATED: use the other methods with a WoW64Mode parameter of WoW64_64bit instead. + JUCE_DEPRECATED (static String getValueWow64 (const String&, const String& defaultValue = String::empty)); + JUCE_DEPRECATED (static bool valueExistsWow64 (const String&)); + JUCE_DEPRECATED (static bool keyExistsWow64 (const String&)); + +private: + WindowsRegistry() JUCE_DELETED_FUNCTION; + JUCE_DECLARE_NON_COPYABLE (WindowsRegistry) +}; + +#endif +#endif // JUCE_WINDOWSREGISTRY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/native/java/JuceAppActivity.java b/JuceLibraryCode/modules/juce_core/native/java/JuceAppActivity.java new file mode 100644 index 0000000..882d0e0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/java/JuceAppActivity.java @@ -0,0 +1,746 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +package com.juce; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.graphics.*; +import android.opengl.*; +import android.text.ClipboardManager; +import android.text.InputType; +import android.util.DisplayMetrics; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.HttpURLConnection; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import android.media.AudioManager; +import android.media.MediaScannerConnection; +import android.media.MediaScannerConnection.MediaScannerConnectionClient; + +//============================================================================== +public final class JuceAppActivity extends Activity +{ + //============================================================================== + static + { + System.loadLibrary ("juce_jni"); + } + + @Override + public final void onCreate (Bundle savedInstanceState) + { + super.onCreate (savedInstanceState); + + viewHolder = new ViewHolder (this); + setContentView (viewHolder); + + setVolumeControlStream (AudioManager.STREAM_MUSIC); + } + + @Override + protected final void onDestroy() + { + quitApp(); + super.onDestroy(); + } + + @Override + protected final void onPause() + { + if (viewHolder != null) + viewHolder.onPause(); + + suspendApp(); + super.onPause(); + } + + @Override + protected final void onResume() + { + super.onResume(); + + if (viewHolder != null) + viewHolder.onResume(); + + resumeApp(); + } + + @Override + public void onConfigurationChanged (Configuration cfg) + { + super.onConfigurationChanged (cfg); + setContentView (viewHolder); + } + + private void callAppLauncher() + { + launchApp (getApplicationInfo().publicSourceDir, + getApplicationInfo().dataDir); + } + + //============================================================================== + private native void launchApp (String appFile, String appDataDir); + private native void quitApp(); + private native void suspendApp(); + private native void resumeApp(); + private native void setScreenSize (int screenWidth, int screenHeight, int dpi); + + //============================================================================== + public native void deliverMessage (long value); + private android.os.Handler messageHandler = new android.os.Handler(); + + public final void postMessage (long value) + { + messageHandler.post (new MessageCallback (value)); + } + + private final class MessageCallback implements Runnable + { + public MessageCallback (long value_) { value = value_; } + public final void run() { deliverMessage (value); } + + private long value; + } + + //============================================================================== + private ViewHolder viewHolder; + + public final ComponentPeerView createNewView (boolean opaque, long host) + { + ComponentPeerView v = new ComponentPeerView (this, opaque, host); + viewHolder.addView (v); + return v; + } + + public final void deleteView (ComponentPeerView view) + { + ViewGroup group = (ViewGroup) (view.getParent()); + + if (group != null) + group.removeView (view); + } + + final class ViewHolder extends ViewGroup + { + public ViewHolder (Context context) + { + super (context); + setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS); + setFocusable (false); + } + + protected final void onLayout (boolean changed, int left, int top, int right, int bottom) + { + setScreenSize (getWidth(), getHeight(), getDPI()); + + if (isFirstResize) + { + isFirstResize = false; + callAppLauncher(); + } + } + + public final void onPause() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof ComponentPeerView) + ((ComponentPeerView) v).onPause(); + } + } + + public final void onResume() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof ComponentPeerView) + ((ComponentPeerView) v).onResume(); + } + } + + private final int getDPI() + { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics (metrics); + return metrics.densityDpi; + } + + private boolean isFirstResize = true; + } + + public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom) + { + canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE); + } + + //============================================================================== + public final String getClipboardContent() + { + ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); + return clipboard.getText().toString(); + } + + public final void setClipboardContent (String newText) + { + ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); + clipboard.setText (newText); + } + + //============================================================================== + public final void showMessageBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("OK", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public final void showOkCancelBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("OK", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public final void showYesNoCancelBox (String title, String message, final long callback) + { + AlertDialog.Builder builder = new AlertDialog.Builder (this); + builder.setTitle (title) + .setMessage (message) + .setCancelable (true) + .setPositiveButton ("Yes", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("No", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 2); + } + }) + .setNeutralButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceAppActivity.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public native void alertDismissed (long callback, int id); + + //============================================================================== + public final class ComponentPeerView extends ViewGroup + implements View.OnFocusChangeListener + { + public ComponentPeerView (Context context, boolean opaque_, long host) + { + super (context); + this.host = host; + setWillNotDraw (false); + opaque = opaque_; + + setFocusable (true); + setFocusableInTouchMode (true); + setOnFocusChangeListener (this); + requestFocus(); + } + + //============================================================================== + private native void handlePaint (long host, Canvas canvas); + + @Override + public void onDraw (Canvas canvas) + { + handlePaint (host, canvas); + } + + @Override + public boolean isOpaque() + { + return opaque; + } + + private boolean opaque; + private long host; + + //============================================================================== + private native void handleMouseDown (long host, int index, float x, float y, long time); + private native void handleMouseDrag (long host, int index, float x, float y, long time); + private native void handleMouseUp (long host, int index, float x, float y, long time); + + @Override + public boolean onTouchEvent (MotionEvent event) + { + int action = event.getAction(); + long time = event.getEventTime(); + + switch (action & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_DOWN: + handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time); + return true; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time); + return true; + + case MotionEvent.ACTION_MOVE: + { + int n = event.getPointerCount(); + for (int i = 0; i < n; ++i) + handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time); + + return true; + } + + case MotionEvent.ACTION_POINTER_UP: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time); + return true; + } + + case MotionEvent.ACTION_POINTER_DOWN: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time); + return true; + } + + default: + break; + } + + return false; + } + + //============================================================================== + private native void handleKeyDown (long host, int keycode, int textchar); + private native void handleKeyUp (long host, int keycode, int textchar); + + public void showKeyboard (String type) + { + InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE); + + if (imm != null) + { + if (type.length() > 0) + { + imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); + imm.setInputMethod (getWindowToken(), type); + } + else + { + imm.hideSoftInputFromWindow (getWindowToken(), 0); + } + } + } + + @Override + public boolean onKeyDown (int keyCode, KeyEvent event) + { + switch (keyCode) + { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + return super.onKeyDown (keyCode, event); + + default: break; + } + + handleKeyDown (host, keyCode, event.getUnicodeChar()); + return true; + } + + @Override + public boolean onKeyUp (int keyCode, KeyEvent event) + { + handleKeyUp (host, keyCode, event.getUnicodeChar()); + return true; + } + + // this is here to make keyboard entry work on a Galaxy Tab2 10.1 + @Override + public InputConnection onCreateInputConnection (EditorInfo outAttrs) + { + outAttrs.actionLabel = ""; + outAttrs.hintText = ""; + outAttrs.initialCapsMode = 0; + outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; + outAttrs.label = ""; + outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; + outAttrs.inputType = InputType.TYPE_NULL; + + return new BaseInputConnection (this, false); + } + + //============================================================================== + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) + { + super.onSizeChanged (w, h, oldw, oldh); + viewSizeChanged (host); + } + + @Override + protected void onLayout (boolean changed, int left, int top, int right, int bottom) + { + for (int i = getChildCount(); --i >= 0;) + requestTransparentRegion (getChildAt (i)); + } + + private native void viewSizeChanged (long host); + + @Override + public void onFocusChange (View v, boolean hasFocus) + { + if (v == this) + focusChanged (host, hasFocus); + } + + private native void focusChanged (long host, boolean hasFocus); + + public void setViewName (String newName) {} + + public boolean isVisible() { return getVisibility() == VISIBLE; } + public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); } + + public boolean containsPoint (int x, int y) + { + return true; //xxx needs to check overlapping views + } + + public final void onPause() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof OpenGLView) + ((OpenGLView) v).onPause(); + } + } + + public final void onResume() + { + for (int i = getChildCount(); --i >= 0;) + { + View v = getChildAt (i); + + if (v instanceof OpenGLView) + ((OpenGLView) v).onResume(); + } + } + + public OpenGLView createGLView() + { + OpenGLView glView = new OpenGLView (getContext()); + addView (glView); + return glView; + } + } + + //============================================================================== + public final class OpenGLView extends GLSurfaceView + implements GLSurfaceView.Renderer + { + OpenGLView (Context context) + { + super (context); + setEGLContextClientVersion (2); + setRenderer (this); + setRenderMode (RENDERMODE_WHEN_DIRTY); + } + + @Override + public void onSurfaceCreated (GL10 unused, EGLConfig config) + { + contextCreated(); + } + + @Override + public void onSurfaceChanged (GL10 unused, int width, int height) + { + contextChangedSize(); + } + + @Override + public void onDrawFrame (GL10 unused) + { + render(); + } + + private native void contextCreated(); + private native void contextChangedSize(); + private native void render(); + } + + //============================================================================== + public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds) + { + Path p = new Path(); + paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p); + + RectF boundsF = new RectF(); + p.computeBounds (boundsF, true); + matrix.mapRect (boundsF); + + boundsF.roundOut (bounds); + bounds.left--; + bounds.right++; + + final int w = bounds.width(); + final int h = Math.max (1, bounds.height()); + + Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888); + + Canvas c = new Canvas (bm); + matrix.postTranslate (-bounds.left, -bounds.top); + c.setMatrix (matrix); + c.drawPath (p, paint); + + final int sizeNeeded = w * h; + if (cachedRenderArray.length < sizeNeeded) + cachedRenderArray = new int [sizeNeeded]; + + bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h); + bm.recycle(); + return cachedRenderArray; + } + + private int[] cachedRenderArray = new int [256]; + + //============================================================================== + public static class HTTPStream + { + public HTTPStream (HttpURLConnection connection_, + int[] statusCode, StringBuffer responseHeaders) throws IOException + { + connection = connection_; + + try + { + inputStream = new BufferedInputStream (connection.getInputStream()); + } + catch (IOException e) + { + if (connection.getResponseCode() < org.apache.http.HttpStatus.SC_BAD_REQUEST) + throw e; + } + finally + { + statusCode[0] = connection.getResponseCode(); + } + + if (statusCode[0] >= org.apache.http.HttpStatus.SC_BAD_REQUEST) + inputStream = connection.getErrorStream(); + else + inputStream = connection.getInputStream(); + + for (java.util.Map.Entry> entry : connection.getHeaderFields().entrySet()) + if (entry.getKey() != null && entry.getValue() != null) + responseHeaders.append (entry.getKey() + ": " + + android.text.TextUtils.join (",", entry.getValue()) + "\n"); + } + + public final void release() + { + try + { + inputStream.close(); + } + catch (IOException e) + {} + + connection.disconnect(); + } + + public final int read (byte[] buffer, int numBytes) + { + int num = 0; + + try + { + num = inputStream.read (buffer, 0, numBytes); + } + catch (IOException e) + {} + + if (num > 0) + position += num; + + return num; + } + + public final long getPosition() { return position; } + public final long getTotalLength() { return -1; } + public final boolean isExhausted() { return false; } + public final boolean setPosition (long newPos) { return false; } + + private HttpURLConnection connection; + private InputStream inputStream; + private long position; + } + + public static final HTTPStream createHTTPStream (String address, + boolean isPost, byte[] postData, String headers, + int timeOutMs, int[] statusCode, + StringBuffer responseHeaders) + { + try + { + HttpURLConnection connection = (HttpURLConnection) (new URL(address) + .openConnection()); + if (connection != null) + { + try + { + if (isPost) + { + connection.setRequestMethod("POST"); + connection.setConnectTimeout(timeOutMs); + connection.setDoOutput(true); + connection.setChunkedStreamingMode(0); + OutputStream out = connection.getOutputStream(); + out.write(postData); + out.flush(); + } + + return new HTTPStream (connection, statusCode, responseHeaders); + } + catch (Throwable e) + { + connection.disconnect(); + } + } + } + catch (Throwable e) {} + + return null; + } + + public final void launchURL (String url) + { + startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url))); + } + + public static final String getLocaleValue (boolean isRegion) + { + java.util.Locale locale = java.util.Locale.getDefault(); + + return isRegion ? locale.getDisplayCountry (java.util.Locale.US) + : locale.getDisplayLanguage (java.util.Locale.US); + } + + //============================================================================== + private final class SingleMediaScanner implements MediaScannerConnectionClient + { + public SingleMediaScanner (Context context, String filename) + { + file = filename; + msc = new MediaScannerConnection (context, this); + msc.connect(); + } + + @Override + public void onMediaScannerConnected() + { + msc.scanFile (file, null); + } + + @Override + public void onScanCompleted (String path, Uri uri) + { + msc.disconnect(); + } + + private MediaScannerConnection msc; + private String file; + } + + public final void scanFile (String filename) + { + new SingleMediaScanner (this, filename); + } +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h new file mode 100644 index 0000000..cdaa0ef --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -0,0 +1,223 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BASICNATIVEHEADERS_H_INCLUDED +#define JUCE_BASICNATIVEHEADERS_H_INCLUDED + +#include "../system/juce_TargetPlatform.h" +#undef T + +//============================================================================== +#if JUCE_MAC || JUCE_IOS + + #if JUCE_IOS + #import + #import + #import + #import + #include + #else + #define Point CarbonDummyPointName + #define Component CarbonDummyCompName + #import + #import + #undef Point + #undef Component + #include + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +//============================================================================== +#elif JUCE_WINDOWS + #if JUCE_MSVC + #ifndef _CPPRTTI + #error "You're compiling without RTTI enabled! This is needed for a lot of JUCE classes, please update your compiler settings!" + #endif + + #ifndef _CPPUNWIND + #error "You're compiling without exceptions enabled! This is needed for a lot of JUCE classes, please update your compiler settings!" + #endif + + #pragma warning (push) + #pragma warning (disable : 4100 4201 4514 4312 4995) + #endif + + #define STRICT 1 + #define WIN32_LEAN_AND_MEAN 1 + #if JUCE_MINGW + #define _WIN32_WINNT 0x0501 + #else + #define _WIN32_WINNT 0x0600 + #endif + #define _UNICODE 1 + #define UNICODE 1 + #ifndef _WIN32_IE + #define _WIN32_IE 0x0500 + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #if JUCE_MINGW + #include + #else + #include + #include + #endif + + #undef PACKED + + #if JUCE_MSVC + #pragma warning (pop) + #pragma warning (4: 4511 4512 4100 /*4365*/) // (enable some warnings that are turned off in VC8) + #endif + + #if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES + #pragma comment (lib, "kernel32.lib") + #pragma comment (lib, "user32.lib") + #pragma comment (lib, "wininet.lib") + #pragma comment (lib, "advapi32.lib") + #pragma comment (lib, "ws2_32.lib") + #pragma comment (lib, "version.lib") + #pragma comment (lib, "shlwapi.lib") + #pragma comment (lib, "winmm.lib") + + #ifdef _NATIVE_WCHAR_T_DEFINED + #ifdef _DEBUG + #pragma comment (lib, "comsuppwd.lib") + #else + #pragma comment (lib, "comsuppw.lib") + #endif + #else + #ifdef _DEBUG + #pragma comment (lib, "comsuppd.lib") + #else + #pragma comment (lib, "comsupp.lib") + #endif + #endif + #endif + + /* Used with DynamicLibrary to simplify importing functions from a win32 DLL. + + dll: the DynamicLibrary object + functionName: function to import + localFunctionName: name you want to use to actually call it (must be different) + returnType: the return type + params: list of params (bracketed) + */ + #define JUCE_LOAD_WINAPI_FUNCTION(dll, functionName, localFunctionName, returnType, params) \ + typedef returnType (WINAPI *type##localFunctionName) params; \ + type##localFunctionName localFunctionName = (type##localFunctionName) dll.getFunction (#functionName); + +//============================================================================== +#elif JUCE_LINUX + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +//============================================================================== +#elif JUCE_ANDROID + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +// Need to clear various moronic redefinitions made by system headers.. +#undef max +#undef min +#undef direct +#undef check + +#endif // JUCE_BASICNATIVEHEADERS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp new file mode 100644 index 0000000..45903b1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp @@ -0,0 +1,104 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +bool File::isOnCDRomDrive() const +{ + return false; +} + +bool File::isOnHardDisk() const +{ + return true; +} + +bool File::isOnRemovableDrive() const +{ + return false; +} + +String File::getVersion() const +{ + return String::empty; +} + +File File::getSpecialLocation (const SpecialLocationType type) +{ + switch (type) + { + case userHomeDirectory: + case userDocumentsDirectory: + case userMusicDirectory: + case userMoviesDirectory: + case userPicturesDirectory: + case userApplicationDataDirectory: + case userDesktopDirectory: + return File (android.appDataDir); + + case commonApplicationDataDirectory: + case commonDocumentsDirectory: + return File (android.appDataDir); + + case globalApplicationsDirectory: + return File ("/system/app"); + + case tempDirectory: + return File (android.appDataDir).getChildFile (".temp"); + + case invokedExecutableFile: + case currentExecutableFile: + case currentApplicationFile: + case hostApplicationPath: + return juce_getExecutableFile(); + + default: + jassertfalse; // unknown type? + break; + } + + return File(); +} + +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + // TODO + return false; +} + +JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& parameters) +{ + const LocalRef t (javaString (fileName)); + android.activity.callVoidMethod (JuceAppActivity.launchURL, t.get()); + return true; +} + +void File::revealToUser() const +{ +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h new file mode 100644 index 0000000..8e8b07d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h @@ -0,0 +1,428 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ANDROID_JNIHELPERS_H_INCLUDED +#define JUCE_ANDROID_JNIHELPERS_H_INCLUDED + +#if ! (defined (JUCE_ANDROID_ACTIVITY_CLASSNAME) && defined (JUCE_ANDROID_ACTIVITY_CLASSPATH)) + #error "The JUCE_ANDROID_ACTIVITY_CLASSNAME and JUCE_ANDROID_ACTIVITY_CLASSPATH macros must be set!" +#endif + +//============================================================================== +extern JNIEnv* getEnv() noexcept; + +//============================================================================== +class GlobalRef +{ +public: + inline GlobalRef() noexcept : obj (0) {} + inline explicit GlobalRef (jobject o) : obj (retain (o)) {} + inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj)) {} + ~GlobalRef() { clear(); } + + inline void clear() + { + if (obj != 0) + { + getEnv()->DeleteGlobalRef (obj); + obj = 0; + } + } + + inline GlobalRef& operator= (const GlobalRef& other) + { + jobject newObj = retain (other.obj); + clear(); + obj = newObj; + return *this; + } + + //============================================================================== + inline operator jobject() const noexcept { return obj; } + inline jobject get() const noexcept { return obj; } + + //============================================================================== + #define DECLARE_CALL_TYPE_METHOD(returnType, typeName) \ + returnType call##typeName##Method (jmethodID methodID, ... ) const \ + { \ + va_list args; \ + va_start (args, methodID); \ + returnType result = getEnv()->Call##typeName##MethodV (obj, methodID, args); \ + va_end (args); \ + return result; \ + } + + DECLARE_CALL_TYPE_METHOD (jobject, Object) + DECLARE_CALL_TYPE_METHOD (jboolean, Boolean) + DECLARE_CALL_TYPE_METHOD (jbyte, Byte) + DECLARE_CALL_TYPE_METHOD (jchar, Char) + DECLARE_CALL_TYPE_METHOD (jshort, Short) + DECLARE_CALL_TYPE_METHOD (jint, Int) + DECLARE_CALL_TYPE_METHOD (jlong, Long) + DECLARE_CALL_TYPE_METHOD (jfloat, Float) + DECLARE_CALL_TYPE_METHOD (jdouble, Double) + #undef DECLARE_CALL_TYPE_METHOD + + void callVoidMethod (jmethodID methodID, ... ) const + { + va_list args; + va_start (args, methodID); + getEnv()->CallVoidMethodV (obj, methodID, args); + va_end (args); + } + +private: + //============================================================================== + jobject obj; + + static inline jobject retain (jobject obj) + { + return obj == 0 ? 0 : getEnv()->NewGlobalRef (obj); + } +}; + +//============================================================================== +template +class LocalRef +{ +public: + explicit inline LocalRef (JavaType o) noexcept : obj (o) {} + inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} + ~LocalRef() { clear(); } + + void clear() + { + if (obj != 0) + getEnv()->DeleteLocalRef (obj); + } + + LocalRef& operator= (const LocalRef& other) + { + jobject newObj = retain (other.obj); + clear(); + obj = newObj; + return *this; + } + + inline operator JavaType() const noexcept { return obj; } + inline JavaType get() const noexcept { return obj; } + +private: + JavaType obj; + + static JavaType retain (JavaType obj) + { + return obj == 0 ? 0 : (JavaType) getEnv()->NewLocalRef (obj); + } +}; + +//============================================================================== +namespace +{ + String juceString (JNIEnv* env, jstring s) + { + const char* const utf8 = env->GetStringUTFChars (s, nullptr); + CharPointer_UTF8 utf8CP (utf8); + const String result (utf8CP); + env->ReleaseStringUTFChars (s, utf8); + return result; + } + + String juceString (jstring s) + { + return juceString (getEnv(), s); + } + + LocalRef javaString (const String& s) + { + return LocalRef (getEnv()->NewStringUTF (s.toUTF8())); + } + + LocalRef javaStringFromChar (const juce_wchar c) + { + char utf8[8] = { 0 }; + CharPointer_UTF8 (utf8).write (c); + return LocalRef (getEnv()->NewStringUTF (utf8)); + } +} + +//============================================================================== +class JNIClassBase +{ +public: + explicit JNIClassBase (const char* classPath); + virtual ~JNIClassBase(); + + inline operator jclass() const noexcept { return classRef; } + + static void initialiseAllClasses (JNIEnv*); + static void releaseAllClasses (JNIEnv*); + +protected: + virtual void initialiseFields (JNIEnv*) = 0; + + jmethodID resolveMethod (JNIEnv*, const char* methodName, const char* params); + jmethodID resolveStaticMethod (JNIEnv*, const char* methodName, const char* params); + jfieldID resolveField (JNIEnv*, const char* fieldName, const char* signature); + jfieldID resolveStaticField (JNIEnv*, const char* fieldName, const char* signature); + +private: + const char* const classPath; + jclass classRef; + + static Array& getClasses(); + void initialise (JNIEnv*); + void release (JNIEnv*); + + JUCE_DECLARE_NON_COPYABLE (JNIClassBase) +}; + +//============================================================================== +#define CREATE_JNI_METHOD(methodID, stringName, params) methodID = resolveMethod (env, stringName, params); +#define CREATE_JNI_STATICMETHOD(methodID, stringName, params) methodID = resolveStaticMethod (env, stringName, params); +#define CREATE_JNI_FIELD(fieldID, stringName, signature) fieldID = resolveField (env, stringName, signature); +#define CREATE_JNI_STATICFIELD(fieldID, stringName, signature) fieldID = resolveStaticField (env, stringName, signature); +#define DECLARE_JNI_METHOD(methodID, stringName, params) jmethodID methodID; +#define DECLARE_JNI_FIELD(fieldID, stringName, signature) jfieldID fieldID; + +#define DECLARE_JNI_CLASS(CppClassName, javaPath) \ + class CppClassName ## _Class : public JNIClassBase \ + { \ + public: \ + CppClassName ## _Class() : JNIClassBase (javaPath) {} \ + \ + void initialiseFields (JNIEnv* env) \ + { \ + JNI_CLASS_MEMBERS (CREATE_JNI_METHOD, CREATE_JNI_STATICMETHOD, CREATE_JNI_FIELD, CREATE_JNI_STATICFIELD); \ + } \ + \ + JNI_CLASS_MEMBERS (DECLARE_JNI_METHOD, DECLARE_JNI_METHOD, DECLARE_JNI_FIELD, DECLARE_JNI_FIELD); \ + }; \ + static CppClassName ## _Class CppClassName; + + +//============================================================================== +#define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ + extern "C" __attribute__ ((visibility("default"))) returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params + +//============================================================================== +class AndroidSystem +{ +public: + AndroidSystem(); + + void initialise (JNIEnv*, jobject activity, jstring appFile, jstring appDataDir); + void shutdown (JNIEnv*); + + //============================================================================== + GlobalRef activity; + String appFile, appDataDir; + int screenWidth, screenHeight, dpi; +}; + +extern AndroidSystem android; + +//============================================================================== +class ThreadLocalJNIEnvHolder +{ +public: + ThreadLocalJNIEnvHolder() noexcept + : jvm (nullptr) + { + zeromem (threads, sizeof (threads)); + zeromem (envs, sizeof (envs)); + } + + void initialise (JNIEnv* env) + { + // NB: the DLL can be left loaded by the JVM, so the same static + // objects can end up being reused by subsequent runs of the app + zeromem (threads, sizeof (threads)); + zeromem (envs, sizeof (envs)); + + env->GetJavaVM (&jvm); + addEnv (env); + } + + JNIEnv* attach() noexcept + { + if (android.activity != nullptr) + { + if (JNIEnv* env = attachToCurrentThread()) + { + SpinLock::ScopedLockType sl (addRemoveLock); + return addEnv (env); + } + + jassertfalse; + } + + return nullptr; + } + + void detach() noexcept + { + if (android.activity != nullptr) + { + jvm->DetachCurrentThread(); + + const pthread_t thisThread = pthread_self(); + + SpinLock::ScopedLockType sl (addRemoveLock); + for (int i = 0; i < maxThreads; ++i) + if (threads[i] == thisThread) + threads[i] = 0; + } + } + + JNIEnv* getOrAttach() noexcept + { + if (JNIEnv* env = get()) + return env; + + SpinLock::ScopedLockType sl (addRemoveLock); + + if (JNIEnv* env = get()) + return env; + + if (JNIEnv* env = attachToCurrentThread()) + return addEnv (env); + + return nullptr; + } + +private: + JavaVM* jvm; + enum { maxThreads = 32 }; + pthread_t threads [maxThreads]; + JNIEnv* envs [maxThreads]; + SpinLock addRemoveLock; + + JNIEnv* addEnv (JNIEnv* env) noexcept + { + const pthread_t thisThread = pthread_self(); + + for (int i = 0; i < maxThreads; ++i) + { + if (threads[i] == 0) + { + envs[i] = env; + threads[i] = thisThread; + return env; + } + } + + jassertfalse; // too many threads! + return nullptr; + } + + JNIEnv* get() const noexcept + { + const pthread_t thisThread = pthread_self(); + + for (int i = 0; i < maxThreads; ++i) + if (threads[i] == thisThread) + return envs[i]; + + return nullptr; + } + + JNIEnv* attachToCurrentThread() + { + JNIEnv* env = nullptr; + jvm->AttachCurrentThread (&env, nullptr); + return env; + } +}; + +extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; + +struct AndroidThreadScope +{ + AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); } + ~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); } +}; + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ + METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ + METHOD (postMessage, "postMessage", "(J)V") \ + METHOD (finish, "finish", "()V") \ + METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ + METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ + METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ + METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ + STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ + METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ + METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ + STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ + METHOD (scanFile, "scanFile", "(Ljava/lang/String;)V") + +DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(I)V") \ + METHOD (setColor, "setColor", "(I)V") \ + METHOD (setAlpha, "setAlpha", "(I)V") \ + METHOD (setTypeface, "setTypeface", "(Landroid/graphics/Typeface;)Landroid/graphics/Typeface;") \ + METHOD (ascent, "ascent", "()F") \ + METHOD (descent, "descent", "()F") \ + METHOD (setTextSize, "setTextSize", "(F)V") \ + METHOD (getTextWidths, "getTextWidths", "(Ljava/lang/String;[F)I") \ + METHOD (setTextScaleX, "setTextScaleX", "(F)V") \ + METHOD (getTextPath, "getTextPath", "(Ljava/lang/String;IIFFLandroid/graphics/Path;)V") \ + METHOD (setShader, "setShader", "(Landroid/graphics/Shader;)Landroid/graphics/Shader;") \ + +DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "()V") \ + METHOD (setValues, "setValues", "([F)V") \ + +DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(IIII)V") \ + FIELD (left, "left", "I") \ + FIELD (right, "right", "I") \ + FIELD (top, "top", "I") \ + FIELD (bottom, "bottom", "I") \ + +DECLARE_JNI_CLASS (RectClass, "android/graphics/Rect"); +#undef JNI_CLASS_MEMBERS + +#endif // JUCE_ANDROID_JNIHELPERS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Misc.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_Misc.cpp new file mode 100644 index 0000000..d26adc1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_Misc.cpp @@ -0,0 +1,32 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +void Logger::outputDebugString (const String& text) +{ + __android_log_print (ANDROID_LOG_INFO, "JUCE", "%s", text.toUTF8().getAddress()); +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_Network.cpp new file mode 100644 index 0000000..29a8cb5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_Network.cpp @@ -0,0 +1,181 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "()V") \ + METHOD (toString, "toString", "()Ljava/lang/String;") \ + +DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (release, "release", "()V") \ + METHOD (read, "read", "([BI)I") \ + METHOD (getPosition, "getPosition", "()J") \ + METHOD (getTotalLength, "getTotalLength", "()J") \ + METHOD (isExhausted, "isExhausted", "()Z") \ + METHOD (setPosition, "setPosition", "(J)Z") \ + +DECLARE_JNI_CLASS (HTTPStream, JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream"); +#undef JNI_CLASS_MEMBERS + + +//============================================================================== +void MACAddress::findAllAddresses (Array& result) +{ + // TODO +} + + +JUCE_API bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + // TODO + return false; +} + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (String address, bool isPost, const MemoryBlock& postData, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers, int timeOutMs, StringPairArray* responseHeaders) + : statusCode (0) + { + if (! address.contains ("://")) + address = "http://" + address; + + JNIEnv* env = getEnv(); + + jbyteArray postDataArray = 0; + + if (postData.getSize() > 0) + { + postDataArray = env->NewByteArray (postData.getSize()); + env->SetByteArrayRegion (postDataArray, 0, postData.getSize(), (const jbyte*) postData.getData()); + } + + LocalRef responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor)); + + // Annoyingly, the android HTTP functions will choke on this call if you try to do it on the message + // thread. You'll need to move your networking code to a background thread to keep it happy.. + jassert (Thread::getCurrentThread() != nullptr); + + jintArray statusCodeArray = env->NewIntArray (1); + jassert (statusCodeArray != 0); + + stream = GlobalRef (env->CallStaticObjectMethod (JuceAppActivity, + JuceAppActivity.createHTTPStream, + javaString (address).get(), + (jboolean) isPost, + postDataArray, + javaString (headers).get(), + (jint) timeOutMs, + statusCodeArray, + responseHeaderBuffer.get())); + + jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0); + statusCode = statusCodeElements[0]; + env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0); + env->DeleteLocalRef (statusCodeArray); + + if (postDataArray != 0) + env->DeleteLocalRef (postDataArray); + + if (stream != 0) + { + StringArray headerLines; + + { + LocalRef headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(), + StringBuffer.toString)); + headerLines.addLines (juceString (env, headersString)); + } + + if (responseHeaders != 0) + { + for (int i = 0; i < headerLines.size(); ++i) + { + const String& header = headerLines[i]; + const String key (header.upToFirstOccurrenceOf (": ", false, false)); + const String value (header.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue ((*responseHeaders) [key]); + + responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + } + } + } + + ~WebInputStream() + { + if (stream != 0) + stream.callVoidMethod (HTTPStream.release); + } + + //============================================================================== + bool isError() const { return stream == nullptr; } + + bool isExhausted() override { return stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted); } + int64 getTotalLength() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0; } + int64 getPosition() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0; } + bool setPosition (int64 wantedPos) override { return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); } + + int read (void* buffer, int bytesToRead) override + { + jassert (buffer != nullptr && bytesToRead >= 0); + + if (stream == nullptr) + return 0; + + JNIEnv* env = getEnv(); + + jbyteArray javaArray = env->NewByteArray (bytesToRead); + + int numBytes = stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead); + + if (numBytes > 0) + env->GetByteArrayRegion (javaArray, 0, numBytes, static_cast (buffer)); + + env->DeleteLocalRef (javaArray); + return numBytes; + } + + //============================================================================== + GlobalRef stream; + int statusCode; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_SystemStats.cpp new file mode 100644 index 0000000..b012381 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_SystemStats.cpp @@ -0,0 +1,315 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +JNIClassBase::JNIClassBase (const char* cp) : classPath (cp), classRef (0) +{ + getClasses().add (this); +} + +JNIClassBase::~JNIClassBase() +{ + getClasses().removeFirstMatchingValue (this); +} + +Array& JNIClassBase::getClasses() +{ + static Array classes; + return classes; +} + +void JNIClassBase::initialise (JNIEnv* env) +{ + classRef = (jclass) env->NewGlobalRef (env->FindClass (classPath)); + jassert (classRef != 0); + + initialiseFields (env); +} + +void JNIClassBase::release (JNIEnv* env) +{ + env->DeleteGlobalRef (classRef); +} + +void JNIClassBase::initialiseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->initialise (env); +} + +void JNIClassBase::releaseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->release (env); +} + +jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetMethodID (classRef, methodName, params); + jassert (m != 0); + return m; +} + +jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetStaticMethodID (classRef, methodName, params); + jassert (m != 0); + return m; +} + +jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +//============================================================================== +ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; + +#if JUCE_DEBUG +static bool systemInitialised = false; +#endif + +JNIEnv* getEnv() noexcept +{ + #if JUCE_DEBUG + if (! systemInitialised) + { + DBG ("*** Call to getEnv() when system not initialised"); + jassertfalse; + std::exit (EXIT_FAILURE); + } + #endif + + return threadLocalJNIEnvHolder.getOrAttach(); +} + +extern "C" jint JNI_OnLoad (JavaVM*, void*) +{ + return JNI_VERSION_1_2; +} + +//============================================================================== +AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) +{ +} + +void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) +{ + screenWidth = screenHeight = 0; + dpi = 160; + JNIClassBase::initialiseAllClasses (env); + + threadLocalJNIEnvHolder.initialise (env); + #if JUCE_DEBUG + systemInitialised = true; + #endif + + activity = GlobalRef (act); + appFile = juceString (env, file); + appDataDir = juceString (env, dataDir); +} + +void AndroidSystem::shutdown (JNIEnv* env) +{ + activity.clear(); + + #if JUCE_DEBUG + systemInitialised = false; + #endif + + JNIClassBase::releaseAllClasses (env); +} + +AndroidSystem android; + +//============================================================================== +namespace AndroidStatsHelpers +{ + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + STATICMETHOD (getProperty, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;") + + DECLARE_JNI_CLASS (SystemClass, "java/lang/System"); + #undef JNI_CLASS_MEMBERS + + String getSystemProperty (const String& name) + { + return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, + SystemClass.getProperty, + javaString (name).get()))); + } + + String getLocaleValue (bool isRegion) + { + return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, + JuceAppActivity.getLocaleValue, + isRegion))); + } + + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) + DECLARE_JNI_CLASS (BuildClass, "android/os/Build"); + #undef JNI_CLASS_MEMBERS + + String getAndroidOsBuildValue (const char* fieldName) + { + return juceString (LocalRef ((jstring) getEnv()->GetStaticObjectField ( + BuildClass, getEnv()->GetStaticFieldID (BuildClass, fieldName, "Ljava/lang/String;")))); + } +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + return Android; +} + +String SystemStats::getOperatingSystemName() +{ + return "Android " + AndroidStatsHelpers::getSystemProperty ("os.version"); +} + +String SystemStats::getDeviceDescription() +{ + return AndroidStatsHelpers::getAndroidOsBuildValue ("MODEL") + + "-" + AndroidStatsHelpers::getAndroidOsBuildValue ("SERIAL"); +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_64BIT + return true; + #else + return false; + #endif +} + +String SystemStats::getCpuVendor() +{ + return AndroidStatsHelpers::getSystemProperty ("os.arch"); +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + return 0; // TODO +} + +int SystemStats::getMemorySizeInMegabytes() +{ + #if __ANDROID_API__ >= 9 + struct sysinfo sysi; + + if (sysinfo (&sysi) == 0) + return (sysi.totalram * sysi.mem_unit / (1024 * 1024)); + #endif + + return 0; +} + +int SystemStats::getPageSize() +{ + return sysconf (_SC_PAGESIZE); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + if (const char* user = getenv ("USER")) + return CharPointer_UTF8 (user); + + if (struct passwd* const pw = getpwuid (getuid())) + return CharPointer_UTF8 (pw->pw_name); + + return String::empty; +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return name; + + return String::empty; +} + + +String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } +String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } +String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + numCpus = jmax (1, (int) sysconf (_SC_NPROCESSORS_ONLN)); +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return t.tv_sec * 1000 + t.tv_nsec / 1000000; +} + +int64 Time::getHighResolutionTicks() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); +} + +int64 Time::getHighResolutionTicksPerSecond() noexcept +{ + return 1000000; // (microseconds) +} + +double Time::getMillisecondCounterHiRes() noexcept +{ + return getHighResolutionTicks() * 0.001; +} + +bool Time::setSystemTimeToThisTime() const +{ + jassertfalse; + return false; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp new file mode 100644 index 0000000..981be9a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp @@ -0,0 +1,76 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +// sets the process to 0=low priority, 1=normal, 2=high, 3=realtime +JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) +{ + // TODO + + struct sched_param param; + int policy, maxp, minp; + + const int p = (int) prior; + + if (p <= 1) + policy = SCHED_OTHER; + else + policy = SCHED_RR; + + minp = sched_get_priority_min (policy); + maxp = sched_get_priority_max (policy); + + if (p < 2) + param.sched_priority = 0; + else if (p == 2 ) + // Set to middle of lower realtime priority range + param.sched_priority = minp + (maxp - minp) / 4; + else + // Set to middle of higher realtime priority range + param.sched_priority = minp + (3 * (maxp - minp) / 4); + + pthread_setschedparam (pthread_self(), policy, ¶m); +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + return false; +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} + +JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} +JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_CommonFile.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_CommonFile.cpp new file mode 100644 index 0000000..2ce98b3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_CommonFile.cpp @@ -0,0 +1,153 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +bool File::copyInternal (const File& dest) const +{ + FileInputStream in (*this); + + if (dest.deleteFile()) + { + { + FileOutputStream out (dest); + + if (out.failedToOpen()) + return false; + + if (out.writeFromInputStream (in, -1) == getSize()) + return true; + } + + dest.deleteFile(); + } + + return false; +} + +void File::findFileSystemRoots (Array& destArray) +{ + destArray.add (File ("/")); +} + +bool File::isHidden() const +{ + return getFileName().startsWithChar ('.'); +} + +static String getLinkedFile (StringRef file) +{ + HeapBlock buffer (8194); + const int numBytes = (int) readlink (file.text, buffer, 8192); + return String::fromUTF8 (buffer, jmax (0, numBytes)); +}; + +bool File::isLink() const +{ + return getLinkedFile (getFullPathName()).isNotEmpty(); +} + +File File::getLinkedTarget() const +{ + String f (getLinkedFile (getFullPathName())); + + if (f.isNotEmpty()) + return getSiblingFile (f); + + return *this; +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wc) + : parentDir (File::addTrailingSeparator (directory.getFullPathName())), + wildCard (wc), dir (opendir (directory.getFullPathName().toUTF8())) + { + } + + ~Pimpl() + { + if (dir != nullptr) + closedir (dir); + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + if (dir != nullptr) + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + struct dirent* const de = readdir (dir); + + if (de == nullptr) + break; + + if (wildcardUTF8 == nullptr) + wildcardUTF8 = wildCard.toUTF8(); + + if (fnmatch (wildcardUTF8, de->d_name, FNM_CASEFOLD) == 0) + { + filenameFound = CharPointer_UTF8 (de->d_name); + + updateStatInfoForFile (parentDir + filenameFound, isDir, fileSize, modTime, creationTime, isReadOnly); + + if (isHidden != nullptr) + *isHidden = filenameFound.startsWithChar ('.'); + + return true; + } + } + } + + return false; + } + +private: + String parentDir, wildCard; + DIR* dir; + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() {} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* isDir, bool* isHidden, int64* fileSize, + Time* modTime, Time* creationTime, bool* isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp new file mode 100644 index 0000000..d0dd54d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp @@ -0,0 +1,241 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +enum +{ + U_ISOFS_SUPER_MAGIC = 0x9660, // linux/iso_fs.h + U_MSDOS_SUPER_MAGIC = 0x4d44, // linux/msdos_fs.h + U_NFS_SUPER_MAGIC = 0x6969, // linux/nfs_fs.h + U_SMB_SUPER_MAGIC = 0x517B // linux/smb_fs.h +}; + +bool File::isOnCDRomDrive() const +{ + struct statfs buf; + + return statfs (getFullPathName().toUTF8(), &buf) == 0 + && buf.f_type == (short) U_ISOFS_SUPER_MAGIC; +} + +bool File::isOnHardDisk() const +{ + struct statfs buf; + + if (statfs (getFullPathName().toUTF8(), &buf) == 0) + { + switch (buf.f_type) + { + case U_ISOFS_SUPER_MAGIC: // CD-ROM + case U_MSDOS_SUPER_MAGIC: // Probably floppy (but could be mounted FAT filesystem) + case U_NFS_SUPER_MAGIC: // Network NFS + case U_SMB_SUPER_MAGIC: // Network Samba + return false; + + default: break; + } + } + + // Assume so if this fails for some reason + return true; +} + +bool File::isOnRemovableDrive() const +{ + jassertfalse; // xxx not implemented for linux! + return false; +} + +String File::getVersion() const +{ + return String(); // xxx not yet implemented +} + +//============================================================================== +static File resolveXDGFolder (const char* const type, const char* const fallbackFolder) +{ + StringArray confLines; + File ("~/.config/user-dirs.dirs").readLines (confLines); + + for (int i = 0; i < confLines.size(); ++i) + { + const String line (confLines[i].trimStart()); + + if (line.startsWith (type)) + { + // eg. resolve XDG_MUSIC_DIR="$HOME/Music" to /home/user/Music + const File f (line.replace ("$HOME", File ("~").getFullPathName()) + .fromFirstOccurrenceOf ("=", false, false) + .trim().unquoted()); + + if (f.isDirectory()) + return f; + } + } + + return File (fallbackFolder); +} + +const char* const* juce_argv = nullptr; +int juce_argc = 0; + +File File::getSpecialLocation (const SpecialLocationType type) +{ + switch (type) + { + case userHomeDirectory: + { + if (const char* homeDir = getenv ("HOME")) + return File (CharPointer_UTF8 (homeDir)); + + if (struct passwd* const pw = getpwuid (getuid())) + return File (CharPointer_UTF8 (pw->pw_dir)); + + return File(); + } + + case userDocumentsDirectory: return resolveXDGFolder ("XDG_DOCUMENTS_DIR", "~"); + case userMusicDirectory: return resolveXDGFolder ("XDG_MUSIC_DIR", "~"); + case userMoviesDirectory: return resolveXDGFolder ("XDG_VIDEOS_DIR", "~"); + case userPicturesDirectory: return resolveXDGFolder ("XDG_PICTURES_DIR", "~"); + case userDesktopDirectory: return resolveXDGFolder ("XDG_DESKTOP_DIR", "~/Desktop"); + case userApplicationDataDirectory: return resolveXDGFolder ("XDG_CONFIG_HOME", "~"); + case commonDocumentsDirectory: + case commonApplicationDataDirectory: return File ("/var"); + case globalApplicationsDirectory: return File ("/usr"); + + case tempDirectory: + { + File tmp ("/var/tmp"); + + if (! tmp.isDirectory()) + { + tmp = "/tmp"; + + if (! tmp.isDirectory()) + tmp = File::getCurrentWorkingDirectory(); + } + + return tmp; + } + + case invokedExecutableFile: + if (juce_argv != nullptr && juce_argc > 0) + return File (CharPointer_UTF8 (juce_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + case currentApplicationFile: + return juce_getExecutableFile(); + + case hostApplicationPath: + { + const File f ("/proc/self/exe"); + return f.isLink() ? f.getLinkedTarget() : juce_getExecutableFile(); + } + + default: + jassertfalse; // unknown type? + break; + } + + return File(); +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + File trashCan ("~/.Trash"); + + if (! trashCan.isDirectory()) + trashCan = "~/.local/share/Trash/files"; + + if (! trashCan.isDirectory()) + return false; + + return moveFileTo (trashCan.getNonexistentChildFile (getFileNameWithoutExtension(), + getFileExtension())); +} + +//============================================================================== +static bool isFileExecutable (const String& filename) +{ + juce_statStruct info; + + return juce_stat (filename, info) + && S_ISREG (info.st_mode) + && access (filename.toUTF8(), X_OK) == 0; +} + +bool Process::openDocument (const String& fileName, const String& parameters) +{ + String cmdString (fileName.replace (" ", "\\ ",false)); + cmdString << " " << parameters; + + if (URL::isProbablyAWebsiteURL (fileName) + || cmdString.startsWithIgnoreCase ("file:") + || URL::isProbablyAnEmailAddress (fileName) + || File::createFileWithoutCheckingPath (fileName).isDirectory() + || ! isFileExecutable (fileName)) + { + // create a command that tries to launch a bunch of likely browsers + static const char* const browserNames[] = { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla", + "google-chrome", "chromium-browser", "opera", "konqueror" }; + StringArray cmdLines; + + for (int i = 0; i < numElementsInArray (browserNames); ++i) + cmdLines.add (String (browserNames[i]) + " " + cmdString.trim().quoted()); + + cmdString = cmdLines.joinIntoString (" || "); + } + + const char* const argv[4] = { "/bin/sh", "-c", cmdString.toUTF8(), 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + setsid(); + + // Child process + execve (argv[0], (char**) argv, environ); + exit (0); + } + + return cpid >= 0; +} + +void File::revealToUser() const +{ + if (isDirectory()) + startAsProcess(); + else if (getParentDirectory().exists()) + getParentDirectory().startAsProcess(); +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp new file mode 100644 index 0000000..6871aa5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp @@ -0,0 +1,445 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +void MACAddress::findAllAddresses (Array& result) +{ + const int s = socket (AF_INET, SOCK_DGRAM, 0); + if (s != -1) + { + struct ifaddrs* addrs = nullptr; + + if (getifaddrs (&addrs) != -1) + { + for (struct ifaddrs* i = addrs; i != nullptr; i = i->ifa_next) + { + struct ifreq ifr; + strcpy (ifr.ifr_name, i->ifa_name); + ifr.ifr_addr.sa_family = AF_INET; + + if (ioctl (s, SIOCGIFHWADDR, &ifr) == 0) + { + MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data); + + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); + } + } + + freeifaddrs (addrs); + } + + close (s); + } +} + + +bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /* targetEmailAddress */, + const String& /* emailSubject */, + const String& /* bodyText */, + const StringArray& /* filesToAttach */) +{ + jassertfalse; // xxx todo + return false; +} + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : statusCode (0), socketHandle (-1), levelsOfRedirection (0), + address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + statusCode = createConnection (progressCallback, progressCallbackContext); + + if (responseHeaders != nullptr && ! isError()) + { + for (int i = 0; i < headerLines.size(); ++i) + { + const String& headersEntry = headerLines[i]; + const String key (headersEntry.upToFirstOccurrenceOf (": ", false, false)); + const String value (headersEntry.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue ((*responseHeaders) [key]); + responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + } + } + + ~WebInputStream() + { + closeSocket(); + } + + //============================================================================== + bool isError() const { return socketHandle < 0; } + bool isExhausted() override { return finished; } + int64 getPosition() override { return position; } + + int64 getTotalLength() override + { + //xxx to do + return -1; + } + + int read (void* buffer, int bytesToRead) override + { + if (finished || isError()) + return 0; + + fd_set readbits; + FD_ZERO (&readbits); + FD_SET (socketHandle, &readbits); + + struct timeval tv; + tv.tv_sec = jmax (1, timeOutMs / 1000); + tv.tv_usec = 0; + + if (select (socketHandle + 1, &readbits, 0, 0, &tv) <= 0) + return 0; // (timeout) + + const int bytesRead = jmax (0, (int) recv (socketHandle, buffer, bytesToRead, MSG_WAITALL)); + if (bytesRead == 0) + finished = true; + + position += bytesRead; + return bytesRead; + } + + bool setPosition (int64 wantedPos) override + { + if (isError()) + return false; + + if (wantedPos != position) + { + finished = false; + + if (wantedPos < position) + { + closeSocket(); + position = 0; + statusCode = createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + + //============================================================================== + int statusCode; + +private: + int socketHandle, levelsOfRedirection; + StringArray headerLines; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + const int timeOutMs; + + void closeSocket() + { + if (socketHandle >= 0) + close (socketHandle); + + socketHandle = -1; + levelsOfRedirection = 0; + } + + int createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) + { + closeSocket(); + + uint32 timeOutTime = Time::getMillisecondCounter(); + + if (timeOutMs == 0) + timeOutTime += 60000; + else if (timeOutMs < 0) + timeOutTime = 0xffffffff; + else + timeOutTime += timeOutMs; + + String hostName, hostPath; + int hostPort; + if (! decomposeURL (address, hostName, hostPath, hostPort)) + return 0; + + String serverName, proxyName, proxyPath; + int proxyPort = 0; + int port = 0; + + const String proxyURL (getenv ("http_proxy")); + if (proxyURL.startsWithIgnoreCase ("http://")) + { + if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) + return 0; + + serverName = proxyName; + port = proxyPort; + } + else + { + serverName = hostName; + port = hostPort; + } + + struct addrinfo hints; + zerostruct (hints); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + struct addrinfo* result = nullptr; + if (getaddrinfo (serverName.toUTF8(), String (port).toUTF8(), &hints, &result) != 0 || result == 0) + return 0; + + socketHandle = socket (result->ai_family, result->ai_socktype, 0); + + if (socketHandle == -1) + { + freeaddrinfo (result); + return 0; + } + + int receiveBufferSize = 16384; + setsockopt (socketHandle, SOL_SOCKET, SO_RCVBUF, (char*) &receiveBufferSize, sizeof (receiveBufferSize)); + setsockopt (socketHandle, SOL_SOCKET, SO_KEEPALIVE, 0, 0); + + #if JUCE_MAC + setsockopt (socketHandle, SOL_SOCKET, SO_NOSIGPIPE, 0, 0); + #endif + + if (connect (socketHandle, result->ai_addr, result->ai_addrlen) == -1) + { + closeSocket(); + freeaddrinfo (result); + return 0; + } + + freeaddrinfo (result); + + { + const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, proxyName, proxyPort, + hostPath, address, headers, postData, isPost)); + + if (! sendHeader (socketHandle, requestHeader, timeOutTime, + progressCallback, progressCallbackContext)) + { + closeSocket(); + return 0; + } + } + + String responseHeader (readResponse (timeOutTime)); + position = 0; + + if (responseHeader.isNotEmpty()) + { + headerLines = StringArray::fromLines (responseHeader); + + const int status = responseHeader.fromFirstOccurrenceOf (" ", false, false) + .substring (0, 3).getIntValue(); + + //int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); + //bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); + + String location (findHeaderItem (headerLines, "Location:")); + + if (status >= 300 && status < 400 + && location.isNotEmpty() && location != address) + { + if (! location.startsWithIgnoreCase ("http://")) + location = "http://" + location; + + if (++levelsOfRedirection <= 3) + { + address = location; + return createConnection (progressCallback, progressCallbackContext); + } + } + else + { + levelsOfRedirection = 0; + return status; + } + } + + closeSocket(); + return 0; + } + + //============================================================================== + String readResponse (const uint32 timeOutTime) + { + int numConsecutiveLFs = 0; + MemoryOutputStream buffer; + + while (numConsecutiveLFs < 2 + && buffer.getDataSize() < 32768 + && Time::getMillisecondCounter() <= timeOutTime + && ! (finished || isError())) + { + char c = 0; + if (read (&c, 1) != 1) + return String(); + + buffer.writeByte (c); + + if (c == '\n') + ++numConsecutiveLFs; + else if (c != '\r') + numConsecutiveLFs = 0; + } + + const String header (buffer.toString().trimEnd()); + + if (header.startsWithIgnoreCase ("HTTP/")) + return header; + + return String(); + } + + static void writeValueIfNotPresent (MemoryOutputStream& dest, const String& headers, const String& key, const String& value) + { + if (! headers.containsIgnoreCase (key)) + dest << "\r\n" << key << ' ' << value; + } + + static void writeHost (MemoryOutputStream& dest, const bool isPost, + const String& path, const String& host, int /*port*/) + { + dest << (isPost ? "POST " : "GET ") << path << " HTTP/1.0\r\nHost: " << host; + } + + static MemoryBlock createRequestHeader (const String& hostName, const int hostPort, + const String& proxyName, const int proxyPort, + const String& hostPath, const String& originalURL, + const String& userHeaders, const MemoryBlock& postData, + const bool isPost) + { + MemoryOutputStream header; + + if (proxyName.isEmpty()) + writeHost (header, isPost, hostPath, hostName, hostPort); + else + writeHost (header, isPost, originalURL, proxyName, proxyPort); + + writeValueIfNotPresent (header, userHeaders, "User-Agent:", "JUCE/" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) + "." JUCE_STRINGIFY(JUCE_MINOR_VERSION) + "." JUCE_STRINGIFY(JUCE_BUILDNUMBER)); + writeValueIfNotPresent (header, userHeaders, "Connection:", "close"); + + if (isPost) + writeValueIfNotPresent (header, userHeaders, "Content-Length:", String ((int) postData.getSize())); + + header << "\r\n" << userHeaders + << "\r\n" << postData; + + return header.getMemoryBlock(); + } + + static bool sendHeader (int socketHandle, const MemoryBlock& requestHeader, const uint32 timeOutTime, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) + { + size_t totalHeaderSent = 0; + + while (totalHeaderSent < requestHeader.getSize()) + { + if (Time::getMillisecondCounter() > timeOutTime) + return false; + + const int numToSend = jmin (1024, (int) (requestHeader.getSize() - totalHeaderSent)); + + if (send (socketHandle, static_cast (requestHeader.getData()) + totalHeaderSent, numToSend, 0) != numToSend) + return false; + + totalHeaderSent += numToSend; + + if (progressCallback != nullptr && ! progressCallback (progressCallbackContext, totalHeaderSent, requestHeader.getSize())) + return false; + } + + return true; + } + + static bool decomposeURL (const String& url, String& host, String& path, int& port) + { + if (! url.startsWithIgnoreCase ("http://")) + return false; + + const int nextSlash = url.indexOfChar (7, '/'); + int nextColon = url.indexOfChar (7, ':'); + if (nextColon > nextSlash && nextSlash > 0) + nextColon = -1; + + if (nextColon >= 0) + { + host = url.substring (7, nextColon); + + if (nextSlash >= 0) + port = url.substring (nextColon + 1, nextSlash).getIntValue(); + else + port = url.substring (nextColon + 1).getIntValue(); + } + else + { + port = 80; + + if (nextSlash >= 0) + host = url.substring (7, nextSlash); + else + host = url.substring (7); + } + + if (nextSlash >= 0) + path = url.substring (nextSlash); + else + path = "/"; + + return true; + } + + static String findHeaderItem (const StringArray& lines, const String& itemName) + { + for (int i = 0; i < lines.size(); ++i) + if (lines[i].startsWithIgnoreCase (itemName)) + return lines[i].substring (itemName.length()).trim(); + + return String(); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp new file mode 100644 index 0000000..939edb4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -0,0 +1,191 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +void Logger::outputDebugString (const String& text) +{ + std::cerr << text << std::endl; +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + return Linux; +} + +String SystemStats::getOperatingSystemName() +{ + return "Linux"; +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_64BIT + return true; + #else + //xxx not sure how to find this out?.. + return false; + #endif +} + +//============================================================================== +namespace LinuxStatsHelpers +{ + String getCpuInfo (const char* const key) + { + StringArray lines; + File ("/proc/cpuinfo").readLines (lines); + + for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order) + if (lines[i].upToFirstOccurrenceOf (":", false, false).trim().equalsIgnoreCase (key)) + return lines[i].fromFirstOccurrenceOf (":", false, false).trim(); + + return String(); + } +} + +String SystemStats::getDeviceDescription() +{ + return LinuxStatsHelpers::getCpuInfo ("Hardware"); +} + +String SystemStats::getCpuVendor() +{ + String v (LinuxStatsHelpers::getCpuInfo ("vendor_id")); + + if (v.isEmpty()) + v = LinuxStatsHelpers::getCpuInfo ("model name"); + + return v; +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + return roundToInt (LinuxStatsHelpers::getCpuInfo ("cpu MHz").getFloatValue()); +} + +int SystemStats::getMemorySizeInMegabytes() +{ + struct sysinfo sysi; + + if (sysinfo (&sysi) == 0) + return sysi.totalram * sysi.mem_unit / (1024 * 1024); + + return 0; +} + +int SystemStats::getPageSize() +{ + return sysconf (_SC_PAGESIZE); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + if (const char* user = getenv ("USER")) + return CharPointer_UTF8 (user); + + if (struct passwd* const pw = getpwuid (getuid())) + return CharPointer_UTF8 (pw->pw_name); + + return String(); +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return name; + + return String(); +} + +static String getLocaleValue (nl_item key) +{ + const char* oldLocale = ::setlocale (LC_ALL, ""); + String result (String::fromUTF8 (nl_langinfo (key))); + ::setlocale (LC_ALL, oldLocale); + return result; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); } +String SystemStats::getUserRegion() { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); } +String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + const String flags (LinuxStatsHelpers::getCpuInfo ("flags")); + hasMMX = flags.contains ("mmx"); + hasSSE = flags.contains ("sse"); + hasSSE2 = flags.contains ("sse2"); + hasSSE3 = flags.contains ("sse3"); + has3DNow = flags.contains ("3dnow"); + + numCpus = LinuxStatsHelpers::getCpuInfo ("processor").getIntValue() + 1; +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return t.tv_sec * 1000 + t.tv_nsec / 1000000; +} + +int64 Time::getHighResolutionTicks() noexcept +{ + timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + + return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); +} + +int64 Time::getHighResolutionTicksPerSecond() noexcept +{ + return 1000000; // (microseconds) +} + +double Time::getMillisecondCounterHiRes() noexcept +{ + return getHighResolutionTicks() * 0.001; +} + +bool Time::setSystemTimeToThisTime() const +{ + timeval t; + t.tv_sec = millisSinceEpoch / 1000; + t.tv_usec = (millisSinceEpoch - t.tv_sec * 1000) * 1000; + + return settimeofday (&t, 0) == 0; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_Threads.cpp new file mode 100644 index 0000000..659fbd9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_Threads.cpp @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +JUCE_API void JUCE_CALLTYPE Process::setPriority (const ProcessPriority prior) +{ + const int policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR; + const int minp = sched_get_priority_min (policy); + const int maxp = sched_get_priority_max (policy); + + struct sched_param param; + + switch (prior) + { + case LowPriority: + case NormalPriority: param.sched_priority = 0; break; + case HighPriority: param.sched_priority = minp + (maxp - minp) / 4; break; + case RealtimePriority: param.sched_priority = minp + (3 * (maxp - minp) / 4); break; + default: jassertfalse; break; + } + + pthread_setschedparam (pthread_self(), policy, ¶m); +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + #if JUCE_BSD + return false; + #else + static char testResult = 0; + + if (testResult == 0) + { + testResult = (char) ptrace (PT_TRACE_ME, 0, 0, 0); + + if (testResult >= 0) + { + ptrace (PT_DETACH, 0, (caddr_t) 1, 0); + testResult = 1; + } + } + + return testResult < 0; + #endif +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} + +static bool swapUserAndEffectiveUser() +{ + int result1 = setreuid (geteuid(), getuid()); + int result2 = setregid (getegid(), getgid()); + return result1 == 0 && result2 == 0; +} + +JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() { if (geteuid() != 0 && getuid() == 0) swapUserAndEffectiveUser(); } +JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() { if (geteuid() == 0 && getuid() != 0) swapUserAndEffectiveUser(); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm new file mode 100644 index 0000000..0a00e92 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm @@ -0,0 +1,498 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +//============================================================================== +bool File::copyInternal (const File& dest) const +{ + JUCE_AUTORELEASEPOOL + { + NSFileManager* fm = [NSFileManager defaultManager]; + + return [fm fileExistsAtPath: juceStringToNS (fullPath)] + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + && [fm copyItemAtPath: juceStringToNS (fullPath) + toPath: juceStringToNS (dest.getFullPathName()) + error: nil]; + #else + && [fm copyPath: juceStringToNS (fullPath) + toPath: juceStringToNS (dest.getFullPathName()) + handler: nil]; + #endif + } +} + +void File::findFileSystemRoots (Array& destArray) +{ + destArray.add (File ("/")); +} + + +//============================================================================== +namespace FileHelpers +{ + static bool isFileOnDriveType (const File& f, const char* const* types) + { + struct statfs buf; + + if (juce_doStatFS (f, buf)) + { + const String type (buf.f_fstypename); + + while (*types != 0) + if (type.equalsIgnoreCase (*types++)) + return true; + } + + return false; + } + + static bool isHiddenFile (const String& path) + { + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + JUCE_AUTORELEASEPOOL + { + NSNumber* hidden = nil; + NSError* err = nil; + + return [[NSURL fileURLWithPath: juceStringToNS (path)] + getResourceValue: &hidden forKey: NSURLIsHiddenKey error: &err] + && [hidden boolValue]; + } + #elif JUCE_IOS + return File (path).getFileName().startsWithChar ('.'); + #else + FSRef ref; + LSItemInfoRecord info; + + return FSPathMakeRefWithOptions ((const UInt8*) path.toRawUTF8(), kFSPathMakeRefDoNotFollowLeafSymlink, &ref, 0) == noErr + && LSCopyItemInfoForRef (&ref, kLSRequestBasicFlagsOnly, &info) == noErr + && (info.flags & kLSItemInfoIsInvisible) != 0; + #endif + } + + #if JUCE_IOS + static String getIOSSystemLocation (NSSearchPathDirectory type) + { + return nsStringToJuce ([NSSearchPathForDirectoriesInDomains (type, NSUserDomainMask, YES) + objectAtIndex: 0]); + } + #endif + + static bool launchExecutable (const String& pathAndArguments) + { + const char* const argv[4] = { "/bin/sh", "-c", pathAndArguments.toUTF8(), 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + // Child process + if (execve (argv[0], (char**) argv, 0) < 0) + exit (0); + } + else + { + if (cpid < 0) + return false; + } + + return true; + } +} + +bool File::isOnCDRomDrive() const +{ + static const char* const cdTypes[] = { "cd9660", "cdfs", "cddafs", "udf", nullptr }; + + return FileHelpers::isFileOnDriveType (*this, cdTypes); +} + +bool File::isOnHardDisk() const +{ + static const char* const nonHDTypes[] = { "nfs", "smbfs", "ramfs", nullptr }; + + return ! (isOnCDRomDrive() || FileHelpers::isFileOnDriveType (*this, nonHDTypes)); +} + +bool File::isOnRemovableDrive() const +{ + #if JUCE_IOS + return false; // xxx is this possible? + #else + JUCE_AUTORELEASEPOOL + { + BOOL removable = false; + + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; + + return removable; + } + #endif +} + +bool File::isHidden() const +{ + return FileHelpers::isHiddenFile (getFullPathName()); +} + +//============================================================================== +const char* const* juce_argv = nullptr; +int juce_argc = 0; + +File File::getSpecialLocation (const SpecialLocationType type) +{ + JUCE_AUTORELEASEPOOL + { + String resultPath; + + switch (type) + { + case userHomeDirectory: resultPath = nsStringToJuce (NSHomeDirectory()); break; + + #if JUCE_IOS + case userDocumentsDirectory: resultPath = FileHelpers::getIOSSystemLocation (NSDocumentDirectory); break; + case userDesktopDirectory: resultPath = FileHelpers::getIOSSystemLocation (NSDesktopDirectory); break; + + case tempDirectory: + { + File tmp (FileHelpers::getIOSSystemLocation (NSCachesDirectory)); + tmp = tmp.getChildFile (juce_getExecutableFile().getFileNameWithoutExtension()); + tmp.createDirectory(); + return tmp.getFullPathName(); + } + + #else + case userDocumentsDirectory: resultPath = "~/Documents"; break; + case userDesktopDirectory: resultPath = "~/Desktop"; break; + + case tempDirectory: + { + File tmp ("~/Library/Caches/" + juce_getExecutableFile().getFileNameWithoutExtension()); + tmp.createDirectory(); + return File (tmp.getFullPathName()); + } + #endif + case userMusicDirectory: resultPath = "~/Music"; break; + case userMoviesDirectory: resultPath = "~/Movies"; break; + case userPicturesDirectory: resultPath = "~/Pictures"; break; + case userApplicationDataDirectory: resultPath = "~/Library"; break; + case commonApplicationDataDirectory: resultPath = "/Library"; break; + case commonDocumentsDirectory: resultPath = "/Users/Shared"; break; + case globalApplicationsDirectory: resultPath = "/Applications"; break; + + case invokedExecutableFile: + if (juce_argv != nullptr && juce_argc > 0) + return File (CharPointer_UTF8 (juce_argv[0])); + // deliberate fall-through... + + case currentExecutableFile: + return juce_getExecutableFile(); + + case currentApplicationFile: + { + const File exe (juce_getExecutableFile()); + const File parent (exe.getParentDirectory()); + + #if JUCE_IOS + return parent; + #else + return parent.getFullPathName().endsWithIgnoreCase ("Contents/MacOS") + ? parent.getParentDirectory().getParentDirectory() + : exe; + #endif + } + + case hostApplicationPath: + { + unsigned int size = 8192; + HeapBlock buffer; + buffer.calloc (size + 8); + + _NSGetExecutablePath (buffer.getData(), &size); + return File (String::fromUTF8 (buffer, (int) size)); + } + + default: + jassertfalse; // unknown type? + break; + } + + if (resultPath.isNotEmpty()) + return File (resultPath.convertToPrecomposedUnicode()); + } + + return File(); +} + +//============================================================================== +String File::getVersion() const +{ + JUCE_AUTORELEASEPOOL + { + if (NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]) + if (NSDictionary* info = [bundle infoDictionary]) + if (NSString* name = [info valueForKey: nsStringLiteral ("CFBundleShortVersionString")]) + return nsStringToJuce (name); + } + + return String(); +} + +//============================================================================== +static NSString* getFileLink (const String& path) +{ + #if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + return [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: juceStringToNS (path) error: nil]; + #else + // (the cast here avoids a deprecation warning) + return [((id) [NSFileManager defaultManager]) pathContentOfSymbolicLinkAtPath: juceStringToNS (path)]; + #endif +} + +bool File::isLink() const +{ + return getFileLink (fullPath) != nil; +} + +File File::getLinkedTarget() const +{ + if (NSString* dest = getFileLink (fullPath)) + return getSiblingFile (nsStringToJuce (dest)); + + return *this; +} + +//============================================================================== +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + #if JUCE_IOS + return deleteFile(); //xxx is there a trashcan on the iOS? + #else + JUCE_AUTORELEASEPOOL + { + NSString* p = juceStringToNS (getFullPathName()); + + return [[NSWorkspace sharedWorkspace] + performFileOperation: NSWorkspaceRecycleOperation + source: [p stringByDeletingLastPathComponent] + destination: nsEmptyString() + files: [NSArray arrayWithObject: [p lastPathComponent]] + tag: nil ]; + } + #endif +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard_) + : parentDir (File::addTrailingSeparator (directory.getFullPathName())), + wildCard (wildCard_), + enumerator (nil) + { + JUCE_AUTORELEASEPOOL + { + enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (directory.getFullPathName())] retain]; + } + } + + ~Pimpl() + { + [enumerator release]; + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + JUCE_AUTORELEASEPOOL + { + const char* wildcardUTF8 = nullptr; + + for (;;) + { + NSString* file; + if (enumerator == nil || (file = [enumerator nextObject]) == nil) + return false; + + [enumerator skipDescendents]; + filenameFound = nsStringToJuce (file).convertToPrecomposedUnicode(); + + if (wildcardUTF8 == nullptr) + wildcardUTF8 = wildCard.toUTF8(); + + if (fnmatch (wildcardUTF8, filenameFound.toUTF8(), FNM_CASEFOLD) != 0) + continue; + + const String fullPath (parentDir + filenameFound); + updateStatInfoForFile (fullPath, isDir, fileSize, modTime, creationTime, isReadOnly); + + if (isHidden != nullptr) + *isHidden = FileHelpers::isHiddenFile (fullPath); + + return true; + } + } + } + +private: + String parentDir, wildCard; + NSDirectoryEnumerator* enumerator; + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildcard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildcard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& parameters) +{ + JUCE_AUTORELEASEPOOL + { + NSURL* filenameAsURL = [NSURL URLWithString: juceStringToNS (fileName)]; + + #if JUCE_IOS + (void) parameters; + return [[UIApplication sharedApplication] openURL: filenameAsURL]; + #else + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + + if (parameters.isEmpty()) + return [workspace openFile: juceStringToNS (fileName)] + || [workspace openURL: filenameAsURL]; + + const File file (fileName); + + if (file.isBundle()) + { + StringArray params; + params.addTokens (parameters, true); + + NSMutableArray* paramArray = [[[NSMutableArray alloc] init] autorelease]; + for (int i = 0; i < params.size(); ++i) + [paramArray addObject: juceStringToNS (params[i])]; + + NSMutableDictionary* dict = [[[NSMutableDictionary alloc] init] autorelease]; + [dict setObject: paramArray + forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")]; + + return [workspace launchApplicationAtURL: filenameAsURL + options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance + configuration: dict + error: nil]; + } + + if (file.exists()) + return FileHelpers::launchExecutable ("\"" + fileName + "\" " + parameters); + + return false; + #endif + } +} + +void File::revealToUser() const +{ + #if ! JUCE_IOS + if (exists()) + [[NSWorkspace sharedWorkspace] selectFile: juceStringToNS (getFullPathName()) inFileViewerRootedAtPath: nsEmptyString()]; + else if (getParentDirectory().exists()) + getParentDirectory().revealToUser(); + #endif +} + +//============================================================================== +OSType File::getMacOSType() const +{ + JUCE_AUTORELEASEPOOL + { + #if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + NSDictionary* fileDict = [[NSFileManager defaultManager] attributesOfItemAtPath: juceStringToNS (getFullPathName()) error: nil]; + #else + // (the cast here avoids a deprecation warning) + NSDictionary* fileDict = [((id) [NSFileManager defaultManager]) fileAttributesAtPath: juceStringToNS (getFullPathName()) traverseLink: NO]; + #endif + + return [fileDict fileHFSTypeCode]; + } +} + +bool File::isBundle() const +{ + #if JUCE_IOS + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. + #else + JUCE_AUTORELEASEPOOL + { + return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (getFullPathName())]; + } + #endif +} + +#if JUCE_MAC +void File::addToDock() const +{ + // check that it's not already there... + if (! juce_getOutputFromCommand ("defaults read com.apple.dock persistent-apps").containsIgnoreCase (getFullPathName())) + { + juce_runSystemCommand ("defaults write com.apple.dock persistent-apps -array-add \"tile-datafile-data_CFURLString" + + getFullPathName() + "_CFURLStringType0\""); + + juce_runSystemCommand ("osascript -e \"tell application \\\"Dock\\\" to quit\""); + } +} +#endif diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm new file mode 100644 index 0000000..130759e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm @@ -0,0 +1,445 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +void MACAddress::findAllAddresses (Array& result) +{ + ifaddrs* addrs = nullptr; + + if (getifaddrs (&addrs) == 0) + { + for (const ifaddrs* cursor = addrs; cursor != nullptr; cursor = cursor->ifa_next) + { + sockaddr_storage* sto = (sockaddr_storage*) cursor->ifa_addr; + if (sto->ss_family == AF_LINK) + { + const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; + + #ifndef IFT_ETHER + enum { IFT_ETHER = 6 }; + #endif + + if (sadd->sdl_type == IFT_ETHER) + { + MACAddress ma (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); + + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); + } + } + } + + freeifaddrs (addrs); + } +} + +//============================================================================== +bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + #if JUCE_IOS + (void) targetEmailAddress; + (void) emailSubject; + (void) bodyText; + (void) filesToAttach; + + //xxx probably need to use MFMailComposeViewController + jassertfalse; + return false; + #else + JUCE_AUTORELEASEPOOL + { + String script; + script << "tell application \"Mail\"\r\n" + "set newMessage to make new outgoing message with properties {subject:\"" + << emailSubject.replace ("\"", "\\\"") + << "\", content:\"" + << bodyText.replace ("\"", "\\\"") + << "\" & return & return}\r\n" + "tell newMessage\r\n" + "set visible to true\r\n" + "set sender to \"sdfsdfsdfewf\"\r\n" + "make new to recipient at end of to recipients with properties {address:\"" + << targetEmailAddress + << "\"}\r\n"; + + for (int i = 0; i < filesToAttach.size(); ++i) + { + script << "tell content\r\n" + "make new attachment with properties {file name:\"" + << filesToAttach[i].replace ("\"", "\\\"") + << "\"} at after the last paragraph\r\n" + "end tell\r\n"; + } + + script << "end tell\r\n" + "end tell\r\n"; + + NSAppleScript* s = [[NSAppleScript alloc] initWithSource: juceStringToNS (script)]; + NSDictionary* error = nil; + const bool ok = [s executeAndReturnError: &error] != nil; + [s release]; + + return ok; + } + #endif +} + +//============================================================================== +class URLConnectionState : public Thread +{ +public: + URLConnectionState (NSURLRequest* req) + : Thread ("http connection"), + contentLength (-1), + delegate (nil), + request ([req retain]), + connection (nil), + data ([[NSMutableData data] retain]), + headers (nil), + statusCode (0), + initialised (false), + hasFailed (false), + hasFinished (false) + { + static DelegateClass cls; + delegate = [cls.createInstance() init]; + DelegateClass::setState (delegate, this); + } + + ~URLConnectionState() + { + stop(); + [connection release]; + [data release]; + [request release]; + [headers release]; + [delegate release]; + } + + bool start (URL::OpenStreamProgressCallback* callback, void* context) + { + startThread(); + + while (isThreadRunning() && ! initialised) + { + if (callback != nullptr) + callback (context, -1, (int) [[request HTTPBody] length]); + + Thread::sleep (1); + } + + return connection != nil && ! hasFailed; + } + + void stop() + { + [connection cancel]; + stopThread (10000); + } + + int read (char* dest, int numBytes) + { + int numDone = 0; + + while (numBytes > 0) + { + const int available = jmin (numBytes, (int) [data length]); + + if (available > 0) + { + const ScopedLock sl (dataLock); + [data getBytes: dest length: (NSUInteger) available]; + [data replaceBytesInRange: NSMakeRange (0, (NSUInteger) available) withBytes: nil length: 0]; + + numDone += available; + numBytes -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + Thread::sleep (1); + } + } + + return numDone; + } + + void didReceiveResponse (NSURLResponse* response) + { + { + const ScopedLock sl (dataLock); + [data setLength: 0]; + } + + initialised = true; + contentLength = [response expectedContentLength]; + + [headers release]; + headers = nil; + + if ([response isKindOfClass: [NSHTTPURLResponse class]]) + { + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; + headers = [[httpResponse allHeaderFields] retain]; + statusCode = (int) [httpResponse statusCode]; + } + } + + void didFailWithError (NSError* error) + { + DBG (nsStringToJuce ([error description])); (void) error; + hasFailed = true; + initialised = true; + signalThreadShouldExit(); + } + + void didReceiveData (NSData* newData) + { + const ScopedLock sl (dataLock); + [data appendData: newData]; + initialised = true; + } + + void didSendBodyData (NSInteger /*totalBytesWritten*/, NSInteger /*totalBytesExpected*/) + { + } + + void finishedLoading() + { + hasFinished = true; + initialised = true; + signalThreadShouldExit(); + } + + void run() override + { + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: delegate]; + while (! threadShouldExit()) + { + JUCE_AUTORELEASEPOOL + { + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + } + } + + int64 contentLength; + CriticalSection dataLock; + NSObject* delegate; + NSURLRequest* request; + NSURLConnection* connection; + NSMutableData* data; + NSDictionary* headers; + int statusCode; + bool initialised, hasFailed, hasFinished; + +private: + //============================================================================== + struct DelegateClass : public ObjCClass + { + DelegateClass() : ObjCClass ("JUCEAppDelegate_") + { + addIvar ("state"); + + addMethod (@selector (connection:didReceiveResponse:), didReceiveResponse, "v@:@@"); + addMethod (@selector (connection:didFailWithError:), didFailWithError, "v@:@@"); + addMethod (@selector (connection:didReceiveData:), didReceiveData, "v@:@@"); + addMethod (@selector (connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:), + connectionDidSendBodyData, "v@:@iii"); + addMethod (@selector (connectionDidFinishLoading:), connectionDidFinishLoading, "v@:@"); + addMethod (@selector (connection:willSendRequest:redirectResponse:), willSendRequest, "@@:@@"); + + registerClass(); + } + + static void setState (id self, URLConnectionState* state) { object_setInstanceVariable (self, "state", state); } + static URLConnectionState* getState (id self) { return getIvar (self, "state"); } + + private: + static void didReceiveResponse (id self, SEL, NSURLConnection*, NSURLResponse* response) + { + getState (self)->didReceiveResponse (response); + } + + static void didFailWithError (id self, SEL, NSURLConnection*, NSError* error) + { + getState (self)->didFailWithError (error); + } + + static void didReceiveData (id self, SEL, NSURLConnection*, NSData* newData) + { + getState (self)->didReceiveData (newData); + } + + static NSURLRequest* willSendRequest (id, SEL, NSURLConnection*, NSURLRequest* request, NSURLResponse*) + { + return request; + } + + static void connectionDidSendBodyData (id self, SEL, NSURLConnection*, NSInteger, NSInteger totalBytesWritten, NSInteger totalBytesExpected) + { + getState (self)->didSendBodyData (totalBytesWritten, totalBytesExpected); + } + + static void connectionDidFinishLoading (id self, SEL, NSURLConnection*) + { + getState (self)->finishedLoading(); + } + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (URLConnectionState) +}; + + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : statusCode (0), address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + JUCE_AUTORELEASEPOOL + { + createConnection (progressCallback, progressCallbackContext); + + if (responseHeaders != nullptr && connection != nullptr && connection->headers != nil) + { + statusCode = connection->statusCode; + + NSEnumerator* enumerator = [connection->headers keyEnumerator]; + + while (NSString* key = [enumerator nextObject]) + responseHeaders->set (nsStringToJuce (key), + nsStringToJuce ((NSString*) [connection->headers objectForKey: key])); + } + } + } + + //============================================================================== + bool isError() const { return connection == nullptr; } + int64 getTotalLength() override { return connection == nullptr ? -1 : connection->contentLength; } + bool isExhausted() override { return finished; } + int64 getPosition() override { return position; } + + int read (void* buffer, int bytesToRead) override + { + jassert (buffer != nullptr && bytesToRead >= 0); + + if (finished || isError()) + return 0; + + JUCE_AUTORELEASEPOOL + { + const int bytesRead = connection->read (static_cast (buffer), bytesToRead); + position += bytesRead; + + if (bytesRead == 0) + finished = true; + + return bytesRead; + } + } + + bool setPosition (int64 wantedPos) override + { + if (wantedPos != position) + { + finished = false; + + if (wantedPos < position) + { + connection = nullptr; + position = 0; + createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + + int statusCode; + +private: + ScopedPointer connection; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + const int timeOutMs; + + void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) + { + jassert (connection == nullptr); + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (address)] + cachePolicy: NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; + + if (req != nil) + { + [req setHTTPMethod: nsStringLiteral (isPost ? "POST" : "GET")]; + //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + + StringArray headerLines; + headerLines.addLines (headers); + headerLines.removeEmptyStrings (true); + + for (int i = 0; i < headerLines.size(); ++i) + { + const String key (headerLines[i].upToFirstOccurrenceOf (":", false, false).trim()); + const String value (headerLines[i].fromFirstOccurrenceOf (":", false, false).trim()); + + if (key.isNotEmpty() && value.isNotEmpty()) + [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)]; + } + + if (isPost && postData.getSize() > 0) + [req setHTTPBody: [NSData dataWithBytes: postData.getData() + length: postData.getSize()]]; + + connection = new URLConnectionState (req); + + if (! connection->start (progressCallback, progressCallbackContext)) + connection = nullptr; + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm new file mode 100644 index 0000000..7a38b3d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm @@ -0,0 +1,100 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +String String::fromCFString (CFStringRef cfString) +{ + if (cfString == 0) + return String(); + + CFRange range = { 0, CFStringGetLength (cfString) }; + CFIndex bytesNeeded = 0; + CFStringGetBytes (cfString, range, kCFStringEncodingUTF8, 0, false, nullptr, 0, &bytesNeeded); + + HeapBlock utf8 ((size_t) bytesNeeded + 1); + CFStringGetBytes (cfString, range, kCFStringEncodingUTF8, 0, false, utf8, bytesNeeded + 1, nullptr); + + return String (CharPointer_UTF8 ((const CharPointer_UTF8::CharType*) utf8.getData()), + CharPointer_UTF8 ((const CharPointer_UTF8::CharType*) utf8.getData() + bytesNeeded)); +} + +CFStringRef String::toCFString() const +{ + const char* const utf8 = toRawUTF8(); + return CFStringCreateWithBytes (kCFAllocatorDefault, (const UInt8*) utf8, + (CFIndex) strlen (utf8), kCFStringEncodingUTF8, false); +} + +String String::convertToPrecomposedUnicode() const +{ + #if JUCE_IOS + JUCE_AUTORELEASEPOOL + { + return nsStringToJuce ([juceStringToNS (*this) precomposedStringWithCanonicalMapping]); + } + #else + UnicodeMapping map; + + map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeNoSubset, + kTextEncodingDefaultFormat); + + map.otherEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeCanonicalCompVariant, + kTextEncodingDefaultFormat); + + map.mappingVersion = kUnicodeUseLatestMapping; + + UnicodeToTextInfo conversionInfo = 0; + String result; + + if (CreateUnicodeToTextInfo (&map, &conversionInfo) == noErr) + { + const size_t bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (getCharPointer()); + + HeapBlock tempOut; + tempOut.calloc (bytesNeeded + 4); + + ByteCount bytesRead = 0; + ByteCount outputBufferSize = 0; + + if (ConvertFromUnicodeToText (conversionInfo, + bytesNeeded, (ConstUniCharArrayPtr) toUTF16().getAddress(), + kUnicodeDefaultDirectionMask, + 0, 0, 0, 0, + bytesNeeded, &bytesRead, + &outputBufferSize, tempOut) == noErr) + { + result = String (CharPointer_UTF16 ((CharPointer_UTF16::CharType*) tempOut.getData())); + } + + DisposeUnicodeToTextInfo (&conversionInfo); + } + + return result; + #endif +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm new file mode 100644 index 0000000..2d49ae6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm @@ -0,0 +1,306 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +ScopedAutoReleasePool::ScopedAutoReleasePool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoReleasePool::~ScopedAutoReleasePool() +{ + [((NSAutoreleasePool*) pool) release]; +} + +//============================================================================== +void Logger::outputDebugString (const String& text) +{ + // Would prefer to use std::cerr here, but avoiding it for + // the moment, due to clang JIT linkage problems. + fputs (text.toRawUTF8(), stderr); + fputs ("\n", stderr); + fflush (stderr); +} + +//============================================================================== +namespace SystemStatsHelpers +{ + #if JUCE_INTEL && ! JUCE_NO_INLINE_ASM + static void doCPUID (uint32& a, uint32& b, uint32& c, uint32& d, uint32 type) + { + uint32 la = a, lb = b, lc = c, ld = d; + + asm ("mov %%ebx, %%esi \n\t" + "cpuid \n\t" + "xchg %%esi, %%ebx" + : "=a" (la), "=S" (lb), "=c" (lc), "=d" (ld) : "a" (type) + #if JUCE_64BIT + , "b" (lb), "c" (lc), "d" (ld) + #endif + ); + + a = la; b = lb; c = lc; d = ld; + } + #endif +} + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + #if JUCE_INTEL && ! JUCE_NO_INLINE_ASM + uint32 a = 0, b = 0, d = 0, c = 0; + SystemStatsHelpers::doCPUID (a, b, c, d, 1); + + hasMMX = (d & (1u << 23)) != 0; + hasSSE = (d & (1u << 25)) != 0; + hasSSE2 = (d & (1u << 26)) != 0; + has3DNow = (b & (1u << 31)) != 0; + hasSSE3 = (c & (1u << 0)) != 0; + #endif + + #if JUCE_IOS || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + numCpus = (int) [[NSProcessInfo processInfo] activeProcessorCount]; + #else + numCpus = (int) MPProcessors(); + #endif +} + +#if JUCE_MAC +struct RLimitInitialiser +{ + RLimitInitialiser() + { + rlimit lim; + getrlimit (RLIMIT_NOFILE, &lim); + lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; + setrlimit (RLIMIT_NOFILE, &lim); + } +}; + +static RLimitInitialiser rLimitInitialiser; +#endif + +//============================================================================== +#if ! JUCE_IOS +static String getOSXVersion() +{ + JUCE_AUTORELEASEPOOL + { + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: + nsStringLiteral ("/System/Library/CoreServices/SystemVersion.plist")]; + + return nsStringToJuce ([dict objectForKey: nsStringLiteral ("ProductVersion")]); + } +} +#endif + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + #if JUCE_IOS + return iOS; + #else + StringArray parts; + parts.addTokens (getOSXVersion(), ".", StringRef()); + + jassert (parts[0].getIntValue() == 10); + const int major = parts[1].getIntValue(); + jassert (major > 2); + + return (OperatingSystemType) (major + MacOSX_10_4 - 4); + #endif +} + +String SystemStats::getOperatingSystemName() +{ + #if JUCE_IOS + return "iOS " + nsStringToJuce ([[UIDevice currentDevice] systemVersion]); + #else + return "Mac OSX " + getOSXVersion(); + #endif +} + +String SystemStats::getDeviceDescription() +{ + #if JUCE_IOS + return nsStringToJuce ([[UIDevice currentDevice] model]); + #else + return String(); + #endif +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_IOS + return false; + #elif JUCE_64BIT + return true; + #else + return getOperatingSystemType() >= MacOSX_10_6; + #endif +} + +int SystemStats::getMemorySizeInMegabytes() +{ + uint64 mem = 0; + size_t memSize = sizeof (mem); + int mib[] = { CTL_HW, HW_MEMSIZE }; + sysctl (mib, 2, &mem, &memSize, 0, 0); + return (int) (mem / (1024 * 1024)); +} + +String SystemStats::getCpuVendor() +{ + #if JUCE_INTEL && ! JUCE_NO_INLINE_ASM + uint32 dummy = 0; + uint32 vendor[4] = { 0 }; + + SystemStatsHelpers::doCPUID (dummy, vendor[0], vendor[2], vendor[1], 0); + + return String (reinterpret_cast (vendor), 12); + #else + return String(); + #endif +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + uint64 speedHz = 0; + size_t speedSize = sizeof (speedHz); + int mib[] = { CTL_HW, HW_CPU_FREQ }; + sysctl (mib, 2, &speedHz, &speedSize, 0, 0); + + #if JUCE_BIG_ENDIAN + if (speedSize == 4) + speedHz >>= 32; + #endif + + return (int) (speedHz / 1000000); +} + +//============================================================================== +String SystemStats::getLogonName() +{ + return nsStringToJuce (NSUserName()); +} + +String SystemStats::getFullUserName() +{ + return nsStringToJuce (NSFullUserName()); +} + +String SystemStats::getComputerName() +{ + char name [256] = { 0 }; + if (gethostname (name, sizeof (name) - 1) == 0) + return String (name).upToLastOccurrenceOf (".local", false, true); + + return String(); +} + +static String getLocaleValue (CFStringRef key) +{ + CFLocaleRef cfLocale = CFLocaleCopyCurrent(); + const String result (String::fromCFString ((CFStringRef) CFLocaleGetValue (cfLocale, key))); + CFRelease (cfLocale); + return result; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (kCFLocaleLanguageCode); } +String SystemStats::getUserRegion() { return getLocaleValue (kCFLocaleCountryCode); } + +String SystemStats::getDisplayLanguage() +{ + CFArrayRef cfPrefLangs = CFLocaleCopyPreferredLanguages(); + const String result (String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (cfPrefLangs, 0))); + CFRelease (cfPrefLangs); + return result; +} + +//============================================================================== +/* NB: these are kept outside the HiResCounterInfo struct and initialised to 1 to avoid + division-by-zero errors if some other static constructor calls us before this file's + static constructors have had a chance to fill them in correctly.. +*/ +static uint64 hiResCounterNumerator = 0, hiResCounterDenominator = 1; + +class HiResCounterInfo +{ +public: + HiResCounterInfo() + { + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + + if (timebase.numer % 1000000 == 0) + { + hiResCounterNumerator = timebase.numer / 1000000; + hiResCounterDenominator = timebase.denom; + } + else + { + hiResCounterNumerator = timebase.numer; + hiResCounterDenominator = timebase.denom * (uint64) 1000000; + } + + highResTimerFrequency = (timebase.denom * (uint64) 1000000000) / timebase.numer; + highResTimerToMillisecRatio = hiResCounterNumerator / (double) hiResCounterDenominator; + } + + uint32 millisecondsSinceStartup() const noexcept + { + return (uint32) ((mach_absolute_time() * hiResCounterNumerator) / hiResCounterDenominator); + } + + double getMillisecondCounterHiRes() const noexcept + { + return mach_absolute_time() * highResTimerToMillisecRatio; + } + + int64 highResTimerFrequency; + +private: + double highResTimerToMillisecRatio; +}; + +static HiResCounterInfo hiResCounterInfo; + +uint32 juce_millisecondsSinceStartup() noexcept { return hiResCounterInfo.millisecondsSinceStartup(); } +double Time::getMillisecondCounterHiRes() noexcept { return hiResCounterInfo.getMillisecondCounterHiRes(); } +int64 Time::getHighResolutionTicksPerSecond() noexcept { return hiResCounterInfo.highResTimerFrequency; } +int64 Time::getHighResolutionTicks() noexcept { return (int64) mach_absolute_time(); } + +bool Time::setSystemTimeToThisTime() const +{ + jassertfalse; + return false; +} + +//============================================================================== +int SystemStats::getPageSize() +{ + return (int) NSPageSize(); +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm new file mode 100644 index 0000000..004baeb --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +#if JUCE_IOS +bool isIOSAppActive = true; +#endif + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() +{ + #if JUCE_MAC + return [NSApp isActive]; + #else + return isIOSAppActive; + #endif +} + +JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() +{ + #if JUCE_MAC + [NSApp activateIgnoringOtherApps: YES]; + #endif +} + +JUCE_API void JUCE_CALLTYPE Process::hide() +{ + #if JUCE_MAC + [NSApp hide: nil]; + #endif +} + +JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() +{ + jassertfalse; +} + +JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() +{ + jassertfalse; +} + +JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) +{ + // xxx +} + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + static char testResult = 0; + + if (testResult == 0) + { + struct kinfo_proc info; + int m[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t sz = sizeof (info); + sysctl (m, 4, &info, &sz, 0, 0); + testResult = ((info.kp_proc.p_flag & P_TRACED) != 0) ? 1 : -1; + } + + return testResult > 0; +} + +JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h new file mode 100644 index 0000000..10878d0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -0,0 +1,169 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OSX_OBJCHELPERS_H_INCLUDED +#define JUCE_OSX_OBJCHELPERS_H_INCLUDED + + +/* This file contains a few helper functions that are used internally but which + need to be kept away from the public headers because they use obj-C symbols. +*/ +namespace +{ + //============================================================================== + static inline String nsStringToJuce (NSString* s) + { + return CharPointer_UTF8 ([s UTF8String]); + } + + static inline NSString* juceStringToNS (const String& s) + { + return [NSString stringWithUTF8String: s.toUTF8()]; + } + + static inline NSString* nsStringLiteral (const char* const s) noexcept + { + return [NSString stringWithUTF8String: s]; + } + + static inline NSString* nsEmptyString() noexcept + { + return [NSString string]; + } + + #if JUCE_MAC + template + static NSRect makeNSRect (const RectangleType& r) noexcept + { + return NSMakeRect (static_cast (r.getX()), + static_cast (r.getY()), + static_cast (r.getWidth()), + static_cast (r.getHeight())); + } + #endif +} + +//============================================================================== +template +struct NSObjectRetainer +{ + inline NSObjectRetainer (ObjectType* o) : object (o) { [object retain]; } + inline ~NSObjectRetainer() { [object release]; } + + ObjectType* object; +}; + +//============================================================================== +template +struct ObjCClass +{ + ObjCClass (const char* nameRoot) + : cls (objc_allocateClassPair ([SuperclassType class], getRandomisedName (nameRoot).toUTF8(), 0)) + { + } + + ~ObjCClass() + { + objc_disposeClassPair (cls); + } + + void registerClass() + { + objc_registerClassPair (cls); + } + + SuperclassType* createInstance() const + { + return class_createInstance (cls, 0); + } + + template + void addIvar (const char* name) + { + BOOL b = class_addIvar (cls, name, sizeof (Type), (uint8_t) rint (log2 (sizeof (Type))), @encode (Type)); + jassert (b); (void) b; + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* signature) + { + BOOL b = class_addMethod (cls, selector, (IMP) callbackFn, signature); + jassert (b); (void) b; + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2) + { + addMethod (selector, callbackFn, (String (sig1) + sig2).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3).toUTF8()); + } + + template + void addMethod (SEL selector, FunctionType callbackFn, const char* sig1, const char* sig2, const char* sig3, const char* sig4) + { + addMethod (selector, callbackFn, (String (sig1) + sig2 + sig3 + sig4).toUTF8()); + } + + void addProtocol (Protocol* protocol) + { + BOOL b = class_addProtocol (cls, protocol); + jassert (b); (void) b; + } + + static id sendSuperclassMessage (id self, SEL selector) + { + objc_super s = { self, [SuperclassType class] }; + return objc_msgSendSuper (&s, selector); + } + + template + static Type getIvar (id self, const char* name) + { + void* v = nullptr; + object_getInstanceVariable (self, name, &v); + return static_cast (v); + } + + Class cls; + +private: + static String getRandomisedName (const char* root) + { + return root + String::toHexString (juce::Random::getSystemRandom().nextInt64()); + } + + JUCE_DECLARE_NON_COPYABLE (ObjCClass) +}; + + +#endif // JUCE_OSX_OBJCHELPERS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp b/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp new file mode 100644 index 0000000..d0c0b09 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp @@ -0,0 +1,224 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class NamedPipe::Pimpl +{ +public: + Pimpl (const String& pipePath, bool createPipe) + : pipeInName (pipePath + "_in"), + pipeOutName (pipePath + "_out"), + pipeIn (-1), pipeOut (-1), + createdPipe (createPipe), + stopReadOperation (false) + { + signal (SIGPIPE, signalHandler); + juce_siginterrupt (SIGPIPE, 1); + } + + ~Pimpl() + { + if (pipeIn != -1) ::close (pipeIn); + if (pipeOut != -1) ::close (pipeOut); + + if (createdPipe) + { + unlink (pipeInName.toUTF8()); + unlink (pipeOutName.toUTF8()); + } + } + + int read (char* destBuffer, int maxBytesToRead, int timeOutMilliseconds) + { + const uint32 timeoutEnd = getTimeoutEnd (timeOutMilliseconds); + + if (pipeIn == -1) + { + pipeIn = openPipe (createdPipe ? pipeInName : pipeOutName, O_RDWR | O_NONBLOCK, timeoutEnd); + + if (pipeIn == -1) + return -1; + } + + int bytesRead = 0; + + while (bytesRead < maxBytesToRead) + { + const int bytesThisTime = maxBytesToRead - bytesRead; + const int numRead = (int) ::read (pipeIn, destBuffer, (size_t) bytesThisTime); + + if (numRead <= 0) + { + if (errno != EWOULDBLOCK || stopReadOperation || hasExpired (timeoutEnd)) + return -1; + + const int maxWaitingTime = 30; + waitForInput (pipeIn, timeoutEnd == 0 ? maxWaitingTime + : jmin (maxWaitingTime, + (int) (timeoutEnd - Time::getMillisecondCounter()))); + continue; + } + + bytesRead += numRead; + destBuffer += numRead; + } + + return bytesRead; + } + + int write (const char* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) + { + const uint32 timeoutEnd = getTimeoutEnd (timeOutMilliseconds); + + if (pipeOut == -1) + { + pipeOut = openPipe (createdPipe ? pipeOutName : pipeInName, O_WRONLY, timeoutEnd); + + if (pipeOut == -1) + return -1; + } + + int bytesWritten = 0; + + while (bytesWritten < numBytesToWrite && ! hasExpired (timeoutEnd)) + { + const int bytesThisTime = numBytesToWrite - bytesWritten; + const int numWritten = (int) ::write (pipeOut, sourceBuffer, (size_t) bytesThisTime); + + if (numWritten <= 0) + return -1; + + bytesWritten += numWritten; + sourceBuffer += numWritten; + } + + return bytesWritten; + } + + bool createFifos() const + { + return (mkfifo (pipeInName .toUTF8(), 0666) == 0 || errno == EEXIST) + && (mkfifo (pipeOutName.toUTF8(), 0666) == 0 || errno == EEXIST); + } + + const String pipeInName, pipeOutName; + int pipeIn, pipeOut; + + const bool createdPipe; + bool stopReadOperation; + +private: + static void signalHandler (int) {} + + static uint32 getTimeoutEnd (const int timeOutMilliseconds) + { + return timeOutMilliseconds >= 0 ? Time::getMillisecondCounter() + (uint32) timeOutMilliseconds : 0; + } + + static bool hasExpired (const uint32 timeoutEnd) + { + return timeoutEnd != 0 && Time::getMillisecondCounter() >= timeoutEnd; + } + + int openPipe (const String& name, int flags, const uint32 timeoutEnd) + { + for (;;) + { + const int p = ::open (name.toUTF8(), flags); + + if (p != -1 || hasExpired (timeoutEnd) || stopReadOperation) + return p; + + Thread::sleep (2); + } + } + + static void waitForInput (const int handle, const int timeoutMsecs) noexcept + { + struct timeval timeout; + timeout.tv_sec = timeoutMsecs / 1000; + timeout.tv_usec = (timeoutMsecs % 1000) * 1000; + + fd_set rset; + FD_ZERO (&rset); + FD_SET (handle, &rset); + + select (handle + 1, &rset, nullptr, 0, &timeout); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +void NamedPipe::close() +{ + if (pimpl != nullptr) + { + pimpl->stopReadOperation = true; + + char buffer[1] = { 0 }; + ssize_t done = ::write (pimpl->pipeIn, buffer, 1); + (void) done; + + ScopedWriteLock sl (lock); + pimpl = nullptr; + } +} + +bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) +{ + #if JUCE_IOS + pimpl = new Pimpl (File::getSpecialLocation (File::tempDirectory) + .getChildFile (File::createLegalFileName (pipeName)).getFullPathName(), createPipe); + #else + String file (pipeName); + + if (! File::isAbsolutePath (file)) + file = "/tmp/" + File::createLegalFileName (file); + + pimpl = new Pimpl (file, createPipe); + #endif + + if (createPipe && ! pimpl->createFifos()) + { + pimpl = nullptr; + return false; + } + + return true; +} + +int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->read (static_cast (destBuffer), maxBytesToRead, timeOutMilliseconds) : -1; +} + +int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->write (static_cast (sourceBuffer), numBytesToWrite, timeOutMilliseconds) : -1; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h b/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h new file mode 100644 index 0000000..c94d3a6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h @@ -0,0 +1,1319 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +CriticalSection::CriticalSection() noexcept +{ + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE); + #if ! JUCE_ANDROID + pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); + #endif + pthread_mutex_init (&lock, &atts); + pthread_mutexattr_destroy (&atts); +} + +CriticalSection::~CriticalSection() noexcept { pthread_mutex_destroy (&lock); } +void CriticalSection::enter() const noexcept { pthread_mutex_lock (&lock); } +bool CriticalSection::tryEnter() const noexcept { return pthread_mutex_trylock (&lock) == 0; } +void CriticalSection::exit() const noexcept { pthread_mutex_unlock (&lock); } + +//============================================================================== +WaitableEvent::WaitableEvent (const bool useManualReset) noexcept + : triggered (false), manualReset (useManualReset) +{ + pthread_cond_init (&condition, 0); + + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + #if ! JUCE_ANDROID + pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); + #endif + pthread_mutex_init (&mutex, &atts); +} + +WaitableEvent::~WaitableEvent() noexcept +{ + pthread_cond_destroy (&condition); + pthread_mutex_destroy (&mutex); +} + +bool WaitableEvent::wait (const int timeOutMillisecs) const noexcept +{ + pthread_mutex_lock (&mutex); + + if (! triggered) + { + if (timeOutMillisecs < 0) + { + do + { + pthread_cond_wait (&condition, &mutex); + } + while (! triggered); + } + else + { + struct timeval now; + gettimeofday (&now, 0); + + struct timespec time; + time.tv_sec = now.tv_sec + (timeOutMillisecs / 1000); + time.tv_nsec = (now.tv_usec + ((timeOutMillisecs % 1000) * 1000)) * 1000; + + if (time.tv_nsec >= 1000000000) + { + time.tv_nsec -= 1000000000; + time.tv_sec++; + } + + do + { + if (pthread_cond_timedwait (&condition, &mutex, &time) == ETIMEDOUT) + { + pthread_mutex_unlock (&mutex); + return false; + } + } + while (! triggered); + } + } + + if (! manualReset) + triggered = false; + + pthread_mutex_unlock (&mutex); + return true; +} + +void WaitableEvent::signal() const noexcept +{ + pthread_mutex_lock (&mutex); + + if (! triggered) + { + triggered = true; + pthread_cond_broadcast (&condition); + } + + pthread_mutex_unlock (&mutex); +} + +void WaitableEvent::reset() const noexcept +{ + pthread_mutex_lock (&mutex); + triggered = false; + pthread_mutex_unlock (&mutex); +} + +//============================================================================== +void JUCE_CALLTYPE Thread::sleep (int millisecs) +{ + struct timespec time; + time.tv_sec = millisecs / 1000; + time.tv_nsec = (millisecs % 1000) * 1000000; + nanosleep (&time, nullptr); +} + +void JUCE_CALLTYPE Process::terminate() +{ + #if JUCE_ANDROID + _exit (EXIT_FAILURE); + #else + std::_Exit (EXIT_FAILURE); + #endif +} + +//============================================================================== +const juce_wchar File::separator = '/'; +const String File::separatorString ("/"); + +//============================================================================== +File File::getCurrentWorkingDirectory() +{ + HeapBlock heapBuffer; + + char localBuffer [1024]; + char* cwd = getcwd (localBuffer, sizeof (localBuffer) - 1); + size_t bufferSize = 4096; + + while (cwd == nullptr && errno == ERANGE) + { + heapBuffer.malloc (bufferSize); + cwd = getcwd (heapBuffer, bufferSize - 1); + bufferSize += 1024; + } + + return File (CharPointer_UTF8 (cwd)); +} + +bool File::setAsCurrentWorkingDirectory() const +{ + return chdir (getFullPathName().toUTF8()) == 0; +} + +//============================================================================== +// The unix siginterrupt function is deprecated - this does the same job. +int juce_siginterrupt (int sig, int flag) +{ + struct ::sigaction act; + (void) ::sigaction (sig, nullptr, &act); + + if (flag != 0) + act.sa_flags &= ~SA_RESTART; + else + act.sa_flags |= SA_RESTART; + + return ::sigaction (sig, &act, nullptr); +} + +//============================================================================== +namespace +{ + #if JUCE_LINUX || (JUCE_IOS && ! __DARWIN_ONLY_64_BIT_INO_T) // (this iOS stuff is to avoid a simulator bug) + typedef struct stat64 juce_statStruct; + #define JUCE_STAT stat64 + #else + typedef struct stat juce_statStruct; + #define JUCE_STAT stat + #endif + + bool juce_stat (const String& fileName, juce_statStruct& info) + { + return fileName.isNotEmpty() + && JUCE_STAT (fileName.toUTF8(), &info) == 0; + } + + // if this file doesn't exist, find a parent of it that does.. + bool juce_doStatFS (File f, struct statfs& result) + { + for (int i = 5; --i >= 0;) + { + if (f.exists()) + break; + + f = f.getParentDirectory(); + } + + return statfs (f.getFullPathName().toUTF8(), &result) == 0; + } + + #if (JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5) || JUCE_IOS + static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_birthtime; } + #else + static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_ctime; } + #endif + + void updateStatInfoForFile (const String& path, bool* const isDir, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + if (isDir != nullptr || fileSize != nullptr || modTime != nullptr || creationTime != nullptr) + { + juce_statStruct info; + const bool statOk = juce_stat (path, info); + + if (isDir != nullptr) *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); + if (fileSize != nullptr) *fileSize = statOk ? (int64) info.st_size : 0; + if (modTime != nullptr) *modTime = Time (statOk ? (int64) info.st_mtime * 1000 : 0); + if (creationTime != nullptr) *creationTime = Time (statOk ? getCreationTime (info) * 1000 : 0); + } + + if (isReadOnly != nullptr) + *isReadOnly = access (path.toUTF8(), W_OK) != 0; + } + + Result getResultForErrno() + { + return Result::fail (String (strerror (errno))); + } + + Result getResultForReturnValue (int value) + { + return value == -1 ? getResultForErrno() : Result::ok(); + } + + int getFD (void* handle) noexcept { return (int) (pointer_sized_int) handle; } + void* fdToVoidPointer (int fd) noexcept { return (void*) (pointer_sized_int) fd; } +} + +bool File::isDirectory() const +{ + juce_statStruct info; + + return fullPath.isEmpty() + || (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0)); +} + +bool File::exists() const +{ + return fullPath.isNotEmpty() + && access (fullPath.toUTF8(), F_OK) == 0; +} + +bool File::existsAsFile() const +{ + return exists() && ! isDirectory(); +} + +int64 File::getSize() const +{ + juce_statStruct info; + return juce_stat (fullPath, info) ? info.st_size : 0; +} + +uint64 File::getFileIdentifier() const +{ + juce_statStruct info; + return juce_stat (fullPath, info) ? (uint64) info.st_ino : 0; +} + +//============================================================================== +bool File::hasWriteAccess() const +{ + if (exists()) + return access (fullPath.toUTF8(), W_OK) == 0; + + if ((! isDirectory()) && fullPath.containsChar (separator)) + return getParentDirectory().hasWriteAccess(); + + return false; +} + +static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept +{ + juce_statStruct info; + if (! juce_stat (fullPath, info)) + return false; + + info.st_mode &= 0777; + + if (shouldSet) + info.st_mode |= flags; + else + info.st_mode &= ~flags; + + return chmod (fullPath.toUTF8(), info.st_mode) == 0; +} + +bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const +{ + // Hmm.. should we give global write permission or just the current user? + return setFileModeFlags (fullPath, S_IWUSR | S_IWGRP | S_IWOTH, ! shouldBeReadOnly); +} + +bool File::setFileExecutableInternal (bool shouldBeExecutable) const +{ + return setFileModeFlags (fullPath, S_IXUSR | S_IXGRP | S_IXOTH, shouldBeExecutable); +} + +void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const +{ + modificationTime = 0; + accessTime = 0; + creationTime = 0; + + juce_statStruct info; + + if (juce_stat (fullPath, info)) + { + modificationTime = (int64) info.st_mtime * 1000; + accessTime = (int64) info.st_atime * 1000; + creationTime = (int64) info.st_ctime * 1000; + } +} + +bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 /*creationTime*/) const +{ + juce_statStruct info; + + if ((modificationTime != 0 || accessTime != 0) && juce_stat (fullPath, info)) + { + struct utimbuf times; + times.actime = accessTime != 0 ? (time_t) (accessTime / 1000) : info.st_atime; + times.modtime = modificationTime != 0 ? (time_t) (modificationTime / 1000) : info.st_mtime; + + return utime (fullPath.toUTF8(), ×) == 0; + } + + return false; +} + +bool File::deleteFile() const +{ + if (! exists()) + return true; + + if (isDirectory()) + return rmdir (fullPath.toUTF8()) == 0; + + return remove (fullPath.toUTF8()) == 0; +} + +bool File::moveInternal (const File& dest) const +{ + if (rename (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) == 0) + return true; + + if (hasWriteAccess() && copyInternal (dest)) + { + if (deleteFile()) + return true; + + dest.deleteFile(); + } + + return false; +} + +Result File::createDirectoryInternal (const String& fileName) const +{ + return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); +} + +//===================================================================== +int64 juce_fileSetPosition (void* handle, int64 pos) +{ + if (handle != 0 && lseek (getFD (handle), pos, SEEK_SET) == pos) + return pos; + + return -1; +} + +void FileInputStream::openHandle() +{ + const int f = open (file.getFullPathName().toUTF8(), O_RDONLY, 00644); + + if (f != -1) + fileHandle = fdToVoidPointer (f); + else + status = getResultForErrno(); +} + +FileInputStream::~FileInputStream() +{ + if (fileHandle != 0) + close (getFD (fileHandle)); +} + +size_t FileInputStream::readInternal (void* const buffer, const size_t numBytes) +{ + ssize_t result = 0; + + if (fileHandle != 0) + { + result = ::read (getFD (fileHandle), buffer, numBytes); + + if (result < 0) + { + status = getResultForErrno(); + result = 0; + } + } + + return (size_t) result; +} + +//============================================================================== +void FileOutputStream::openHandle() +{ + if (file.exists()) + { + const int f = open (file.getFullPathName().toUTF8(), O_RDWR, 00644); + + if (f != -1) + { + currentPosition = lseek (f, 0, SEEK_END); + + if (currentPosition >= 0) + { + fileHandle = fdToVoidPointer (f); + } + else + { + status = getResultForErrno(); + close (f); + } + } + else + { + status = getResultForErrno(); + } + } + else + { + const int f = open (file.getFullPathName().toUTF8(), O_RDWR + O_CREAT, 00644); + + if (f != -1) + fileHandle = fdToVoidPointer (f); + else + status = getResultForErrno(); + } +} + +void FileOutputStream::closeHandle() +{ + if (fileHandle != 0) + { + close (getFD (fileHandle)); + fileHandle = 0; + } +} + +ssize_t FileOutputStream::writeInternal (const void* const data, const size_t numBytes) +{ + ssize_t result = 0; + + if (fileHandle != 0) + { + result = ::write (getFD (fileHandle), data, numBytes); + + if (result == -1) + status = getResultForErrno(); + } + + return result; +} + +void FileOutputStream::flushInternal() +{ + if (fileHandle != 0) + { + if (fsync (getFD (fileHandle)) == -1) + status = getResultForErrno(); + + #if JUCE_ANDROID + // This stuff tells the OS to asynchronously update the metadata + // that the OS has cached aboud the file - this metadata is used + // when the device is acting as a USB drive, and unless it's explicitly + // refreshed, it'll get out of step with the real file. + const LocalRef t (javaString (file.getFullPathName())); + android.activity.callVoidMethod (JuceAppActivity.scanFile, t.get()); + #endif + } +} + +Result FileOutputStream::truncate() +{ + if (fileHandle == 0) + return status; + + flush(); + return getResultForReturnValue (ftruncate (getFD (fileHandle), (off_t) currentPosition)); +} + +//============================================================================== +String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue) +{ + if (const char* s = ::getenv (name.toUTF8())) + return String::fromUTF8 (s); + + return defaultValue; +} + +//============================================================================== +void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +{ + jassert (mode == readOnly || mode == readWrite); + + if (range.getStart() > 0) + { + const long pageSize = sysconf (_SC_PAGE_SIZE); + range.setStart (range.getStart() - (range.getStart() % pageSize)); + } + + fileHandle = open (file.getFullPathName().toUTF8(), + mode == readWrite ? (O_CREAT + O_RDWR) : O_RDONLY, 00644); + + if (fileHandle != -1) + { + void* m = mmap (0, (size_t) range.getLength(), + mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ, + MAP_SHARED, fileHandle, + (off_t) range.getStart()); + + if (m != MAP_FAILED) + { + address = m; + madvise (m, (size_t) range.getLength(), MADV_SEQUENTIAL); + } + else + { + range = Range(); + } + } +} + +MemoryMappedFile::~MemoryMappedFile() +{ + if (address != nullptr) + munmap (address, (size_t) range.getLength()); + + if (fileHandle != 0) + close (fileHandle); +} + +//============================================================================== +#if JUCE_PROJUCER_LIVE_BUILD +extern "C" const char* juce_getCurrentExecutablePath(); +#endif + +File juce_getExecutableFile(); +File juce_getExecutableFile() +{ + #if JUCE_PROJUCER_LIVE_BUILD + return File (juce_getCurrentExecutablePath()); + #elif JUCE_ANDROID + return File (android.appFile); + #else + struct DLAddrReader + { + static String getFilename() + { + Dl_info exeInfo; + dladdr ((void*) juce_getExecutableFile, &exeInfo); + return CharPointer_UTF8 (exeInfo.dli_fname); + } + }; + + static String filename (DLAddrReader::getFilename()); + return File::getCurrentWorkingDirectory().getChildFile (filename); + #endif +} + +//============================================================================== +int64 File::getBytesFreeOnVolume() const +{ + struct statfs buf; + if (juce_doStatFS (*this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_bavail; // Note: this returns space available to non-super user + + return 0; +} + +int64 File::getVolumeTotalSize() const +{ + struct statfs buf; + if (juce_doStatFS (*this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_blocks; + + return 0; +} + +String File::getVolumeLabel() const +{ + #if JUCE_MAC + struct VolAttrBuf + { + u_int32_t length; + attrreference_t mountPointRef; + char mountPointSpace [MAXPATHLEN]; + } attrBuf; + + struct attrlist attrList; + zerostruct (attrList); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME; + + File f (*this); + + for (;;) + { + if (getattrlist (f.getFullPathName().toUTF8(), &attrList, &attrBuf, sizeof (attrBuf), 0) == 0) + return String::fromUTF8 (((const char*) &attrBuf.mountPointRef) + attrBuf.mountPointRef.attr_dataoffset, + (int) attrBuf.mountPointRef.attr_length); + + const File parent (f.getParentDirectory()); + + if (f == parent) + break; + + f = parent; + } + #endif + + return String(); +} + +int File::getVolumeSerialNumber() const +{ + int result = 0; +/* int fd = open (getFullPathName().toUTF8(), O_RDONLY | O_NONBLOCK); + + char info [512]; + + #ifndef HDIO_GET_IDENTITY + #define HDIO_GET_IDENTITY 0x030d + #endif + + if (ioctl (fd, HDIO_GET_IDENTITY, info) == 0) + { + DBG (String (info + 20, 20)); + result = String (info + 20, 20).trim().getIntValue(); + } + + close (fd);*/ + return result; +} + +//============================================================================== +#if ! JUCE_IOS +void juce_runSystemCommand (const String&); +void juce_runSystemCommand (const String& command) +{ + int result = system (command.toUTF8()); + (void) result; +} + +String juce_getOutputFromCommand (const String&); +String juce_getOutputFromCommand (const String& command) +{ + // slight bodge here, as we just pipe the output into a temp file and read it... + const File tempFile (File::getSpecialLocation (File::tempDirectory) + .getNonexistentChildFile (String::toHexString (Random::getSystemRandom().nextInt()), ".tmp", false)); + + juce_runSystemCommand (command + " > " + tempFile.getFullPathName()); + + String result (tempFile.loadFileAsString()); + tempFile.deleteFile(); + return result; +} +#endif + +//============================================================================== +#if JUCE_IOS +class InterProcessLock::Pimpl +{ +public: + Pimpl (const String&, int) + : handle (1), refCount (1) // On iOS just fake success.. + { + } + + int handle, refCount; +}; + +#else + +class InterProcessLock::Pimpl +{ +public: + Pimpl (const String& lockName, const int timeOutMillisecs) + : handle (0), refCount (1) + { + #if JUCE_MAC + if (! createLockFile (File ("~/Library/Caches/com.juce.locks").getChildFile (lockName), timeOutMillisecs)) + // Fallback if the user's home folder is on a network drive with no ability to lock.. + createLockFile (File ("/tmp/com.juce.locks").getChildFile (lockName), timeOutMillisecs); + + #else + File tempFolder ("/var/tmp"); + if (! tempFolder.isDirectory()) + tempFolder = "/tmp"; + + createLockFile (tempFolder.getChildFile (lockName), timeOutMillisecs); + #endif + } + + ~Pimpl() + { + closeFile(); + } + + bool createLockFile (const File& file, const int timeOutMillisecs) + { + file.create(); + handle = open (file.getFullPathName().toUTF8(), O_RDWR); + + if (handle != 0) + { + struct flock fl; + zerostruct (fl); + + fl.l_whence = SEEK_SET; + fl.l_type = F_WRLCK; + + const int64 endTime = Time::currentTimeMillis() + timeOutMillisecs; + + for (;;) + { + const int result = fcntl (handle, F_SETLK, &fl); + + if (result >= 0) + return true; + + const int error = errno; + + if (error != EINTR) + { + if (error == EBADF || error == ENOTSUP) + return false; + + if (timeOutMillisecs == 0 + || (timeOutMillisecs > 0 && Time::currentTimeMillis() >= endTime)) + break; + + Thread::sleep (10); + } + } + } + + closeFile(); + return true; // only false if there's a file system error. Failure to lock still returns true. + } + + void closeFile() + { + if (handle != 0) + { + struct flock fl; + zerostruct (fl); + + fl.l_whence = SEEK_SET; + fl.l_type = F_UNLCK; + + while (! (fcntl (handle, F_SETLKW, &fl) >= 0 || errno != EINTR)) + {} + + close (handle); + handle = 0; + } + } + + int handle, refCount; +}; +#endif + +InterProcessLock::InterProcessLock (const String& nm) : name (nm) +{ +} + +InterProcessLock::~InterProcessLock() +{ +} + +bool InterProcessLock::enter (const int timeOutMillisecs) +{ + const ScopedLock sl (lock); + + if (pimpl == nullptr) + { + pimpl = new Pimpl (name, timeOutMillisecs); + + if (pimpl->handle == 0) + pimpl = nullptr; + } + else + { + pimpl->refCount++; + } + + return pimpl != nullptr; +} + +void InterProcessLock::exit() +{ + const ScopedLock sl (lock); + + // Trying to release the lock too many times! + jassert (pimpl != nullptr); + + if (pimpl != nullptr && --(pimpl->refCount) == 0) + pimpl = nullptr; +} + +//============================================================================== +void JUCE_API juce_threadEntryPoint (void*); + +extern "C" void* threadEntryProc (void*); +extern "C" void* threadEntryProc (void* userData) +{ + JUCE_AUTORELEASEPOOL + { + #if JUCE_ANDROID + const AndroidThreadScope androidEnv; + #endif + + juce_threadEntryPoint (userData); + } + + return nullptr; +} + +void Thread::launchThread() +{ + threadHandle = 0; + pthread_t handle = 0; + + if (pthread_create (&handle, 0, threadEntryProc, this) == 0) + { + pthread_detach (handle); + threadHandle = (void*) handle; + threadId = (ThreadID) threadHandle; + } +} + +void Thread::closeThreadHandle() +{ + threadId = 0; + threadHandle = 0; +} + +void Thread::killThread() +{ + if (threadHandle != 0) + { + #if JUCE_ANDROID + jassertfalse; // pthread_cancel not available! + #else + pthread_cancel ((pthread_t) threadHandle); + #endif + } +} + +void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) +{ + #if JUCE_IOS || (JUCE_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + JUCE_AUTORELEASEPOOL + { + [[NSThread currentThread] setName: juceStringToNS (name)]; + } + #elif JUCE_LINUX + #if (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 + pthread_setname_np (pthread_self(), name.toRawUTF8()); + #else + prctl (PR_SET_NAME, name.toRawUTF8(), 0, 0, 0); + #endif + #endif +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + struct sched_param param; + int policy; + priority = jlimit (0, 10, priority); + + if (handle == nullptr) + handle = (void*) pthread_self(); + + if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) + return false; + + policy = priority == 0 ? SCHED_OTHER : SCHED_RR; + + const int minPriority = sched_get_priority_min (policy); + const int maxPriority = sched_get_priority_max (policy); + + param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; + return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; +} + +Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() +{ + return (ThreadID) pthread_self(); +} + +void JUCE_CALLTYPE Thread::yield() +{ + sched_yield(); +} + +//============================================================================== +/* Remove this macro if you're having problems compiling the cpu affinity + calls (the API for these has changed about quite a bit in various Linux + versions, and a lot of distros seem to ship with obsolete versions) +*/ +#if defined (CPU_ISSET) && ! defined (SUPPORT_AFFINITIES) + #define SUPPORT_AFFINITIES 1 +#endif + +void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) +{ + #if SUPPORT_AFFINITIES + cpu_set_t affinity; + CPU_ZERO (&affinity); + + for (int i = 0; i < 32; ++i) + if ((affinityMask & (1 << i)) != 0) + CPU_SET (i, &affinity); + + /* + N.B. If this line causes a compile error, then you've probably not got the latest + version of glibc installed. + + If you don't want to update your copy of glibc and don't care about cpu affinities, + then you can just disable all this stuff by setting the SUPPORT_AFFINITIES macro to 0. + */ + sched_setaffinity (getpid(), sizeof (cpu_set_t), &affinity); + sched_yield(); + + #else + /* affinities aren't supported because either the appropriate header files weren't found, + or the SUPPORT_AFFINITIES macro was turned off + */ + jassertfalse; + (void) affinityMask; + #endif +} + +//============================================================================== +bool DynamicLibrary::open (const String& name) +{ + close(); + handle = dlopen (name.isEmpty() ? nullptr : name.toUTF8().getAddress(), RTLD_LOCAL | RTLD_NOW); + return handle != nullptr; +} + +void DynamicLibrary::close() +{ + if (handle != nullptr) + { + dlclose (handle); + handle = nullptr; + } +} + +void* DynamicLibrary::getFunction (const String& functionName) noexcept +{ + return handle != nullptr ? dlsym (handle, functionName.toUTF8()) : nullptr; +} + + + +//============================================================================== +class ChildProcess::ActiveProcess +{ +public: + ActiveProcess (const StringArray& arguments, int streamFlags) + : childPID (0), pipeHandle (0), readHandle (0) + { + // Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX + // you're trying to launch the .app folder rather than the actual binary inside it?) + jassert ((! arguments[0].containsChar ('/')) + || File::getCurrentWorkingDirectory().getChildFile (arguments[0]).existsAsFile()); + + int pipeHandles[2] = { 0 }; + + if (pipe (pipeHandles) == 0) + { + const pid_t result = fork(); + + if (result < 0) + { + close (pipeHandles[0]); + close (pipeHandles[1]); + } + else if (result == 0) + { + // we're the child process.. + close (pipeHandles[0]); // close the read handle + + if ((streamFlags & wantStdOut) != 0) + dup2 (pipeHandles[1], 1); // turns the pipe into stdout + else + close (STDOUT_FILENO); + + if ((streamFlags & wantStdErr) != 0) + dup2 (pipeHandles[1], 2); + else + close (STDERR_FILENO); + + close (pipeHandles[1]); + + Array argv; + for (int i = 0; i < arguments.size(); ++i) + if (arguments[i].isNotEmpty()) + argv.add (const_cast (arguments[i].toUTF8().getAddress())); + + argv.add (nullptr); + + execvp (argv[0], argv.getRawDataPointer()); + exit (-1); + } + else + { + // we're the parent process.. + childPID = result; + pipeHandle = pipeHandles[0]; + close (pipeHandles[1]); // close the write handle + } + } + } + + ~ActiveProcess() + { + if (readHandle != 0) + fclose (readHandle); + + if (pipeHandle != 0) + close (pipeHandle); + } + + bool isRunning() const noexcept + { + if (childPID != 0) + { + int childState; + const int pid = waitpid (childPID, &childState, WNOHANG); + return pid == 0 || ! (WIFEXITED (childState) || WIFSIGNALED (childState)); + } + + return false; + } + + int read (void* const dest, const int numBytes) noexcept + { + jassert (dest != nullptr); + + #ifdef fdopen + #error // the zlib headers define this function as NULL! + #endif + + if (readHandle == 0 && childPID != 0) + readHandle = fdopen (pipeHandle, "r"); + + if (readHandle != 0) + return (int) fread (dest, 1, (size_t) numBytes, readHandle); + + return 0; + } + + bool killProcess() const noexcept + { + return ::kill (childPID, SIGKILL) == 0; + } + + uint32 getExitCode() const noexcept + { + if (childPID != 0) + { + int childState = 0; + const int pid = waitpid (childPID, &childState, WNOHANG); + + if (pid >= 0 && WIFEXITED (childState)) + return WEXITSTATUS (childState); + } + + return 0; + } + + int childPID; + +private: + int pipeHandle; + FILE* readHandle; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveProcess) +}; + +bool ChildProcess::start (const String& command, int streamFlags) +{ + return start (StringArray::fromTokens (command, true), streamFlags); +} + +bool ChildProcess::start (const StringArray& args, int streamFlags) +{ + if (args.size() == 0) + return false; + + activeProcess = new ActiveProcess (args, streamFlags); + + if (activeProcess->childPID == 0) + activeProcess = nullptr; + + return activeProcess != nullptr; +} + +//============================================================================== +struct HighResolutionTimer::Pimpl +{ + Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) + { + } + + ~Pimpl() + { + jassert (thread == 0); + } + + void start (int newPeriod) + { + if (periodMs != newPeriod) + { + if (thread != pthread_self()) + { + stop(); + + periodMs = newPeriod; + shouldStop = false; + + if (pthread_create (&thread, nullptr, timerThread, this) == 0) + setThreadToRealtime (thread, (uint64) newPeriod); + else + jassertfalse; + } + else + { + periodMs = newPeriod; + shouldStop = false; + } + } + } + + void stop() + { + if (thread != 0) + { + shouldStop = true; + + while (thread != 0 && thread != pthread_self()) + Thread::yield(); + } + } + + HighResolutionTimer& owner; + int volatile periodMs; + +private: + pthread_t thread; + bool volatile shouldStop; + + static void* timerThread (void* param) + { + #if JUCE_ANDROID + const AndroidThreadScope androidEnv; + #else + int dummy; + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); + #endif + + reinterpret_cast (param)->timerThread(); + return nullptr; + } + + void timerThread() + { + int lastPeriod = periodMs; + Clock clock (lastPeriod); + + while (! shouldStop) + { + clock.wait(); + owner.hiResTimerCallback(); + + if (lastPeriod != periodMs) + { + lastPeriod = periodMs; + clock = Clock (lastPeriod); + } + } + + periodMs = 0; + thread = 0; + } + + struct Clock + { + #if JUCE_MAC || JUCE_IOS + Clock (double millis) noexcept + { + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + delta = (((uint64_t) (millis * 1000000.0)) * timebase.denom) / timebase.numer; + time = mach_absolute_time(); + } + + void wait() noexcept + { + time += delta; + mach_wait_until (time); + } + + uint64_t time, delta; + + #elif JUCE_ANDROID + Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) + { + } + + void wait() noexcept + { + struct timespec t; + t.tv_sec = (time_t) (delta / 1000000000); + t.tv_nsec = (long) (delta % 1000000000); + nanosleep (&t, nullptr); + } + + uint64 delta; + #else + Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) + { + struct timespec t; + clock_gettime (CLOCK_MONOTONIC, &t); + time = 1000000000 * (int64) t.tv_sec + t.tv_nsec; + } + + void wait() noexcept + { + time += delta; + + struct timespec t; + t.tv_sec = (time_t) (time / 1000000000); + t.tv_nsec = (long) (time % 1000000000); + + clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &t, nullptr); + } + + uint64 time, delta; + #endif + }; + + static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) + { + #if JUCE_MAC || JUCE_IOS + thread_time_constraint_policy_data_t policy; + policy.period = (uint32_t) (periodMs * 1000000); + policy.computation = 50000; + policy.constraint = policy.period; + policy.preemptible = true; + + return thread_policy_set (pthread_mach_thread_np (thread), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; + + #else + (void) periodMs; + struct sched_param param; + param.sched_priority = sched_get_priority_max (SCHED_RR); + return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; + + #endif + } + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h b/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h new file mode 100644 index 0000000..0dc0efc --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -0,0 +1,170 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WIN32_COMSMARTPTR_H_INCLUDED +#define JUCE_WIN32_COMSMARTPTR_H_INCLUDED + +#if ! (defined (_MSC_VER) || defined (__uuidof)) +template struct UUIDGetter { static CLSID get() { jassertfalse; return CLSID(); } }; +#define __uuidof(x) UUIDGetter::get() +#endif + +inline GUID uuidFromString (const char* const s) noexcept +{ + unsigned long p0; + unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + + #ifndef _MSC_VER + sscanf + #else + sscanf_s + #endif + (s, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + &p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10); + + GUID g = { p0, (uint16) p1, (uint16) p2, { (uint8) p3, (uint8) p4, (uint8) p5, (uint8) p6, + (uint8) p7, (uint8) p8, (uint8) p9, (uint8) p10 }}; + return g; +} + +//============================================================================== +/** A simple COM smart pointer. +*/ +template +class ComSmartPtr +{ +public: + ComSmartPtr() throw() : p (0) {} + ComSmartPtr (ComClass* const obj) : p (obj) { if (p) p->AddRef(); } + ComSmartPtr (const ComSmartPtr& other) : p (other.p) { if (p) p->AddRef(); } + ~ComSmartPtr() { release(); } + + operator ComClass*() const throw() { return p; } + ComClass& operator*() const throw() { return *p; } + ComClass* operator->() const throw() { return p; } + + ComSmartPtr& operator= (ComClass* const newP) + { + if (newP != 0) newP->AddRef(); + release(); + p = newP; + return *this; + } + + ComSmartPtr& operator= (const ComSmartPtr& newP) { return operator= (newP.p); } + + // Releases and nullifies this pointer and returns its address + ComClass** resetAndGetPointerAddress() + { + release(); + p = 0; + return &p; + } + + HRESULT CoCreateInstance (REFCLSID classUUID, DWORD dwClsContext = CLSCTX_INPROC_SERVER) + { + HRESULT hr = ::CoCreateInstance (classUUID, 0, dwClsContext, __uuidof (ComClass), (void**) resetAndGetPointerAddress()); + jassert (hr != CO_E_NOTINITIALIZED); // You haven't called CoInitialize for the current thread! + return hr; + } + + template + HRESULT QueryInterface (REFCLSID classUUID, ComSmartPtr& destObject) const + { + if (p == 0) + return E_POINTER; + + return p->QueryInterface (classUUID, (void**) destObject.resetAndGetPointerAddress()); + } + + template + HRESULT QueryInterface (ComSmartPtr& destObject) const + { + return this->QueryInterface (__uuidof (OtherComClass), destObject); + } + +private: + ComClass* p; + + void release() { if (p != 0) p->Release(); } + + ComClass** operator&() throw(); // private to avoid it being used accidentally +}; + +//============================================================================== +#define JUCE_COMRESULT HRESULT __stdcall + +//============================================================================== +template +class ComBaseClassHelperBase : public ComClass +{ +public: + ComBaseClassHelperBase (unsigned int initialRefCount) : refCount (initialRefCount) {} + virtual ~ComBaseClassHelperBase() {} + + ULONG __stdcall AddRef() { return ++refCount; } + ULONG __stdcall Release() { const ULONG r = --refCount; if (r == 0) delete this; return r; } + +protected: + ULONG refCount; + + JUCE_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == IID_IUnknown) + return castToType (result); + + *result = 0; + return E_NOINTERFACE; + } + + template + JUCE_COMRESULT castToType (void** result) + { + this->AddRef(); *result = dynamic_cast (this); return S_OK; + } +}; + +/** Handy base class for writing COM objects, providing ref-counting and a basic QueryInterface method. +*/ +template +class ComBaseClassHelper : public ComBaseClassHelperBase +{ +public: + ComBaseClassHelper (unsigned int initialRefCount = 1) : ComBaseClassHelperBase (initialRefCount) {} + ~ComBaseClassHelper() {} + + JUCE_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == __uuidof (ComClass)) + return this->template castToType (result); + + return ComBaseClassHelperBase ::QueryInterface (refId, result); + } +}; + +#endif // JUCE_WIN32_COMSMARTPTR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp new file mode 100644 index 0000000..b918cab --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp @@ -0,0 +1,1005 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef INVALID_FILE_ATTRIBUTES + #define INVALID_FILE_ATTRIBUTES ((DWORD) -1) +#endif + +//============================================================================== +namespace WindowsFileHelpers +{ + DWORD getAtts (const String& path) + { + return GetFileAttributes (path.toWideCharPointer()); + } + + int64 fileTimeToTime (const FILETIME* const ft) + { + static_jassert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME)); // tell me if this fails! + + return (int64) ((reinterpret_cast (ft)->QuadPart - 116444736000000000LL) / 10000); + } + + FILETIME* timeToFileTime (const int64 time, FILETIME* const ft) noexcept + { + if (time <= 0) + return nullptr; + + reinterpret_cast (ft)->QuadPart = (ULONGLONG) (time * 10000 + 116444736000000000LL); + return ft; + } + + String getDriveFromPath (String path) + { + if (path.isNotEmpty() && path[1] == ':' && path[2] == 0) + path << '\\'; + + const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (path.getCharPointer()) + 4; + HeapBlock pathCopy; + pathCopy.calloc (numBytes, 1); + path.copyToUTF16 (pathCopy, numBytes); + + if (PathStripToRoot (pathCopy)) + path = static_cast (pathCopy); + + return path; + } + + int64 getDiskSpaceInfo (const String& path, const bool total) + { + ULARGE_INTEGER spc, tot, totFree; + + if (GetDiskFreeSpaceEx (getDriveFromPath (path).toWideCharPointer(), &spc, &tot, &totFree)) + return total ? (int64) tot.QuadPart + : (int64) spc.QuadPart; + + return 0; + } + + unsigned int getWindowsDriveType (const String& path) + { + return GetDriveType (getDriveFromPath (path).toWideCharPointer()); + } + + File getSpecialFolderPath (int type) + { + WCHAR path [MAX_PATH + 256]; + + if (SHGetSpecialFolderPath (0, path, type, FALSE)) + return File (String (path)); + + return File(); + } + + File getModuleFileName (HINSTANCE moduleHandle) + { + WCHAR dest [MAX_PATH + 256]; + dest[0] = 0; + GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest)); + return File (String (dest)); + } + + Result getResultForLastError() + { + TCHAR messageBuffer [256] = { 0 }; + + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr); + + return Result::fail (String (messageBuffer)); + } +} + +//============================================================================== +const juce_wchar File::separator = '\\'; +const String File::separatorString ("\\"); + + +//============================================================================== +bool File::exists() const +{ + return fullPath.isNotEmpty() + && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES; +} + +bool File::existsAsFile() const +{ + return fullPath.isNotEmpty() + && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool File::isDirectory() const +{ + const DWORD attr = WindowsFileHelpers::getAtts (fullPath); + return ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) && (attr != INVALID_FILE_ATTRIBUTES); +} + +bool File::hasWriteAccess() const +{ + if (exists()) + return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_READONLY) == 0; + + // on windows, it seems that even read-only directories can still be written into, + // so checking the parent directory's permissions would return the wrong result.. + return true; +} + +bool File::setFileReadOnlyInternal (const bool shouldBeReadOnly) const +{ + const DWORD oldAtts = WindowsFileHelpers::getAtts (fullPath); + + if (oldAtts == INVALID_FILE_ATTRIBUTES) + return false; + + const DWORD newAtts = shouldBeReadOnly ? (oldAtts | FILE_ATTRIBUTE_READONLY) + : (oldAtts & ~FILE_ATTRIBUTE_READONLY); + return newAtts == oldAtts + || SetFileAttributes (fullPath.toWideCharPointer(), newAtts) != FALSE; +} + +bool File::setFileExecutableInternal (bool /*shouldBeExecutable*/) const +{ + // XXX is this possible? + return false; +} + +bool File::isHidden() const +{ + return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_HIDDEN) != 0; +} + +//============================================================================== +bool File::deleteFile() const +{ + if (! exists()) + return true; + + return isDirectory() ? RemoveDirectory (fullPath.toWideCharPointer()) != 0 + : DeleteFile (fullPath.toWideCharPointer()) != 0; +} + +bool File::moveToTrash() const +{ + if (! exists()) + return true; + + // The string we pass in must be double null terminated.. + const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (fullPath.getCharPointer()) + 8; + HeapBlock doubleNullTermPath; + doubleNullTermPath.calloc (numBytes, 1); + fullPath.copyToUTF16 (doubleNullTermPath, numBytes); + + SHFILEOPSTRUCT fos = { 0 }; + fos.wFunc = FO_DELETE; + fos.pFrom = doubleNullTermPath; + fos.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION + | FOF_NOCONFIRMMKDIR | FOF_RENAMEONCOLLISION; + + return SHFileOperation (&fos) == 0; +} + +bool File::copyInternal (const File& dest) const +{ + return CopyFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer(), false) != 0; +} + +bool File::moveInternal (const File& dest) const +{ + return MoveFile (fullPath.toWideCharPointer(), dest.getFullPathName().toWideCharPointer()) != 0; +} + +Result File::createDirectoryInternal (const String& fileName) const +{ + return CreateDirectory (fileName.toWideCharPointer(), 0) ? Result::ok() + : WindowsFileHelpers::getResultForLastError(); +} + +//============================================================================== +int64 juce_fileSetPosition (void* handle, int64 pos) +{ + LARGE_INTEGER li; + li.QuadPart = pos; + li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart, &li.HighPart, FILE_BEGIN); // (returns -1 if it fails) + return li.QuadPart; +} + +void FileInputStream::openHandle() +{ + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (h != INVALID_HANDLE_VALUE) + fileHandle = (void*) h; + else + status = WindowsFileHelpers::getResultForLastError(); +} + +FileInputStream::~FileInputStream() +{ + CloseHandle ((HANDLE) fileHandle); +} + +size_t FileInputStream::readInternal (void* buffer, size_t numBytes) +{ + if (fileHandle != 0) + { + DWORD actualNum = 0; + if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0)) + status = WindowsFileHelpers::getResultForLastError(); + + return (size_t) actualNum; + } + + return 0; +} + +//============================================================================== +void FileOutputStream::openHandle() +{ + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (h != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER li; + li.QuadPart = 0; + li.LowPart = SetFilePointer (h, 0, &li.HighPart, FILE_END); + + if (li.LowPart != INVALID_SET_FILE_POINTER) + { + fileHandle = (void*) h; + currentPosition = li.QuadPart; + return; + } + } + + status = WindowsFileHelpers::getResultForLastError(); +} + +void FileOutputStream::closeHandle() +{ + CloseHandle ((HANDLE) fileHandle); +} + +ssize_t FileOutputStream::writeInternal (const void* buffer, size_t numBytes) +{ + if (fileHandle != nullptr) + { + DWORD actualNum = 0; + if (! WriteFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0)) + status = WindowsFileHelpers::getResultForLastError(); + + return (ssize_t) actualNum; + } + + return 0; +} + +void FileOutputStream::flushInternal() +{ + if (fileHandle != nullptr) + if (! FlushFileBuffers ((HANDLE) fileHandle)) + status = WindowsFileHelpers::getResultForLastError(); +} + +Result FileOutputStream::truncate() +{ + if (fileHandle == nullptr) + return status; + + flush(); + return SetEndOfFile ((HANDLE) fileHandle) ? Result::ok() + : WindowsFileHelpers::getResultForLastError(); +} + +//============================================================================== +void MemoryMappedFile::openInternal (const File& file, AccessMode mode) +{ + jassert (mode == readOnly || mode == readWrite); + + if (range.getStart() > 0) + { + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + + range.setStart (range.getStart() - (range.getStart() % systemInfo.dwAllocationGranularity)); + } + + DWORD accessMode = GENERIC_READ, createType = OPEN_EXISTING; + DWORD protect = PAGE_READONLY, access = FILE_MAP_READ; + + if (mode == readWrite) + { + accessMode = GENERIC_READ | GENERIC_WRITE; + createType = OPEN_ALWAYS; + protect = PAGE_READWRITE; + access = FILE_MAP_ALL_ACCESS; + } + + HANDLE h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode, FILE_SHARE_READ, 0, + createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0); + + if (h != INVALID_HANDLE_VALUE) + { + fileHandle = (void*) h; + + HANDLE mappingHandle = CreateFileMapping (h, 0, protect, (DWORD) (range.getEnd() >> 32), (DWORD) range.getEnd(), 0); + + if (mappingHandle != 0) + { + address = MapViewOfFile (mappingHandle, access, (DWORD) (range.getStart() >> 32), + (DWORD) range.getStart(), (SIZE_T) range.getLength()); + + if (address == nullptr) + range = Range(); + + CloseHandle (mappingHandle); + } + } +} + +MemoryMappedFile::~MemoryMappedFile() +{ + if (address != nullptr) + UnmapViewOfFile (address); + + if (fileHandle != nullptr) + CloseHandle ((HANDLE) fileHandle); +} + +//============================================================================== +int64 File::getSize() const +{ + WIN32_FILE_ATTRIBUTE_DATA attributes; + + if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes)) + return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow; + + return 0; +} + +void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const +{ + using namespace WindowsFileHelpers; + WIN32_FILE_ATTRIBUTE_DATA attributes; + + if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes)) + { + modificationTime = fileTimeToTime (&attributes.ftLastWriteTime); + creationTime = fileTimeToTime (&attributes.ftCreationTime); + accessTime = fileTimeToTime (&attributes.ftLastAccessTime); + } + else + { + creationTime = accessTime = modificationTime = 0; + } +} + +bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 creationTime) const +{ + using namespace WindowsFileHelpers; + + bool ok = false; + HANDLE h = CreateFile (fullPath.toWideCharPointer(), GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (h != INVALID_HANDLE_VALUE) + { + FILETIME m, a, c; + + ok = SetFileTime (h, + timeToFileTime (creationTime, &c), + timeToFileTime (accessTime, &a), + timeToFileTime (modificationTime, &m)) != 0; + + CloseHandle (h); + } + + return ok; +} + +//============================================================================== +void File::findFileSystemRoots (Array& destArray) +{ + TCHAR buffer [2048] = { 0 }; + GetLogicalDriveStrings (2048, buffer); + + const TCHAR* n = buffer; + StringArray roots; + + while (*n != 0) + { + roots.add (String (n)); + + while (*n++ != 0) + {} + } + + roots.sort (true); + + for (int i = 0; i < roots.size(); ++i) + destArray.add (roots [i]); +} + +//============================================================================== +String File::getVolumeLabel() const +{ + TCHAR dest[64]; + if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest, + (DWORD) numElementsInArray (dest), 0, 0, 0, 0, 0)) + dest[0] = 0; + + return dest; +} + +int File::getVolumeSerialNumber() const +{ + TCHAR dest[64]; + DWORD serialNum; + + if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest, + (DWORD) numElementsInArray (dest), &serialNum, 0, 0, 0, 0)) + return 0; + + return (int) serialNum; +} + +int64 File::getBytesFreeOnVolume() const +{ + return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), false); +} + +int64 File::getVolumeTotalSize() const +{ + return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true); +} + +uint64 File::getFileIdentifier() const +{ + uint64 result = 0; + + HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), + GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + + if (h != INVALID_HANDLE_VALUE) + { + BY_HANDLE_FILE_INFORMATION info; + zerostruct (info); + + if (GetFileInformationByHandle (h, &info)) + result = (((uint64) info.nFileIndexHigh) << 32) | info.nFileIndexLow; + + CloseHandle (h); + } + + return result; +} + +//============================================================================== +bool File::isOnCDRomDrive() const +{ + return WindowsFileHelpers::getWindowsDriveType (getFullPathName()) == DRIVE_CDROM; +} + +bool File::isOnHardDisk() const +{ + if (fullPath.isEmpty()) + return false; + + const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName()); + + if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':') + return n != DRIVE_REMOVABLE; + + return n != DRIVE_CDROM && n != DRIVE_REMOTE; +} + +bool File::isOnRemovableDrive() const +{ + if (fullPath.isEmpty()) + return false; + + const unsigned int n = WindowsFileHelpers::getWindowsDriveType (getFullPathName()); + + return n == DRIVE_CDROM + || n == DRIVE_REMOTE + || n == DRIVE_REMOVABLE + || n == DRIVE_RAMDISK; +} + +//============================================================================== +File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type) +{ + int csidlType = 0; + + switch (type) + { + case userHomeDirectory: csidlType = CSIDL_PROFILE; break; + case userDocumentsDirectory: csidlType = CSIDL_PERSONAL; break; + case userDesktopDirectory: csidlType = CSIDL_DESKTOP; break; + case userApplicationDataDirectory: csidlType = CSIDL_APPDATA; break; + case commonApplicationDataDirectory: csidlType = CSIDL_COMMON_APPDATA; break; + case commonDocumentsDirectory: csidlType = CSIDL_COMMON_DOCUMENTS; break; + case globalApplicationsDirectory: csidlType = CSIDL_PROGRAM_FILES; break; + case userMusicDirectory: csidlType = 0x0d; /*CSIDL_MYMUSIC*/ break; + case userMoviesDirectory: csidlType = 0x0e; /*CSIDL_MYVIDEO*/ break; + case userPicturesDirectory: csidlType = 0x27; /*CSIDL_MYPICTURES*/ break; + + case tempDirectory: + { + WCHAR dest [2048]; + dest[0] = 0; + GetTempPath ((DWORD) numElementsInArray (dest), dest); + return File (String (dest)); + } + + case windowsSystemDirectory: + { + WCHAR dest [2048]; + dest[0] = 0; + GetSystemDirectoryW (dest, (UINT) numElementsInArray (dest)); + return File (String (dest)); + } + + case invokedExecutableFile: + case currentExecutableFile: + case currentApplicationFile: + return WindowsFileHelpers::getModuleFileName ((HINSTANCE) Process::getCurrentModuleInstanceHandle()); + + case hostApplicationPath: + return WindowsFileHelpers::getModuleFileName (0); + + default: + jassertfalse; // unknown type? + return File(); + } + + return WindowsFileHelpers::getSpecialFolderPath (csidlType); +} + +//============================================================================== +File File::getCurrentWorkingDirectory() +{ + WCHAR dest [MAX_PATH + 256]; + dest[0] = 0; + GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest); + return File (String (dest)); +} + +bool File::setAsCurrentWorkingDirectory() const +{ + return SetCurrentDirectory (getFullPathName().toWideCharPointer()) != FALSE; +} + +//============================================================================== +String File::getVersion() const +{ + String result; + + DWORD handle = 0; + DWORD bufferSize = GetFileVersionInfoSize (getFullPathName().toWideCharPointer(), &handle); + HeapBlock buffer; + buffer.calloc (bufferSize); + + if (GetFileVersionInfo (getFullPathName().toWideCharPointer(), 0, bufferSize, buffer)) + { + VS_FIXEDFILEINFO* vffi; + UINT len = 0; + + if (VerQueryValue (buffer, (LPTSTR) _T("\\"), (LPVOID*) &vffi, &len)) + { + result << (int) HIWORD (vffi->dwFileVersionMS) << '.' + << (int) LOWORD (vffi->dwFileVersionMS) << '.' + << (int) HIWORD (vffi->dwFileVersionLS) << '.' + << (int) LOWORD (vffi->dwFileVersionLS); + } + } + + return result; +} + +//============================================================================== +bool File::isLink() const +{ + return hasFileExtension (".lnk"); +} + +File File::getLinkedTarget() const +{ + File result (*this); + String p (getFullPathName()); + + if (! exists()) + p += ".lnk"; + else if (! hasFileExtension (".lnk")) + return result; + + ComSmartPtr shellLink; + ComSmartPtr persistFile; + + if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) + && SUCCEEDED (shellLink.QueryInterface (persistFile)) + && SUCCEEDED (persistFile->Load (p.toWideCharPointer(), STGM_READ)) + && SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI))) + { + WIN32_FIND_DATA winFindData; + WCHAR resolvedPath [MAX_PATH]; + + if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, SLGP_UNCPRIORITY))) + result = File (resolvedPath); + } + + return result; +} + +bool File::createLink (const String& description, const File& linkFileToCreate) const +{ + linkFileToCreate.deleteFile(); + + ComSmartPtr shellLink; + ComSmartPtr persistFile; + + CoInitialize (0); + + return SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) + && SUCCEEDED (shellLink->SetPath (getFullPathName().toWideCharPointer())) + && SUCCEEDED (shellLink->SetDescription (description.toWideCharPointer())) + && SUCCEEDED (shellLink.QueryInterface (persistFile)) + && SUCCEEDED (persistFile->Save (linkFileToCreate.getFullPathName().toWideCharPointer(), TRUE)); +} + +//============================================================================== +class DirectoryIterator::NativeIterator::Pimpl +{ +public: + Pimpl (const File& directory, const String& wildCard) + : directoryWithWildCard (File::addTrailingSeparator (directory.getFullPathName()) + wildCard), + handle (INVALID_HANDLE_VALUE) + { + } + + ~Pimpl() + { + if (handle != INVALID_HANDLE_VALUE) + FindClose (handle); + } + + bool next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) + { + using namespace WindowsFileHelpers; + WIN32_FIND_DATA findData; + + if (handle == INVALID_HANDLE_VALUE) + { + handle = FindFirstFile (directoryWithWildCard.toWideCharPointer(), &findData); + + if (handle == INVALID_HANDLE_VALUE) + return false; + } + else + { + if (FindNextFile (handle, &findData) == 0) + return false; + } + + filenameFound = findData.cFileName; + + if (isDir != nullptr) *isDir = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + if (isHidden != nullptr) *isHidden = ((findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0); + if (isReadOnly != nullptr) *isReadOnly = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0); + if (fileSize != nullptr) *fileSize = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32); + if (modTime != nullptr) *modTime = Time (fileTimeToTime (&findData.ftLastWriteTime)); + if (creationTime != nullptr) *creationTime = Time (fileTimeToTime (&findData.ftCreationTime)); + + return true; + } + +private: + const String directoryWithWildCard; + HANDLE handle; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard) + : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard)) +{ +} + +DirectoryIterator::NativeIterator::~NativeIterator() +{ +} + +bool DirectoryIterator::NativeIterator::next (String& filenameFound, + bool* const isDir, bool* const isHidden, int64* const fileSize, + Time* const modTime, Time* const creationTime, bool* const isReadOnly) +{ + return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); +} + + +//============================================================================== +bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& parameters) +{ + HINSTANCE hInstance = 0; + + JUCE_TRY + { + hInstance = ShellExecute (0, 0, fileName.toWideCharPointer(), + parameters.toWideCharPointer(), 0, SW_SHOWDEFAULT); + } + JUCE_CATCH_ALL + + return hInstance > (HINSTANCE) 32; +} + +void File::revealToUser() const +{ + DynamicLibrary dll ("Shell32.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, ILCreateFromPathW, ilCreateFromPathW, ITEMIDLIST*, (LPCWSTR)) + JUCE_LOAD_WINAPI_FUNCTION (dll, ILFree, ilFree, void, (ITEMIDLIST*)) + JUCE_LOAD_WINAPI_FUNCTION (dll, SHOpenFolderAndSelectItems, shOpenFolderAndSelectItems, HRESULT, (ITEMIDLIST*, UINT, void*, DWORD)) + + if (ilCreateFromPathW != nullptr && shOpenFolderAndSelectItems != nullptr && ilFree != nullptr) + { + if (ITEMIDLIST* const itemIDList = ilCreateFromPathW (fullPath.toWideCharPointer())) + { + shOpenFolderAndSelectItems (itemIDList, 0, nullptr, 0); + ilFree (itemIDList); + } + } +} + +//============================================================================== +class NamedPipe::Pimpl +{ +public: + Pimpl (const String& pipeName, const bool createPipe) + : filename ("\\\\.\\pipe\\" + File::createLegalFileName (pipeName)), + pipeH (INVALID_HANDLE_VALUE), + cancelEvent (CreateEvent (0, FALSE, FALSE, 0)), + connected (false), ownsPipe (createPipe), shouldStop (false) + { + if (createPipe) + pipeH = CreateNamedPipe (filename.toWideCharPointer(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0, + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, 0); + } + + ~Pimpl() + { + disconnectPipe(); + + if (pipeH != INVALID_HANDLE_VALUE) + CloseHandle (pipeH); + + CloseHandle (cancelEvent); + } + + bool connect (const int timeOutMs) + { + if (! ownsPipe) + { + if (pipeH != INVALID_HANDLE_VALUE) + return true; + + const Time timeOutEnd (Time::getCurrentTime() + RelativeTime::milliseconds (timeOutMs)); + + for (;;) + { + { + const ScopedLock sl (createFileLock); + + if (pipeH == INVALID_HANDLE_VALUE) + pipeH = CreateFile (filename.toWideCharPointer(), + GENERIC_READ | GENERIC_WRITE, 0, 0, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + } + + if (pipeH != INVALID_HANDLE_VALUE) + return true; + + if (shouldStop || (timeOutMs >= 0 && Time::getCurrentTime() > timeOutEnd)) + return false; + + Thread::sleep (1); + } + } + + if (! connected) + { + OverlappedEvent over; + + if (ConnectNamedPipe (pipeH, &over.over) == 0) + { + switch (GetLastError()) + { + case ERROR_PIPE_CONNECTED: connected = true; break; + case ERROR_IO_PENDING: + case ERROR_PIPE_LISTENING: connected = waitForIO (over, timeOutMs); break; + default: break; + } + } + } + + return connected; + } + + void disconnectPipe() + { + if (ownsPipe && connected) + { + DisconnectNamedPipe (pipeH); + connected = false; + } + } + + int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds) + { + while (connect (timeOutMilliseconds)) + { + if (maxBytesToRead <= 0) + return 0; + + OverlappedEvent over; + unsigned long numRead; + + if (ReadFile (pipeH, destBuffer, (DWORD) maxBytesToRead, &numRead, &over.over)) + return (int) numRead; + + const DWORD lastError = GetLastError(); + + if (lastError == ERROR_IO_PENDING) + { + if (! waitForIO (over, timeOutMilliseconds)) + return -1; + + if (GetOverlappedResult (pipeH, &over.over, &numRead, FALSE)) + return (int) numRead; + } + + if (ownsPipe && (GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_PIPE_NOT_CONNECTED)) + disconnectPipe(); + else + break; + } + + return -1; + } + + int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) + { + if (connect (timeOutMilliseconds)) + { + if (numBytesToWrite <= 0) + return 0; + + OverlappedEvent over; + unsigned long numWritten; + + if (WriteFile (pipeH, sourceBuffer, (DWORD) numBytesToWrite, &numWritten, &over.over)) + return (int) numWritten; + + if (GetLastError() == ERROR_IO_PENDING) + { + if (! waitForIO (over, timeOutMilliseconds)) + return -1; + + if (GetOverlappedResult (pipeH, &over.over, &numWritten, FALSE)) + return (int) numWritten; + + if (GetLastError() == ERROR_BROKEN_PIPE && ownsPipe) + disconnectPipe(); + } + } + + return -1; + } + + const String filename; + HANDLE pipeH, cancelEvent; + bool connected, ownsPipe, shouldStop; + CriticalSection createFileLock; + +private: + struct OverlappedEvent + { + OverlappedEvent() + { + zerostruct (over); + over.hEvent = CreateEvent (0, TRUE, FALSE, 0); + } + + ~OverlappedEvent() + { + CloseHandle (over.hEvent); + } + + OVERLAPPED over; + }; + + bool waitForIO (OverlappedEvent& over, int timeOutMilliseconds) + { + if (shouldStop) + return false; + + HANDLE handles[] = { over.over.hEvent, cancelEvent }; + DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE, + timeOutMilliseconds >= 0 ? timeOutMilliseconds + : INFINITE); + + if (waitResult == WAIT_OBJECT_0) + return true; + + CancelIo (pipeH); + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +void NamedPipe::close() +{ + if (pimpl != nullptr) + { + pimpl->shouldStop = true; + SetEvent (pimpl->cancelEvent); + + ScopedWriteLock sl (lock); + pimpl = nullptr; + } +} + +bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) +{ + pimpl = new Pimpl (pipeName, createPipe); + + if (createPipe && pimpl->pipeH == INVALID_HANDLE_VALUE) + { + pimpl = nullptr; + return false; + } + + return true; +} + +int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->read (destBuffer, maxBytesToRead, timeOutMilliseconds) : -1; +} + +int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) +{ + ScopedReadLock sl (lock); + return pimpl != nullptr ? pimpl->write (sourceBuffer, numBytesToWrite, timeOutMilliseconds) : -1; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp new file mode 100644 index 0000000..f0b6061 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp @@ -0,0 +1,506 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef INTERNET_FLAG_NEED_FILE + #define INTERNET_FLAG_NEED_FILE 0x00000010 +#endif + +#ifndef INTERNET_OPTION_DISABLE_AUTODIAL + #define INTERNET_OPTION_DISABLE_AUTODIAL 70 +#endif + +//============================================================================== +class WebInputStream : public InputStream +{ +public: + WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, + URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, + const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) + : statusCode (0), connection (0), request (0), + address (address_), headers (headers_), postData (postData_), position (0), + finished (false), isPost (isPost_), timeOutMs (timeOutMs_) + { + for (int maxRedirects = 10; --maxRedirects >= 0;) + { + createConnection (progressCallback, progressCallbackContext); + + if (! isError()) + { + DWORD bufferSizeBytes = 4096; + StringPairArray headers (false); + + for (;;) + { + HeapBlock buffer ((size_t) bufferSizeBytes); + + if (HttpQueryInfo (request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer.getData(), &bufferSizeBytes, 0)) + { + StringArray headersArray; + headersArray.addLines (String (reinterpret_cast (buffer.getData()))); + + for (int i = 0; i < headersArray.size(); ++i) + { + const String& header = headersArray[i]; + const String key (header.upToFirstOccurrenceOf (": ", false, false)); + const String value (header.fromFirstOccurrenceOf (": ", false, false)); + const String previousValue (headers[key]); + headers.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); + } + + break; + } + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; + + bufferSizeBytes += 4096; + } + + DWORD status = 0; + DWORD statusSize = sizeof (status); + + if (HttpQueryInfo (request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statusSize, 0)) + { + statusCode = (int) status; + + if (status == 301 || status == 302 || status == 303 || status == 307) + { + const String newLocation (headers["Location"]); + + if (newLocation.isNotEmpty() && newLocation != address) + { + address = newLocation; + continue; + } + } + } + + if (responseHeaders != nullptr) + responseHeaders->addArray (headers); + } + + break; + } + } + + ~WebInputStream() + { + close(); + } + + //============================================================================== + bool isError() const { return request == 0; } + bool isExhausted() { return finished; } + int64 getPosition() { return position; } + + int64 getTotalLength() + { + if (! isError()) + { + DWORD index = 0, result = 0, size = sizeof (result); + + if (HttpQueryInfo (request, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &result, &size, &index)) + return (int64) result; + } + + return -1; + } + + int read (void* buffer, int bytesToRead) + { + jassert (buffer != nullptr && bytesToRead >= 0); + DWORD bytesRead = 0; + + if (! (finished || isError())) + { + InternetReadFile (request, buffer, (DWORD) bytesToRead, &bytesRead); + position += bytesRead; + + if (bytesRead == 0) + finished = true; + } + + return (int) bytesRead; + } + + bool setPosition (int64 wantedPos) + { + if (isError()) + return false; + + if (wantedPos != position) + { + finished = false; + position = (int64) InternetSetFilePointer (request, (LONG) wantedPos, 0, FILE_BEGIN, 0); + + if (position == wantedPos) + return true; + + if (wantedPos < position) + { + close(); + position = 0; + createConnection (0, 0); + } + + skipNextBytes (wantedPos - position); + } + + return true; + } + + int statusCode; + +private: + //============================================================================== + HINTERNET connection, request; + String address, headers; + MemoryBlock postData; + int64 position; + bool finished; + const bool isPost; + int timeOutMs; + + void close() + { + if (request != 0) + { + InternetCloseHandle (request); + request = 0; + } + + if (connection != 0) + { + InternetCloseHandle (connection); + connection = 0; + } + } + + void createConnection (URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + static HINTERNET sessionHandle = InternetOpen (_T("juce"), INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0); + + close(); + + if (sessionHandle != 0) + { + // break up the url.. + const int fileNumChars = 65536; + const int serverNumChars = 2048; + const int usernameNumChars = 1024; + const int passwordNumChars = 1024; + HeapBlock file (fileNumChars), server (serverNumChars), + username (usernameNumChars), password (passwordNumChars); + + URL_COMPONENTS uc = { 0 }; + uc.dwStructSize = sizeof (uc); + uc.lpszUrlPath = file; + uc.dwUrlPathLength = fileNumChars; + uc.lpszHostName = server; + uc.dwHostNameLength = serverNumChars; + uc.lpszUserName = username; + uc.dwUserNameLength = usernameNumChars; + uc.lpszPassword = password; + uc.dwPasswordLength = passwordNumChars; + + if (InternetCrackUrl (address.toWideCharPointer(), 0, 0, &uc)) + openConnection (uc, sessionHandle, progressCallback, progressCallbackContext); + } + } + + void openConnection (URL_COMPONENTS& uc, HINTERNET sessionHandle, + URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + int disable = 1; + InternetSetOption (sessionHandle, INTERNET_OPTION_DISABLE_AUTODIAL, &disable, sizeof (disable)); + + if (timeOutMs == 0) + timeOutMs = 30000; + else if (timeOutMs < 0) + timeOutMs = -1; + + applyTimeout (sessionHandle, INTERNET_OPTION_CONNECT_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_RECEIVE_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_SEND_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT); + applyTimeout (sessionHandle, INTERNET_OPTION_DATA_SEND_TIMEOUT); + + const bool isFtp = address.startsWithIgnoreCase ("ftp:"); + + connection = InternetConnect (sessionHandle, uc.lpszHostName, uc.nPort, + uc.lpszUserName, uc.lpszPassword, + isFtp ? (DWORD) INTERNET_SERVICE_FTP + : (DWORD) INTERNET_SERVICE_HTTP, + 0, 0); + if (connection != 0) + { + if (isFtp) + request = FtpOpenFile (connection, uc.lpszUrlPath, GENERIC_READ, + FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_NEED_FILE, 0); + else + openHTTPConnection (uc, progressCallback, progressCallbackContext); + } + } + + void applyTimeout (HINTERNET sessionHandle, const DWORD option) + { + InternetSetOption (sessionHandle, option, &timeOutMs, sizeof (timeOutMs)); + } + + void openHTTPConnection (URL_COMPONENTS& uc, URL::OpenStreamProgressCallback* progressCallback, + void* progressCallbackContext) + { + const TCHAR* mimeTypes[] = { _T("*/*"), nullptr }; + + DWORD flags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES + | INTERNET_FLAG_NO_AUTO_REDIRECT | SECURITY_SET_MASK; + + if (address.startsWithIgnoreCase ("https:")) + flags |= INTERNET_FLAG_SECURE; // (this flag only seems necessary if the OS is running IE6 - + // IE7 seems to automatically work out when it's https) + + request = HttpOpenRequest (connection, isPost ? _T("POST") : _T("GET"), + uc.lpszUrlPath, 0, 0, mimeTypes, flags, 0); + + if (request != 0) + { + setSecurityFlags(); + + INTERNET_BUFFERS buffers = { 0 }; + buffers.dwStructSize = sizeof (INTERNET_BUFFERS); + buffers.lpcszHeader = headers.toWideCharPointer(); + buffers.dwHeadersLength = (DWORD) headers.length(); + buffers.dwBufferTotal = (DWORD) postData.getSize(); + + if (HttpSendRequestEx (request, &buffers, 0, HSR_INITIATE, 0)) + { + int bytesSent = 0; + + for (;;) + { + const int bytesToDo = jmin (1024, (int) postData.getSize() - bytesSent); + DWORD bytesDone = 0; + + if (bytesToDo > 0 + && ! InternetWriteFile (request, + static_cast (postData.getData()) + bytesSent, + (DWORD) bytesToDo, &bytesDone)) + { + break; + } + + if (bytesToDo == 0 || (int) bytesDone < bytesToDo) + { + if (HttpEndRequest (request, 0, 0, 0)) + return; + + break; + } + + bytesSent += bytesDone; + + if (progressCallback != nullptr + && ! progressCallback (progressCallbackContext, bytesSent, (int) postData.getSize())) + break; + } + } + } + + close(); + } + + void setSecurityFlags() + { + DWORD dwFlags = 0, dwBuffLen = sizeof (DWORD); + InternetQueryOption (request, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBuffLen); + dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_SET_MASK; + InternetSetOption (request, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags)); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) +}; + + +//============================================================================== +struct GetAdaptersInfoHelper +{ + bool callGetAdaptersInfo() + { + DynamicLibrary dll ("iphlpapi.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, GetAdaptersInfo, getAdaptersInfo, DWORD, (PIP_ADAPTER_INFO, PULONG)) + + if (getAdaptersInfo == nullptr) + return false; + + adapterInfo.malloc (1); + ULONG len = sizeof (IP_ADAPTER_INFO); + + if (getAdaptersInfo (adapterInfo, &len) == ERROR_BUFFER_OVERFLOW) + adapterInfo.malloc (len, 1); + + return getAdaptersInfo (adapterInfo, &len) == NO_ERROR; + } + + HeapBlock adapterInfo; +}; + +namespace MACAddressHelpers +{ + static void addAddress (Array& result, const MACAddress& ma) + { + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); + } + + static void getViaGetAdaptersInfo (Array& result) + { + GetAdaptersInfoHelper gah; + + if (gah.callGetAdaptersInfo()) + { + for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) + if (adapter->AddressLength >= 6) + addAddress (result, MACAddress (adapter->Address)); + } + } + + static void getViaNetBios (Array& result) + { + DynamicLibrary dll ("netapi32.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, Netbios, NetbiosCall, UCHAR, (PNCB)) + + if (NetbiosCall != 0) + { + LANA_ENUM enums = { 0 }; + + { + NCB ncb = { 0 }; + ncb.ncb_command = NCBENUM; + ncb.ncb_buffer = (unsigned char*) &enums; + ncb.ncb_length = sizeof (LANA_ENUM); + NetbiosCall (&ncb); + } + + for (int i = 0; i < enums.length; ++i) + { + NCB ncb2 = { 0 }; + ncb2.ncb_command = NCBRESET; + ncb2.ncb_lana_num = enums.lana[i]; + + if (NetbiosCall (&ncb2) == 0) + { + NCB ncb = { 0 }; + memcpy (ncb.ncb_callname, "* ", NCBNAMSZ); + ncb.ncb_command = NCBASTAT; + ncb.ncb_lana_num = enums.lana[i]; + + struct ASTAT + { + ADAPTER_STATUS adapt; + NAME_BUFFER NameBuff [30]; + }; + + ASTAT astat; + zerostruct (astat); + ncb.ncb_buffer = (unsigned char*) &astat; + ncb.ncb_length = sizeof (ASTAT); + + if (NetbiosCall (&ncb) == 0 && astat.adapt.adapter_type == 0xfe) + addAddress (result, MACAddress (astat.adapt.adapter_address)); + } + } + } + } +} + +void MACAddress::findAllAddresses (Array& result) +{ + MACAddressHelpers::getViaGetAdaptersInfo (result); + MACAddressHelpers::getViaNetBios (result); +} + +void IPAddress::findAllAddresses (Array& result) +{ + result.addIfNotAlreadyThere (IPAddress::local()); + + GetAdaptersInfoHelper gah; + + if (gah.callGetAdaptersInfo()) + { + for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) + { + IPAddress ip (adapter->IpAddressList.IpAddress.String); + + if (ip != IPAddress::any()) + result.addIfNotAlreadyThere (ip); + } + } +} + +//============================================================================== +bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ + DynamicLibrary dll ("MAPI32.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, MAPISendMail, mapiSendMail, + ULONG, (LHANDLE, ULONG, lpMapiMessage, ::FLAGS, ULONG)) + + if (mapiSendMail == nullptr) + return false; + + MapiMessage message = { 0 }; + message.lpszSubject = (LPSTR) emailSubject.toRawUTF8(); + message.lpszNoteText = (LPSTR) bodyText.toRawUTF8(); + + MapiRecipDesc recip = { 0 }; + recip.ulRecipClass = MAPI_TO; + String targetEmailAddress_ (targetEmailAddress); + if (targetEmailAddress_.isEmpty()) + targetEmailAddress_ = " "; // (Windows Mail can't deal with a blank address) + recip.lpszName = (LPSTR) targetEmailAddress_.toRawUTF8(); + message.nRecipCount = 1; + message.lpRecips = &recip; + + HeapBlock files; + files.calloc ((size_t) filesToAttach.size()); + + message.nFileCount = (ULONG) filesToAttach.size(); + message.lpFiles = files; + + for (int i = 0; i < filesToAttach.size(); ++i) + { + files[i].nPosition = (ULONG) -1; + files[i].lpszPathName = (LPSTR) filesToAttach[i].toRawUTF8(); + } + + return mapiSendMail (0, 0, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0) == SUCCESS_SUCCESS; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Registry.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Registry.cpp new file mode 100644 index 0000000..8119474 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Registry.cpp @@ -0,0 +1,227 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +struct RegistryKeyWrapper +{ + RegistryKeyWrapper (String name, const bool createForWriting, const DWORD wow64Flags) + : key (0), wideCharValueName (nullptr) + { + HKEY rootKey = 0; + + if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) rootKey = HKEY_CURRENT_USER; + else if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) rootKey = HKEY_LOCAL_MACHINE; + else if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) rootKey = HKEY_CLASSES_ROOT; + + if (rootKey != 0) + { + name = name.substring (name.indexOfChar ('\\') + 1); + + const int lastSlash = name.lastIndexOfChar ('\\'); + valueName = name.substring (lastSlash + 1); + wideCharValueName = valueName.toWideCharPointer(); + + name = name.substring (0, lastSlash); + const wchar_t* const wideCharName = name.toWideCharPointer(); + DWORD result; + + if (createForWriting) + RegCreateKeyEx (rootKey, wideCharName, 0, 0, REG_OPTION_NON_VOLATILE, + KEY_WRITE | KEY_QUERY_VALUE | wow64Flags, 0, &key, &result); + else + RegOpenKeyEx (rootKey, wideCharName, 0, KEY_READ | wow64Flags, &key); + } + } + + ~RegistryKeyWrapper() + { + if (key != 0) + RegCloseKey (key); + } + + static bool setValue (const String& regValuePath, const DWORD type, + const void* data, size_t dataSize, const DWORD wow64Flags) + { + const RegistryKeyWrapper key (regValuePath, true, wow64Flags); + + return key.key != 0 + && RegSetValueEx (key.key, key.wideCharValueName, 0, type, + reinterpret_cast (data), + (DWORD) dataSize) == ERROR_SUCCESS; + } + + static uint32 getBinaryValue (const String& regValuePath, MemoryBlock& result, DWORD wow64Flags) + { + const RegistryKeyWrapper key (regValuePath, false, wow64Flags); + + if (key.key != 0) + { + for (unsigned long bufferSize = 1024; ; bufferSize *= 2) + { + result.setSize (bufferSize, false); + DWORD type = REG_NONE; + + const LONG err = RegQueryValueEx (key.key, key.wideCharValueName, 0, &type, + (LPBYTE) result.getData(), &bufferSize); + + if (err == ERROR_SUCCESS) + { + result.setSize (bufferSize, false); + return type; + } + + if (err != ERROR_MORE_DATA) + break; + } + } + + return REG_NONE; + } + + static String getValue (const String& regValuePath, const String& defaultValue, DWORD wow64Flags) + { + MemoryBlock buffer; + switch (getBinaryValue (regValuePath, buffer, wow64Flags)) + { + case REG_SZ: return static_cast (buffer.getData()); + case REG_DWORD: return String ((int) *reinterpret_cast (buffer.getData())); + default: break; + } + + return defaultValue; + } + + static bool keyExists (const String& regValuePath, const DWORD wow64Flags) + { + return RegistryKeyWrapper (regValuePath, false, wow64Flags).key != 0; + } + + static bool valueExists (const String& regValuePath, const DWORD wow64Flags) + { + const RegistryKeyWrapper key (regValuePath, false, wow64Flags); + + if (key.key == 0) + return false; + + unsigned char buffer [512]; + unsigned long bufferSize = sizeof (buffer); + DWORD type = 0; + + const LONG result = RegQueryValueEx (key.key, key.wideCharValueName, + 0, &type, buffer, &bufferSize); + + return result == ERROR_SUCCESS || result == ERROR_MORE_DATA; + } + + HKEY key; + const wchar_t* wideCharValueName; + String valueName; + + JUCE_DECLARE_NON_COPYABLE (RegistryKeyWrapper) +}; + +uint32 JUCE_CALLTYPE WindowsRegistry::getBinaryValue (const String& regValuePath, MemoryBlock& result, WoW64Mode mode) +{ + return RegistryKeyWrapper::getBinaryValue (regValuePath, result, (DWORD) mode); +} + +String JUCE_CALLTYPE WindowsRegistry::getValue (const String& regValuePath, const String& defaultValue, WoW64Mode mode) +{ + return RegistryKeyWrapper::getValue (regValuePath, defaultValue, (DWORD) mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::setValue (const String& regValuePath, const String& value, WoW64Mode mode) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_SZ, value.toWideCharPointer(), + CharPointer_UTF16::getBytesRequiredFor (value.getCharPointer()), mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::setValue (const String& regValuePath, const uint32 value, WoW64Mode mode) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_DWORD, &value, sizeof (value), (DWORD) mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::setValue (const String& regValuePath, const uint64 value, WoW64Mode mode) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_QWORD, &value, sizeof (value), (DWORD) mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::setValue (const String& regValuePath, const MemoryBlock& value, WoW64Mode mode) +{ + return RegistryKeyWrapper::setValue (regValuePath, REG_BINARY, value.getData(), value.getSize(), (DWORD) mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::valueExists (const String& regValuePath, WoW64Mode mode) +{ + return RegistryKeyWrapper::valueExists (regValuePath, (DWORD) mode); +} + +bool JUCE_CALLTYPE WindowsRegistry::keyExists (const String& regValuePath, WoW64Mode mode) +{ + return RegistryKeyWrapper::keyExists (regValuePath, (DWORD) mode); +} + +void JUCE_CALLTYPE WindowsRegistry::deleteValue (const String& regValuePath, WoW64Mode mode) +{ + const RegistryKeyWrapper key (regValuePath, true, (DWORD) mode); + + if (key.key != 0) + RegDeleteValue (key.key, key.wideCharValueName); +} + +void JUCE_CALLTYPE WindowsRegistry::deleteKey (const String& regKeyPath, WoW64Mode mode) +{ + const RegistryKeyWrapper key (regKeyPath, true, (DWORD) mode); + + if (key.key != 0) + RegDeleteKey (key.key, key.wideCharValueName); +} + +bool JUCE_CALLTYPE WindowsRegistry::registerFileAssociation (const String& fileExtension, + const String& symbolicDescription, + const String& fullDescription, + const File& targetExecutable, + const int iconResourceNumber, + const bool registerForCurrentUserOnly, + WoW64Mode mode) +{ + const char* const root = registerForCurrentUserOnly ? "HKEY_CURRENT_USER\\Software\\Classes\\" + : "HKEY_CLASSES_ROOT\\"; + const String key (root + symbolicDescription); + + return setValue (root + fileExtension + "\\", symbolicDescription, mode) + && setValue (key + "\\", fullDescription, mode) + && setValue (key + "\\shell\\open\\command\\", targetExecutable.getFullPathName() + " \"%1\"", mode) + && (iconResourceNumber == 0 + || setValue (key + "\\DefaultIcon\\", + targetExecutable.getFullPathName() + "," + String (iconResourceNumber))); +} + +// These methods are deprecated: +String WindowsRegistry::getValueWow64 (const String& p, const String& defVal) { return getValue (p, defVal, WoW64_64bit); } +bool WindowsRegistry::valueExistsWow64 (const String& p) { return valueExists (p, WoW64_64bit); } +bool WindowsRegistry::keyExistsWow64 (const String& p) { return keyExists (p, WoW64_64bit); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp new file mode 100644 index 0000000..d98e323 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -0,0 +1,459 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +void Logger::outputDebugString (const String& text) +{ + OutputDebugString ((text + "\n").toWideCharPointer()); +} + +//============================================================================== +#ifdef JUCE_DLL_BUILD + JUCE_API void* juceDLL_malloc (size_t sz) { return std::malloc (sz); } + JUCE_API void juceDLL_free (void* block) { std::free (block); } +#endif + +//============================================================================== +#if JUCE_USE_MSVC_INTRINSICS + +// CPU info functions using intrinsics... + +#pragma intrinsic (__cpuid) +#pragma intrinsic (__rdtsc) + +static void callCPUID (int result[4], int infoType) +{ + __cpuid (result, infoType); +} + +#else + +static void callCPUID (int result[4], int infoType) +{ + #if ! JUCE_MINGW + __try + #endif + { + #if JUCE_GCC + __asm__ __volatile__ ("cpuid" : "=a" (result[0]), "=b" (result[1]), "=c" (result[2]),"=d" (result[3]) : "a" (infoType)); + #else + __asm + { + mov esi, result + mov eax, infoType + xor ecx, ecx + cpuid + mov dword ptr [esi + 0], eax + mov dword ptr [esi + 4], ebx + mov dword ptr [esi + 8], ecx + mov dword ptr [esi + 12], edx + } + #endif + } + #if ! JUCE_MINGW + __except (EXCEPTION_EXECUTE_HANDLER) {} + #endif +} + +#endif + +String SystemStats::getCpuVendor() +{ + int info[4] = { 0 }; + callCPUID (info, 0); + + char v [12]; + memcpy (v, info + 1, 4); + memcpy (v + 4, info + 3, 4); + memcpy (v + 8, info + 2, 4); + + return String (v, 12); +} + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + int info[4] = { 0 }; + callCPUID (info, 1); + + // NB: IsProcessorFeaturePresent doesn't work on XP + hasMMX = (info[3] & (1 << 23)) != 0; + hasSSE = (info[3] & (1 << 25)) != 0; + hasSSE2 = (info[3] & (1 << 26)) != 0; + hasSSE3 = (info[2] & (1 << 0)) != 0; + has3DNow = (info[1] & (1 << 31)) != 0; + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + numCpus = (int) systemInfo.dwNumberOfProcessors; +} + +#if JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS +struct DebugFlagsInitialiser +{ + DebugFlagsInitialiser() + { + _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + } +}; + +static DebugFlagsInitialiser debugFlagsInitialiser; +#endif + +//============================================================================== +static bool isWindowsVersionOrLater (SystemStats::OperatingSystemType target) +{ + OSVERSIONINFOEX info; + zerostruct (info); + info.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEX); + + if (target >= SystemStats::WinVista) + { + info.dwMajorVersion = 6; + + switch (target) + { + case SystemStats::WinVista: break; + case SystemStats::Windows7: info.dwMinorVersion = 1; break; + case SystemStats::Windows8_0: info.dwMinorVersion = 2; break; + case SystemStats::Windows8_1: info.dwMinorVersion = 3; break; + default: jassertfalse; break; + } + } + else + { + info.dwMajorVersion = 5; + info.dwMinorVersion = target >= SystemStats::WinXP ? 1 : 0; + } + + DWORDLONG mask = 0; + + VER_SET_CONDITION (mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION (mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + return VerifyVersionInfo (&info, + VER_MAJORVERSION | VER_MINORVERSION + | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + mask) != FALSE; +} + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() +{ + const SystemStats::OperatingSystemType types[] + = { Windows8_1, Windows8_0, Windows7, WinVista, WinXP, Win2000 }; + + for (int i = 0; i < numElementsInArray (types); ++i) + if (isWindowsVersionOrLater (types[i])) + return types[i]; + + jassertfalse; // need to support whatever new version is running! + return UnknownOS; +} + +String SystemStats::getOperatingSystemName() +{ + const char* name = "Unknown OS"; + + switch (getOperatingSystemType()) + { + case Windows8_1: name = "Windows 8.1"; break; + case Windows8_0: name = "Windows 8.0"; break; + case Windows7: name = "Windows 7"; break; + case WinVista: name = "Windows Vista"; break; + case WinXP: name = "Windows XP"; break; + case Win2000: name = "Windows 2000"; break; + default: jassertfalse; break; // !! new type of OS? + } + + return name; +} + +String SystemStats::getDeviceDescription() +{ + return String(); +} + +bool SystemStats::isOperatingSystem64Bit() +{ + #if JUCE_64BIT + return true; + #else + typedef BOOL (WINAPI* LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); + + LPFN_ISWOW64PROCESS fnIsWow64Process + = (LPFN_ISWOW64PROCESS) GetProcAddress (GetModuleHandleA ("kernel32"), "IsWow64Process"); + + BOOL isWow64 = FALSE; + + return fnIsWow64Process != nullptr + && fnIsWow64Process (GetCurrentProcess(), &isWow64) + && isWow64 != FALSE; + #endif +} + +//============================================================================== +int SystemStats::getMemorySizeInMegabytes() +{ + MEMORYSTATUSEX mem; + mem.dwLength = sizeof (mem); + GlobalMemoryStatusEx (&mem); + return (int) (mem.ullTotalPhys / (1024 * 1024)) + 1; +} + +//============================================================================== +String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue) +{ + DWORD len = GetEnvironmentVariableW (name.toWideCharPointer(), nullptr, 0); + if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) + return String (defaultValue); + + HeapBlock buffer (len); + len = GetEnvironmentVariableW (name.toWideCharPointer(), buffer, len); + + return String (CharPointer_wchar_t (buffer), + CharPointer_wchar_t (buffer + len)); +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept +{ + return (uint32) timeGetTime(); +} + +//============================================================================== +class HiResCounterHandler +{ +public: + HiResCounterHandler() + : hiResTicksOffset (0) + { + // This macro allows you to override the default timer-period + // used on Windows. By default this is set to 1, because that has + // always been the value used in JUCE apps, and changing it could + // affect the behaviour of existing code, but you may wish to make + // it larger (or set it to 0 to use the system default) to make your + // app less demanding on the CPU. + // For more info, see win32 documentation about the timeBeginPeriod + // function. + #ifndef JUCE_WIN32_TIMER_PERIOD + #define JUCE_WIN32_TIMER_PERIOD 1 + #endif + + #if JUCE_WIN32_TIMER_PERIOD > 0 + const MMRESULT res = timeBeginPeriod (JUCE_WIN32_TIMER_PERIOD); + (void) res; + jassert (res == TIMERR_NOERROR); + #endif + + LARGE_INTEGER f; + QueryPerformanceFrequency (&f); + hiResTicksPerSecond = f.QuadPart; + hiResTicksScaleFactor = 1000.0 / hiResTicksPerSecond; + } + + inline int64 getHighResolutionTicks() noexcept + { + LARGE_INTEGER ticks; + QueryPerformanceCounter (&ticks); + + const int64 mainCounterAsHiResTicks = (juce_millisecondsSinceStartup() * hiResTicksPerSecond) / 1000; + const int64 newOffset = mainCounterAsHiResTicks - ticks.QuadPart; + + // fix for a very obscure PCI hardware bug that can make the counter + // sometimes jump forwards by a few seconds.. + const int64 offsetDrift = abs64 (newOffset - hiResTicksOffset); + + if (offsetDrift > (hiResTicksPerSecond >> 1)) + hiResTicksOffset = newOffset; + + return ticks.QuadPart + hiResTicksOffset; + } + + inline double getMillisecondCounterHiRes() noexcept + { + return getHighResolutionTicks() * hiResTicksScaleFactor; + } + + int64 hiResTicksPerSecond, hiResTicksOffset; + double hiResTicksScaleFactor; +}; + +static HiResCounterHandler hiResCounterHandler; + +int64 Time::getHighResolutionTicksPerSecond() noexcept { return hiResCounterHandler.hiResTicksPerSecond; } +int64 Time::getHighResolutionTicks() noexcept { return hiResCounterHandler.getHighResolutionTicks(); } +double Time::getMillisecondCounterHiRes() noexcept { return hiResCounterHandler.getMillisecondCounterHiRes(); } + +//============================================================================== +static int64 juce_getClockCycleCounter() noexcept +{ + #if JUCE_USE_MSVC_INTRINSICS + // MS intrinsics version... + return (int64) __rdtsc(); + + #elif JUCE_GCC + // GNU inline asm version... + unsigned int hi = 0, lo = 0; + + __asm__ __volatile__ ( + "xor %%eax, %%eax \n\ + xor %%edx, %%edx \n\ + rdtsc \n\ + movl %%eax, %[lo] \n\ + movl %%edx, %[hi]" + : + : [hi] "m" (hi), + [lo] "m" (lo) + : "cc", "eax", "ebx", "ecx", "edx", "memory"); + + return (int64) ((((uint64) hi) << 32) | lo); + #else + // MSVC inline asm version... + unsigned int hi = 0, lo = 0; + + __asm + { + xor eax, eax + xor edx, edx + rdtsc + mov lo, eax + mov hi, edx + } + + return (int64) ((((uint64) hi) << 32) | lo); + #endif +} + +int SystemStats::getCpuSpeedInMegaherz() +{ + const int64 cycles = juce_getClockCycleCounter(); + const uint32 millis = Time::getMillisecondCounter(); + int lastResult = 0; + + for (;;) + { + int n = 1000000; + while (--n > 0) {} + + const uint32 millisElapsed = Time::getMillisecondCounter() - millis; + const int64 cyclesNow = juce_getClockCycleCounter(); + + if (millisElapsed > 80) + { + const int newResult = (int) (((cyclesNow - cycles) / millisElapsed) / 1000); + + if (millisElapsed > 500 || (lastResult == newResult && newResult > 100)) + return newResult; + + lastResult = newResult; + } + } +} + + +//============================================================================== +bool Time::setSystemTimeToThisTime() const +{ + SYSTEMTIME st; + + st.wDayOfWeek = 0; + st.wYear = (WORD) getYear(); + st.wMonth = (WORD) (getMonth() + 1); + st.wDay = (WORD) getDayOfMonth(); + st.wHour = (WORD) getHours(); + st.wMinute = (WORD) getMinutes(); + st.wSecond = (WORD) getSeconds(); + st.wMilliseconds = (WORD) (millisSinceEpoch % 1000); + + // do this twice because of daylight saving conversion problems - the + // first one sets it up, the second one kicks it in. + return SetLocalTime (&st) != 0 + && SetLocalTime (&st) != 0; +} + +int SystemStats::getPageSize() +{ + SYSTEM_INFO systemInfo; + GetNativeSystemInfo (&systemInfo); + + return (int) systemInfo.dwPageSize; +} + +//============================================================================== +String SystemStats::getLogonName() +{ + TCHAR text [256] = { 0 }; + DWORD len = (DWORD) numElementsInArray (text) - 1; + GetUserName (text, &len); + return String (text, len); +} + +String SystemStats::getFullUserName() +{ + return getLogonName(); +} + +String SystemStats::getComputerName() +{ + TCHAR text [MAX_COMPUTERNAME_LENGTH + 1] = { 0 }; + DWORD len = (DWORD) numElementsInArray (text) - 1; + GetComputerName (text, &len); + return String (text, len); +} + +static String getLocaleValue (LCID locale, LCTYPE key, const char* defaultValue) +{ + TCHAR buffer [256] = { 0 }; + if (GetLocaleInfo (locale, key, buffer, 255) > 0) + return buffer; + + return defaultValue; +} + +String SystemStats::getUserLanguage() { return getLocaleValue (LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, "en"); } +String SystemStats::getUserRegion() { return getLocaleValue (LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, "US"); } + +String SystemStats::getDisplayLanguage() +{ + DynamicLibrary dll ("kernel32.dll"); + JUCE_LOAD_WINAPI_FUNCTION (dll, GetUserDefaultUILanguage, getUserDefaultUILanguage, LANGID, (void)) + + if (getUserDefaultUILanguage == nullptr) + return "en"; + + const DWORD langID = MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT); + + String mainLang (getLocaleValue (langID, LOCALE_SISO639LANGNAME, "en")); + String region (getLocaleValue (langID, LOCALE_SISO3166CTRYNAME, nullptr)); + + if (region.isNotEmpty()) + mainLang << '-' << region; + + return mainLang; +} diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp new file mode 100644 index 0000000..1421c10 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp @@ -0,0 +1,625 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +HWND juce_messageWindowHandle = 0; // (this is used by other parts of the codebase) + +void* getUser32Function (const char* functionName) +{ + HMODULE module = GetModuleHandleA ("user32.dll"); + jassert (module != 0); + return (void*) GetProcAddress (module, functionName); +} + +//============================================================================== +#if ! JUCE_USE_MSVC_INTRINSICS +// In newer compilers, the inline versions of these are used (in juce_Atomic.h), but in +// older ones we have to actually call the ops as win32 functions.. +long juce_InterlockedExchange (volatile long* a, long b) noexcept { return InterlockedExchange (a, b); } +long juce_InterlockedIncrement (volatile long* a) noexcept { return InterlockedIncrement (a); } +long juce_InterlockedDecrement (volatile long* a) noexcept { return InterlockedDecrement (a); } +long juce_InterlockedExchangeAdd (volatile long* a, long b) noexcept { return InterlockedExchangeAdd (a, b); } +long juce_InterlockedCompareExchange (volatile long* a, long b, long c) noexcept { return InterlockedCompareExchange (a, b, c); } + +__int64 juce_InterlockedCompareExchange64 (volatile __int64* value, __int64 newValue, __int64 valueToCompare) noexcept +{ + jassertfalse; // This operation isn't available in old MS compiler versions! + + __int64 oldValue = *value; + if (oldValue == valueToCompare) + *value = newValue; + + return oldValue; +} + +#endif + +//============================================================================== +CriticalSection::CriticalSection() noexcept +{ + // (just to check the MS haven't changed this structure and broken things...) + #if JUCE_VC7_OR_EARLIER + static_jassert (sizeof (CRITICAL_SECTION) <= 24); + #else + static_jassert (sizeof (CRITICAL_SECTION) <= sizeof (lock)); + #endif + + InitializeCriticalSection ((CRITICAL_SECTION*) lock); +} + +CriticalSection::~CriticalSection() noexcept { DeleteCriticalSection ((CRITICAL_SECTION*) lock); } +void CriticalSection::enter() const noexcept { EnterCriticalSection ((CRITICAL_SECTION*) lock); } +bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) lock) != FALSE; } +void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) lock); } + + +//============================================================================== +WaitableEvent::WaitableEvent (const bool manualReset) noexcept + : handle (CreateEvent (0, manualReset ? TRUE : FALSE, FALSE, 0)) {} + +WaitableEvent::~WaitableEvent() noexcept { CloseHandle (handle); } + +void WaitableEvent::signal() const noexcept { SetEvent (handle); } +void WaitableEvent::reset() const noexcept { ResetEvent (handle); } + +bool WaitableEvent::wait (const int timeOutMs) const noexcept +{ + return WaitForSingleObject (handle, (DWORD) timeOutMs) == WAIT_OBJECT_0; +} + +//============================================================================== +void JUCE_API juce_threadEntryPoint (void*); + +static unsigned int __stdcall threadEntryProc (void* userData) +{ + if (juce_messageWindowHandle != 0) + AttachThreadInput (GetWindowThreadProcessId (juce_messageWindowHandle, 0), + GetCurrentThreadId(), TRUE); + + juce_threadEntryPoint (userData); + + _endthreadex (0); + return 0; +} + +void Thread::launchThread() +{ + unsigned int newThreadId; + threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); + threadId = (ThreadID) newThreadId; +} + +void Thread::closeThreadHandle() +{ + CloseHandle ((HANDLE) threadHandle); + threadId = 0; + threadHandle = 0; +} + +void Thread::killThread() +{ + if (threadHandle != 0) + { + #if JUCE_DEBUG + OutputDebugStringA ("** Warning - Forced thread termination **\n"); + #endif + TerminateThread (threadHandle, 0); + } +} + +void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) +{ + #if JUCE_DEBUG && JUCE_MSVC + struct + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + } info; + + info.dwType = 0x1000; + info.szName = name.toUTF8(); + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; + + __try + { + RaiseException (0x406d1388 /*MS_VC_EXCEPTION*/, 0, sizeof (info) / sizeof (ULONG_PTR), (ULONG_PTR*) &info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + {} + #else + (void) name; + #endif +} + +Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() +{ + return (ThreadID) (pointer_sized_int) GetCurrentThreadId(); +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + int pri = THREAD_PRIORITY_TIME_CRITICAL; + + if (priority < 1) pri = THREAD_PRIORITY_IDLE; + else if (priority < 2) pri = THREAD_PRIORITY_LOWEST; + else if (priority < 5) pri = THREAD_PRIORITY_BELOW_NORMAL; + else if (priority < 7) pri = THREAD_PRIORITY_NORMAL; + else if (priority < 9) pri = THREAD_PRIORITY_ABOVE_NORMAL; + else if (priority < 10) pri = THREAD_PRIORITY_HIGHEST; + + if (handle == 0) + handle = GetCurrentThread(); + + return SetThreadPriority (handle, pri) != FALSE; +} + +void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) +{ + SetThreadAffinityMask (GetCurrentThread(), affinityMask); +} + +//============================================================================== +struct SleepEvent +{ + SleepEvent() noexcept + : handle (CreateEvent (nullptr, FALSE, FALSE, + #if JUCE_DEBUG + _T("JUCE Sleep Event"))) + #else + nullptr)) + #endif + {} + + ~SleepEvent() noexcept + { + CloseHandle (handle); + handle = 0; + } + + HANDLE handle; +}; + +static SleepEvent sleepEvent; + +void JUCE_CALLTYPE Thread::sleep (const int millisecs) +{ + jassert (millisecs >= 0); + + if (millisecs >= 10 || sleepEvent.handle == 0) + Sleep ((DWORD) millisecs); + else + // unlike Sleep() this is guaranteed to return to the current thread after + // the time expires, so we'll use this for short waits, which are more likely + // to need to be accurate + WaitForSingleObject (sleepEvent.handle, (DWORD) millisecs); +} + +void Thread::yield() +{ + Sleep (0); +} + +//============================================================================== +static int lastProcessPriority = -1; + +// called when the app gains focus because Windows does weird things to process priority +// when you swap apps, and this forces an update when the app is brought to the front. +void juce_repeatLastProcessPriority() +{ + if (lastProcessPriority >= 0) // (avoid changing this if it's not been explicitly set by the app..) + { + DWORD p; + + switch (lastProcessPriority) + { + case Process::LowPriority: p = IDLE_PRIORITY_CLASS; break; + case Process::NormalPriority: p = NORMAL_PRIORITY_CLASS; break; + case Process::HighPriority: p = HIGH_PRIORITY_CLASS; break; + case Process::RealtimePriority: p = REALTIME_PRIORITY_CLASS; break; + default: jassertfalse; return; // bad priority value + } + + SetPriorityClass (GetCurrentProcess(), p); + } +} + +void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) +{ + if (lastProcessPriority != (int) prior) + { + lastProcessPriority = (int) prior; + juce_repeatLastProcessPriority(); + } +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() +{ + return IsDebuggerPresent() != FALSE; +} + +bool JUCE_CALLTYPE Process::isRunningUnderDebugger() +{ + return juce_isRunningUnderDebugger(); +} + +static void* currentModuleHandle = nullptr; + +void* JUCE_CALLTYPE Process::getCurrentModuleInstanceHandle() noexcept +{ + if (currentModuleHandle == nullptr) + currentModuleHandle = GetModuleHandleA (nullptr); + + return currentModuleHandle; +} + +void JUCE_CALLTYPE Process::setCurrentModuleInstanceHandle (void* const newHandle) noexcept +{ + currentModuleHandle = newHandle; +} + +void JUCE_CALLTYPE Process::raisePrivilege() +{ + jassertfalse; // xxx not implemented +} + +void JUCE_CALLTYPE Process::lowerPrivilege() +{ + jassertfalse; // xxx not implemented +} + +void JUCE_CALLTYPE Process::terminate() +{ + #if JUCE_MSVC && JUCE_CHECK_MEMORY_LEAKS + _CrtDumpMemoryLeaks(); + #endif + + // bullet in the head in case there's a problem shutting down.. + ExitProcess (1); +} + +bool juce_isRunningInWine() +{ + HMODULE ntdll = GetModuleHandleA ("ntdll"); + return ntdll != 0 && GetProcAddress (ntdll, "wine_get_version") != nullptr; +} + +//============================================================================== +bool DynamicLibrary::open (const String& name) +{ + close(); + + JUCE_TRY + { + handle = LoadLibrary (name.toWideCharPointer()); + } + JUCE_CATCH_ALL + + return handle != nullptr; +} + +void DynamicLibrary::close() +{ + JUCE_TRY + { + if (handle != nullptr) + { + FreeLibrary ((HMODULE) handle); + handle = nullptr; + } + } + JUCE_CATCH_ALL +} + +void* DynamicLibrary::getFunction (const String& functionName) noexcept +{ + return handle != nullptr ? (void*) GetProcAddress ((HMODULE) handle, functionName.toUTF8()) // (void* cast is required for mingw) + : nullptr; +} + + +//============================================================================== +class InterProcessLock::Pimpl +{ +public: + Pimpl (String name, const int timeOutMillisecs) + : handle (0), refCount (1) + { + name = name.replaceCharacter ('\\', '/'); + handle = CreateMutexW (0, TRUE, ("Global\\" + name).toWideCharPointer()); + + // Not 100% sure why a global mutex sometimes can't be allocated, but if it fails, fall back to + // a local one. (A local one also sometimes fails on other machines so neither type appears to be + // universally reliable) + if (handle == 0) + handle = CreateMutexW (0, TRUE, ("Local\\" + name).toWideCharPointer()); + + if (handle != 0 && GetLastError() == ERROR_ALREADY_EXISTS) + { + if (timeOutMillisecs == 0) + { + close(); + return; + } + + switch (WaitForSingleObject (handle, timeOutMillisecs < 0 ? INFINITE : timeOutMillisecs)) + { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + break; + + case WAIT_TIMEOUT: + default: + close(); + break; + } + } + } + + ~Pimpl() + { + close(); + } + + void close() + { + if (handle != 0) + { + ReleaseMutex (handle); + CloseHandle (handle); + handle = 0; + } + } + + HANDLE handle; + int refCount; +}; + +InterProcessLock::InterProcessLock (const String& name_) + : name (name_) +{ +} + +InterProcessLock::~InterProcessLock() +{ +} + +bool InterProcessLock::enter (const int timeOutMillisecs) +{ + const ScopedLock sl (lock); + + if (pimpl == nullptr) + { + pimpl = new Pimpl (name, timeOutMillisecs); + + if (pimpl->handle == 0) + pimpl = nullptr; + } + else + { + pimpl->refCount++; + } + + return pimpl != nullptr; +} + +void InterProcessLock::exit() +{ + const ScopedLock sl (lock); + + // Trying to release the lock too many times! + jassert (pimpl != nullptr); + + if (pimpl != nullptr && --(pimpl->refCount) == 0) + pimpl = nullptr; +} + +//============================================================================== +class ChildProcess::ActiveProcess +{ +public: + ActiveProcess (const String& command, int streamFlags) + : ok (false), readPipe (0), writePipe (0) + { + SECURITY_ATTRIBUTES securityAtts = { 0 }; + securityAtts.nLength = sizeof (securityAtts); + securityAtts.bInheritHandle = TRUE; + + if (CreatePipe (&readPipe, &writePipe, &securityAtts, 0) + && SetHandleInformation (readPipe, HANDLE_FLAG_INHERIT, 0)) + { + STARTUPINFOW startupInfo = { 0 }; + startupInfo.cb = sizeof (startupInfo); + + startupInfo.hStdOutput = (streamFlags | wantStdOut) != 0 ? writePipe : 0; + startupInfo.hStdError = (streamFlags | wantStdErr) != 0 ? writePipe : 0; + startupInfo.dwFlags = STARTF_USESTDHANDLES; + + ok = CreateProcess (nullptr, const_cast (command.toWideCharPointer()), + nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + nullptr, nullptr, &startupInfo, &processInfo) != FALSE; + } + } + + ~ActiveProcess() + { + if (ok) + { + CloseHandle (processInfo.hThread); + CloseHandle (processInfo.hProcess); + } + + if (readPipe != 0) + CloseHandle (readPipe); + + if (writePipe != 0) + CloseHandle (writePipe); + } + + bool isRunning() const noexcept + { + return WaitForSingleObject (processInfo.hProcess, 0) != WAIT_OBJECT_0; + } + + int read (void* dest, int numNeeded) const noexcept + { + int total = 0; + + while (ok && numNeeded > 0) + { + DWORD available = 0; + + if (! PeekNamedPipe ((HANDLE) readPipe, nullptr, 0, nullptr, &available, nullptr)) + break; + + const int numToDo = jmin ((int) available, numNeeded); + + if (available == 0) + { + if (! isRunning()) + break; + + Thread::yield(); + } + else + { + DWORD numRead = 0; + if (! ReadFile ((HANDLE) readPipe, dest, numToDo, &numRead, nullptr)) + break; + + total += numRead; + dest = addBytesToPointer (dest, numRead); + numNeeded -= numRead; + } + } + + return total; + } + + bool killProcess() const noexcept + { + return TerminateProcess (processInfo.hProcess, 0) != FALSE; + } + + uint32 getExitCode() const noexcept + { + DWORD exitCode = 0; + GetExitCodeProcess (processInfo.hProcess, &exitCode); + return (uint32) exitCode; + } + + bool ok; + +private: + HANDLE readPipe, writePipe; + PROCESS_INFORMATION processInfo; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveProcess) +}; + +bool ChildProcess::start (const String& command, int streamFlags) +{ + activeProcess = new ActiveProcess (command, streamFlags); + + if (! activeProcess->ok) + activeProcess = nullptr; + + return activeProcess != nullptr; +} + +bool ChildProcess::start (const StringArray& args, int streamFlags) +{ + String escaped; + + for (int i = 0; i < args.size(); ++i) + { + String arg (args[i]); + + // If there are spaces, surround it with quotes. If there are quotes, + // replace them with \" so that CommandLineToArgv will correctly parse them. + if (arg.containsAnyOf ("\" ")) + arg = arg.replace ("\"", "\\\"").quoted(); + + escaped << arg << ' '; + } + + return start (escaped.trim(), streamFlags); +} + +//============================================================================== +struct HighResolutionTimer::Pimpl +{ + Pimpl (HighResolutionTimer& t) noexcept : owner (t), periodMs (0) + { + } + + ~Pimpl() + { + jassert (periodMs == 0); + } + + void start (int newPeriod) + { + if (newPeriod != periodMs) + { + stop(); + periodMs = newPeriod; + + TIMECAPS tc; + if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR) + { + const int actualPeriod = jlimit ((int) tc.wPeriodMin, (int) tc.wPeriodMax, newPeriod); + + timerID = timeSetEvent (actualPeriod, tc.wPeriodMin, callbackFunction, (DWORD_PTR) this, + TIME_PERIODIC | TIME_CALLBACK_FUNCTION | 0x100 /*TIME_KILL_SYNCHRONOUS*/); + } + } + } + + void stop() + { + periodMs = 0; + timeKillEvent (timerID); + } + + HighResolutionTimer& owner; + int periodMs; + +private: + unsigned int timerID; + + static void __stdcall callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) + { + if (Pimpl* const timer = reinterpret_cast (userInfo)) + if (timer->periodMs != 0) + timer->owner.hiResTimerCallback(); + } + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp new file mode 100644 index 0000000..6c4d087 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.cpp @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +IPAddress::IPAddress() noexcept +{ + address[0] = 0; address[1] = 0; + address[2] = 0; address[3] = 0; +} + +IPAddress::IPAddress (const uint8 bytes[4]) noexcept +{ + address[0] = bytes[0]; address[1] = bytes[1]; + address[2] = bytes[2]; address[3] = bytes[3]; +} + +IPAddress::IPAddress (uint8 a0, uint8 a1, uint8 a2, uint8 a3) noexcept +{ + address[0] = a0; address[1] = a1; + address[2] = a2; address[3] = a3; +} + +IPAddress::IPAddress (uint32 n) noexcept +{ + address[0] = (n >> 24); + address[1] = (n >> 16) & 255; + address[2] = (n >> 8) & 255; + address[3] = (n & 255); +} + +IPAddress::IPAddress (const String& adr) +{ + StringArray tokens; + tokens.addTokens (adr, ".", String()); + + for (int i = 0; i < 4; ++i) + address[i] = (uint8) tokens[i].getIntValue(); +} + +String IPAddress::toString() const +{ + String s ((int) address[0]); + + for (int i = 1; i < 4; ++i) + s << '.' << (int) address[i]; + + return s; +} + +IPAddress IPAddress::any() noexcept { return IPAddress(); } +IPAddress IPAddress::broadcast() noexcept { return IPAddress (255, 255, 255, 255); } +IPAddress IPAddress::local() noexcept { return IPAddress (127, 0, 0, 1); } + +bool IPAddress::operator== (const IPAddress& other) const noexcept +{ + return address[0] == other.address[0] + && address[1] == other.address[1] + && address[2] == other.address[2] + && address[3] == other.address[3]; +} + +bool IPAddress::operator!= (const IPAddress& other) const noexcept +{ + return ! operator== (other); +} + +#if ! JUCE_WINDOWS +static void addAddress (const sockaddr_in* addr_in, Array& result) +{ + in_addr_t addr = addr_in->sin_addr.s_addr; + + if (addr != INADDR_NONE) + result.addIfNotAlreadyThere (IPAddress (ntohl (addr))); +} + +static void findIPAddresses (int sock, Array& result) +{ + ifconf cfg; + HeapBlock buffer; + int bufferSize = 1024; + + do + { + bufferSize *= 2; + buffer.calloc ((size_t) bufferSize); + + cfg.ifc_len = bufferSize; + cfg.ifc_buf = buffer; + + if (ioctl (sock, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL) + return; + + } while (bufferSize < cfg.ifc_len + 2 * (int) (IFNAMSIZ + sizeof (struct sockaddr_in6))); + + #if JUCE_MAC || JUCE_IOS + while (cfg.ifc_len >= (int) (IFNAMSIZ + sizeof (struct sockaddr_in))) + { + if (cfg.ifc_req->ifr_addr.sa_family == AF_INET) // Skip non-internet addresses + addAddress ((const sockaddr_in*) &cfg.ifc_req->ifr_addr, result); + + cfg.ifc_len -= IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len; + cfg.ifc_buf += IFNAMSIZ + cfg.ifc_req->ifr_addr.sa_len; + } + #else + for (size_t i = 0; i < cfg.ifc_len / sizeof (struct ifreq); ++i) + { + const ifreq& item = cfg.ifc_req[i]; + + if (item.ifr_addr.sa_family == AF_INET) + addAddress ((const sockaddr_in*) &item.ifr_addr, result); + } + #endif +} + +void IPAddress::findAllAddresses (Array& result) +{ + const int sock = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control + + if (sock >= 0) + { + findIPAddresses (sock, result); + ::close (sock); + } +} +#endif diff --git a/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.h b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.h new file mode 100644 index 0000000..1f2f0e8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_IPAddress.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_IPADDRESS_H_INCLUDED +#define JUCE_IPADDRESS_H_INCLUDED + + +//============================================================================== +/** + An IPV4 address. +*/ +class JUCE_API IPAddress +{ +public: + //============================================================================== + /** Populates a list of all the IP addresses that this machine is using. */ + static void findAllAddresses (Array& results); + + //============================================================================== + /** Creates a null address (0.0.0.0). */ + IPAddress() noexcept; + + /** Creates an address from 4 bytes. */ + explicit IPAddress (const uint8 bytes[4]) noexcept; + + /** Creates an address from 4 bytes. */ + IPAddress (uint8 address1, uint8 address2, uint8 address3, uint8 address4) noexcept; + + /** Creates an address from a packed 32-bit integer, where the MSB is + the first number in the address, and the LSB is the last. + */ + explicit IPAddress (uint32 asNativeEndian32Bit) noexcept; + + /** Parses a string IP address of the form "a.b.c.d". */ + explicit IPAddress (const String& address); + + /** Returns a dot-separated string in the form "1.2.3.4" */ + String toString() const; + + /** Returns an address meaning "any" (0.0.0.0) */ + static IPAddress any() noexcept; + + /** Returns an address meaning "broadcast" (255.255.255.255) */ + static IPAddress broadcast() noexcept; + + /** Returns an address meaning "localhost" (127.0.0.1) */ + static IPAddress local() noexcept; + + bool operator== (const IPAddress& other) const noexcept; + bool operator!= (const IPAddress& other) const noexcept; + + /** The elements of the IP address. */ + uint8 address[4]; +}; + + +#endif // JUCE_IPADDRESS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.cpp b/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.cpp new file mode 100644 index 0000000..b7cb3c1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.cpp @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +MACAddress::MACAddress() +{ + zeromem (address, sizeof (address)); +} + +MACAddress::MACAddress (const MACAddress& other) +{ + memcpy (address, other.address, sizeof (address)); +} + +MACAddress& MACAddress::operator= (const MACAddress& other) +{ + memcpy (address, other.address, sizeof (address)); + return *this; +} + +MACAddress::MACAddress (const uint8 bytes[6]) +{ + memcpy (address, bytes, sizeof (address)); +} + +String MACAddress::toString() const +{ + String s; + + for (size_t i = 0; i < sizeof (address); ++i) + { + s << String::toHexString ((int) address[i]).paddedLeft ('0', 2); + + if (i < sizeof (address) - 1) + s << '-'; + } + + return s; +} + +int64 MACAddress::toInt64() const noexcept +{ + int64 n = 0; + + for (int i = (int) sizeof (address); --i >= 0;) + n = (n << 8) | address[i]; + + return n; +} + +bool MACAddress::isNull() const noexcept { return toInt64() == 0; } + +bool MACAddress::operator== (const MACAddress& other) const noexcept { return memcmp (address, other.address, sizeof (address)) == 0; } +bool MACAddress::operator!= (const MACAddress& other) const noexcept { return ! operator== (other); } diff --git a/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.h b/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.h new file mode 100644 index 0000000..67e119e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_MACAddress.h @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MACADDRESS_H_INCLUDED +#define JUCE_MACADDRESS_H_INCLUDED + + +//============================================================================== +/** + Represents a MAC network card adapter address ID. +*/ +class JUCE_API MACAddress +{ +public: + //============================================================================== + /** Populates a list of the MAC addresses of all the available network cards. */ + static void findAllAddresses (Array& results); + + //============================================================================== + /** Creates a null address (00-00-00-00-00-00). */ + MACAddress(); + + /** Creates a copy of another address. */ + MACAddress (const MACAddress&); + + /** Creates a copy of another address. */ + MACAddress& operator= (const MACAddress&); + + /** Creates an address from 6 bytes. */ + explicit MACAddress (const uint8 bytes[6]); + + /** Returns a pointer to the 6 bytes that make up this address. */ + const uint8* getBytes() const noexcept { return address; } + + /** Returns a dash-separated string in the form "11-22-33-44-55-66" */ + String toString() const; + + /** Returns the address in the lower 6 bytes of an int64. + + This uses a little-endian arrangement, with the first byte of the address being + stored in the least-significant byte of the result value. + */ + int64 toInt64() const noexcept; + + /** Returns true if this address is null (00-00-00-00-00-00). */ + bool isNull() const noexcept; + + bool operator== (const MACAddress&) const noexcept; + bool operator!= (const MACAddress&) const noexcept; + + //============================================================================== +private: + uint8 address[6]; +}; + + +#endif // JUCE_MACADDRESS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp new file mode 100644 index 0000000..488e63f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp @@ -0,0 +1,66 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +NamedPipe::NamedPipe() +{ +} + +NamedPipe::~NamedPipe() +{ + close(); +} + +bool NamedPipe::openExisting (const String& pipeName) +{ + close(); + + ScopedWriteLock sl (lock); + currentPipeName = pipeName; + return openInternal (pipeName, false); +} + +bool NamedPipe::isOpen() const +{ + return pimpl != nullptr; +} + +bool NamedPipe::createNewPipe (const String& pipeName) +{ + close(); + + ScopedWriteLock sl (lock); + currentPipeName = pipeName; + return openInternal (pipeName, true); +} + +String NamedPipe::getName() const +{ + return currentPipeName; +} + +// other methods for this class are implemented in the platform-specific files diff --git a/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.h b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.h new file mode 100644 index 0000000..d2029bd --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.h @@ -0,0 +1,104 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NAMEDPIPE_H_INCLUDED +#define JUCE_NAMEDPIPE_H_INCLUDED + + +//============================================================================== +/** + A cross-process pipe that can have data written to and read from it. + + Two processes can use NamedPipe objects to exchange blocks of data. + + @see InterprocessConnection +*/ +class JUCE_API NamedPipe +{ +public: + //============================================================================== + /** Creates a NamedPipe. */ + NamedPipe(); + + /** Destructor. */ + ~NamedPipe(); + + //============================================================================== + /** Tries to open a pipe that already exists. + Returns true if it succeeds. + */ + bool openExisting (const String& pipeName); + + /** Tries to create a new pipe. + Returns true if it succeeds. + */ + bool createNewPipe (const String& pipeName); + + /** Closes the pipe, if it's open. */ + void close(); + + /** True if the pipe is currently open. */ + bool isOpen() const; + + /** Returns the last name that was used to try to open this pipe. */ + String getName() const; + + //============================================================================== + /** Reads data from the pipe. + + This will block until another thread has written enough data into the pipe to fill + the number of bytes specified, or until another thread calls the cancelPendingReads() + method. + + If the operation fails, it returns -1, otherwise, it will return the number of + bytes read. + + If timeOutMilliseconds is less than zero, it will wait indefinitely, otherwise + this is a maximum timeout for reading from the pipe. + */ + int read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds); + + /** Writes some data to the pipe. + @returns the number of bytes written, or -1 on failure. + */ + int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds); + +private: + //============================================================================== + JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl) + ScopedPointer pimpl; + String currentPipeName; + ReadWriteLock lock; + + bool openInternal (const String& pipeName, const bool createPipe); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NamedPipe) +}; + + +#endif // JUCE_NAMEDPIPE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp b/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp new file mode 100644 index 0000000..291b580 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_Socket.cpp @@ -0,0 +1,604 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable : 4127 4389 4018) +#endif + +#ifndef AI_NUMERICSERV // (missing in older Mac SDKs) + #define AI_NUMERICSERV 0x1000 +#endif + +#if JUCE_WINDOWS + typedef int juce_socklen_t; + typedef SOCKET SocketHandle; +#else + typedef socklen_t juce_socklen_t; + typedef int SocketHandle; +#endif + +//============================================================================== +namespace SocketHelpers +{ + static void initSockets() + { + #if JUCE_WINDOWS + static bool socketsStarted = false; + + if (! socketsStarted) + { + socketsStarted = true; + + WSADATA wsaData; + const WORD wVersionRequested = MAKEWORD (1, 1); + WSAStartup (wVersionRequested, &wsaData); + } + #endif + } + + static bool resetSocketOptions (const SocketHandle handle, const bool isDatagram, const bool allowBroadcast) noexcept + { + const int sndBufSize = 65536; + const int rcvBufSize = 65536; + const int one = 1; + + return handle > 0 + && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0 + && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*) &sndBufSize, sizeof (sndBufSize)) == 0 + && (isDatagram ? ((! allowBroadcast) || setsockopt (handle, SOL_SOCKET, SO_BROADCAST, (const char*) &one, sizeof (one)) == 0) + : (setsockopt (handle, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof (one)) == 0)); + } + + static bool bindSocketToPort (const SocketHandle handle, const int port) noexcept + { + if (handle <= 0 || port <= 0) + return false; + + struct sockaddr_in servTmpAddr; + zerostruct (servTmpAddr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + servTmpAddr.sin_family = PF_INET; + servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); + servTmpAddr.sin_port = htons ((uint16) port); + + return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; + } + + static int readSocket (const SocketHandle handle, + void* const destBuffer, const int maxBytesToRead, + bool volatile& connected, + const bool blockUntilSpecifiedAmountHasArrived) noexcept + { + int bytesRead = 0; + + while (bytesRead < maxBytesToRead) + { + int bytesThisTime; + + #if JUCE_WINDOWS + bytesThisTime = recv (handle, static_cast (destBuffer) + bytesRead, maxBytesToRead - bytesRead, 0); + #else + while ((bytesThisTime = (int) ::read (handle, addBytesToPointer (destBuffer, bytesRead), + (size_t) (maxBytesToRead - bytesRead))) < 0 + && errno == EINTR + && connected) + { + } + #endif + + if (bytesThisTime <= 0 || ! connected) + { + if (bytesRead == 0) + bytesRead = -1; + + break; + } + + bytesRead += bytesThisTime; + + if (! blockUntilSpecifiedAmountHasArrived) + break; + } + + return bytesRead; + } + + static int waitForReadiness (const SocketHandle handle, const bool forReading, const int timeoutMsecs) noexcept + { + struct timeval timeout; + struct timeval* timeoutp; + + if (timeoutMsecs >= 0) + { + timeout.tv_sec = timeoutMsecs / 1000; + timeout.tv_usec = (timeoutMsecs % 1000) * 1000; + timeoutp = &timeout; + } + else + { + timeoutp = 0; + } + + fd_set rset, wset; + FD_ZERO (&rset); + FD_SET (handle, &rset); + FD_ZERO (&wset); + FD_SET (handle, &wset); + + fd_set* const prset = forReading ? &rset : nullptr; + fd_set* const pwset = forReading ? nullptr : &wset; + + #if JUCE_WINDOWS + if (select ((int) handle + 1, prset, pwset, 0, timeoutp) < 0) + return -1; + #else + { + int result; + while ((result = select (handle + 1, prset, pwset, 0, timeoutp)) < 0 + && errno == EINTR) + { + } + + if (result < 0) + return -1; + } + #endif + + { + int opt; + juce_socklen_t len = sizeof (opt); + + if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 + || opt != 0) + return -1; + } + + return FD_ISSET (handle, forReading ? &rset : &wset) ? 1 : 0; + } + + static bool setSocketBlockingState (const SocketHandle handle, const bool shouldBlock) noexcept + { + #if JUCE_WINDOWS + u_long nonBlocking = shouldBlock ? 0 : (u_long) 1; + return ioctlsocket (handle, FIONBIO, &nonBlocking) == 0; + #else + int socketFlags = fcntl (handle, F_GETFL, 0); + + if (socketFlags == -1) + return false; + + if (shouldBlock) + socketFlags &= ~O_NONBLOCK; + else + socketFlags |= O_NONBLOCK; + + return fcntl (handle, F_SETFL, socketFlags) == 0; + #endif + } + + static bool connectSocket (int volatile& handle, + const bool isDatagram, + struct addrinfo** const serverAddress, + const String& hostName, + const int portNumber, + const int timeOutMillisecs) noexcept + { + struct addrinfo hints; + zerostruct (hints); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = isDatagram ? SOCK_DGRAM : SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + + struct addrinfo* info = nullptr; + if (getaddrinfo (hostName.toUTF8(), String (portNumber).toUTF8(), &hints, &info) != 0 + || info == nullptr) + return false; + + if (handle < 0) + handle = (int) socket (info->ai_family, info->ai_socktype, 0); + + if (handle < 0) + { + freeaddrinfo (info); + return false; + } + + if (isDatagram) + { + if (*serverAddress != nullptr) + freeaddrinfo (*serverAddress); + + *serverAddress = info; + return true; + } + + setSocketBlockingState (handle, false); + const int result = ::connect (handle, info->ai_addr, (socklen_t) info->ai_addrlen); + freeaddrinfo (info); + + if (result < 0) + { + #if JUCE_WINDOWS + if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) + #else + if (errno == EINPROGRESS) + #endif + { + if (waitForReadiness (handle, false, timeOutMillisecs) != 1) + { + setSocketBlockingState (handle, true); + return false; + } + } + } + + setSocketBlockingState (handle, true); + resetSocketOptions (handle, false, false); + + return true; + } + + static void makeReusable (int handle) noexcept + { + const int reuse = 1; + setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); + } +} + +//============================================================================== +StreamingSocket::StreamingSocket() + : portNumber (0), + handle (-1), + connected (false), + isListener (false) +{ + SocketHelpers::initSockets(); +} + +StreamingSocket::StreamingSocket (const String& host, int portNum, int h) + : hostName (host), + portNumber (portNum), + handle (h), + connected (true), + isListener (false) +{ + SocketHelpers::initSockets(); + SocketHelpers::resetSocketOptions (h, false, false); +} + +StreamingSocket::~StreamingSocket() +{ + close(); +} + +//============================================================================== +int StreamingSocket::read (void* destBuffer, const int maxBytesToRead, + const bool blockUntilSpecifiedAmountHasArrived) +{ + return (connected && ! isListener) ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, + connected, blockUntilSpecifiedAmountHasArrived) + : -1; +} + +int StreamingSocket::write (const void* sourceBuffer, const int numBytesToWrite) +{ + if (isListener || ! connected) + return -1; + + #if JUCE_WINDOWS + return send (handle, (const char*) sourceBuffer, numBytesToWrite, 0); + #else + int result; + + while ((result = (int) ::write (handle, sourceBuffer, (size_t) numBytesToWrite)) < 0 + && errno == EINTR) + { + } + + return result; + #endif +} + +//============================================================================== +int StreamingSocket::waitUntilReady (const bool readyForReading, + const int timeoutMsecs) const +{ + return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) + : -1; +} + +//============================================================================== +bool StreamingSocket::bindToPort (const int port) +{ + return SocketHelpers::bindSocketToPort (handle, port); +} + +bool StreamingSocket::connect (const String& remoteHostName, + const int remotePortNumber, + const int timeOutMillisecs) +{ + if (isListener) + { + jassertfalse; // a listener socket can't connect to another one! + return false; + } + + if (connected) + close(); + + hostName = remoteHostName; + portNumber = remotePortNumber; + isListener = false; + + connected = SocketHelpers::connectSocket (handle, false, nullptr, remoteHostName, + remotePortNumber, timeOutMillisecs); + + if (! (connected && SocketHelpers::resetSocketOptions (handle, false, false))) + { + close(); + return false; + } + + return true; +} + +void StreamingSocket::close() +{ + #if JUCE_WINDOWS + if (handle != SOCKET_ERROR || connected) + closesocket (handle); + + connected = false; + #else + if (connected) + { + connected = false; + + if (isListener) + { + // need to do this to interrupt the accept() function.. + StreamingSocket temp; + temp.connect (IPAddress::local().toString(), portNumber, 1000); + } + } + + if (handle != -1) + { + ::shutdown (handle, SHUT_RDWR); + ::close (handle); + } + #endif + + hostName.clear(); + portNumber = 0; + handle = -1; + isListener = false; +} + +//============================================================================== +bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) +{ + if (connected) + close(); + + hostName = "listener"; + portNumber = newPortNumber; + isListener = true; + + struct sockaddr_in servTmpAddr; + zerostruct (servTmpAddr); + + servTmpAddr.sin_family = PF_INET; + servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); + + if (localHostName.isNotEmpty()) + servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); + + servTmpAddr.sin_port = htons ((uint16) portNumber); + + handle = (int) socket (AF_INET, SOCK_STREAM, 0); + + if (handle < 0) + return false; + + #if ! JUCE_WINDOWS // on windows, adding this option produces behaviour different to posix + SocketHelpers::makeReusable (handle); + #endif + + if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 + || listen (handle, SOMAXCONN) < 0) + { + close(); + return false; + } + + connected = true; + return true; +} + +StreamingSocket* StreamingSocket::waitForNextConnection() const +{ + // To call this method, you first have to use createListener() to + // prepare this socket as a listener. + jassert (isListener || ! connected); + + if (connected && isListener) + { + struct sockaddr_storage address; + juce_socklen_t len = sizeof (address); + const int newSocket = (int) accept (handle, (struct sockaddr*) &address, &len); + + if (newSocket >= 0 && connected) + return new StreamingSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), + portNumber, newSocket); + } + + return nullptr; +} + +bool StreamingSocket::isLocal() const noexcept +{ + return hostName == "127.0.0.1"; +} + + +//============================================================================== +//============================================================================== +DatagramSocket::DatagramSocket (const int localPortNumber, const bool canBroadcast) + : portNumber (0), + handle (-1), + connected (true), + allowBroadcast (canBroadcast), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + handle = (int) socket (AF_INET, SOCK_DGRAM, 0); + SocketHelpers::makeReusable (handle); + bindToPort (localPortNumber); +} + +DatagramSocket::DatagramSocket (const String& host, const int portNum, + const int h, const int localPortNumber) + : hostName (host), + portNumber (portNum), + handle (h), + connected (true), + allowBroadcast (false), + serverAddress (nullptr) +{ + SocketHelpers::initSockets(); + + SocketHelpers::resetSocketOptions (h, true, allowBroadcast); + bindToPort (localPortNumber); +} + +DatagramSocket::~DatagramSocket() +{ + close(); + + if (serverAddress != nullptr) + freeaddrinfo (static_cast (serverAddress)); +} + +void DatagramSocket::close() +{ + #if JUCE_WINDOWS + closesocket (handle); + connected = false; + #else + connected = false; + ::close (handle); + #endif + + hostName.clear(); + portNumber = 0; + handle = -1; +} + +bool DatagramSocket::bindToPort (const int port) +{ + return SocketHelpers::bindSocketToPort (handle, port); +} + +bool DatagramSocket::connect (const String& remoteHostName, + const int remotePortNumber, + const int timeOutMillisecs) +{ + if (connected) + close(); + + hostName = remoteHostName; + portNumber = remotePortNumber; + + connected = SocketHelpers::connectSocket (handle, true, (struct addrinfo**) &serverAddress, + remoteHostName, remotePortNumber, + timeOutMillisecs); + + if (! (connected && SocketHelpers::resetSocketOptions (handle, true, allowBroadcast))) + { + close(); + return false; + } + + return true; +} + +DatagramSocket* DatagramSocket::waitForNextConnection() const +{ + while (waitUntilReady (true, -1) == 1) + { + struct sockaddr_storage address; + juce_socklen_t len = sizeof (address); + char buf[1]; + + if (recvfrom (handle, buf, 0, 0, (struct sockaddr*) &address, &len) > 0) + return new DatagramSocket (inet_ntoa (((struct sockaddr_in*) &address)->sin_addr), + ntohs (((struct sockaddr_in*) &address)->sin_port), + -1, -1); + } + + return nullptr; +} + +//============================================================================== +int DatagramSocket::waitUntilReady (const bool readyForReading, + const int timeoutMsecs) const +{ + return connected ? SocketHelpers::waitForReadiness (handle, readyForReading, timeoutMsecs) + : -1; +} + +int DatagramSocket::read (void* destBuffer, const int maxBytesToRead, const bool blockUntilSpecifiedAmountHasArrived) +{ + return connected ? SocketHelpers::readSocket (handle, destBuffer, maxBytesToRead, + connected, blockUntilSpecifiedAmountHasArrived) + : -1; +} + +int DatagramSocket::write (const void* sourceBuffer, const int numBytesToWrite) +{ + // You need to call connect() first to set the server address.. + jassert (serverAddress != nullptr && connected); + + return connected ? (int) sendto (handle, (const char*) sourceBuffer, + (size_t) numBytesToWrite, 0, + static_cast (serverAddress)->ai_addr, + (juce_socklen_t) static_cast (serverAddress)->ai_addrlen) + : -1; +} + +bool DatagramSocket::isLocal() const noexcept +{ + return hostName == "127.0.0.1"; +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif diff --git a/JuceLibraryCode/modules/juce_core/network/juce_Socket.h b/JuceLibraryCode/modules/juce_core/network/juce_Socket.h new file mode 100644 index 0000000..73795c9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_Socket.h @@ -0,0 +1,305 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SOCKET_H_INCLUDED +#define JUCE_SOCKET_H_INCLUDED + + +//============================================================================== +/** + A wrapper for a streaming (TCP) socket. + + This allows low-level use of sockets; for an easier-to-use messaging layer on top of + sockets, you could also try the InterprocessConnection class. + + @see DatagramSocket, InterprocessConnection, InterprocessConnectionServer +*/ +class JUCE_API StreamingSocket +{ +public: + //============================================================================== + /** Creates an uninitialised socket. + + To connect it, use the connect() method, after which you can read() or write() + to it. + + To wait for other sockets to connect to this one, the createListener() method + enters "listener" mode, and can be used to spawn new sockets for each connection + that comes along. + */ + StreamingSocket(); + + /** Destructor. */ + ~StreamingSocket(); + + //============================================================================== + /** Binds the socket to the specified local port. + + @returns true on success; false may indicate that another socket is already bound + on the same port + */ + bool bindToPort (int localPortNumber); + + /** Tries to connect the socket to hostname:port. + + If timeOutMillisecs is 0, then this method will block until the operating system + rejects the connection (which could take a long time). + + @returns true if it succeeds. + @see isConnected + */ + bool connect (const String& remoteHostname, + int remotePortNumber, + int timeOutMillisecs = 3000); + + /** True if the socket is currently connected. */ + bool isConnected() const noexcept { return connected; } + + /** Closes the connection. */ + void close(); + + /** Returns the name of the currently connected host. */ + const String& getHostName() const noexcept { return hostName; } + + /** Returns the port number that's currently open. */ + int getPort() const noexcept { return portNumber; } + + /** True if the socket is connected to this machine rather than over the network. */ + bool isLocal() const noexcept; + + /** Returns the OS's socket handle that's currently open. */ + int getRawSocketHandle() const noexcept { return handle; } + + //============================================================================== + /** Waits until the socket is ready for reading or writing. + + If readyForReading is true, it will wait until the socket is ready for + reading; if false, it will wait until it's ready for writing. + + If the timeout is < 0, it will wait forever, or else will give up after + the specified time. + + If the socket is ready on return, this returns 1. If it times-out before + the socket becomes ready, it returns 0. If an error occurs, it returns -1. + */ + int waitUntilReady (bool readyForReading, + int timeoutMsecs) const; + + /** Reads bytes from the socket. + + If blockUntilSpecifiedAmountHasArrived is true, the method will block until + maxBytesToRead bytes have been read, (or until an error occurs). If this + flag is false, the method will return as much data as is currently available + without blocking. + + @returns the number of bytes read, or -1 if there was an error. + @see waitUntilReady + */ + int read (void* destBuffer, int maxBytesToRead, + bool blockUntilSpecifiedAmountHasArrived); + + /** Writes bytes to the socket from a buffer. + + Note that this method will block unless you have checked the socket is ready + for writing before calling it (see the waitUntilReady() method). + + @returns the number of bytes written, or -1 if there was an error. + */ + int write (const void* sourceBuffer, int numBytesToWrite); + + //============================================================================== + /** Puts this socket into "listener" mode. + + When in this mode, your thread can call waitForNextConnection() repeatedly, + which will spawn new sockets for each new connection, so that these can + be handled in parallel by other threads. + + @param portNumber the port number to listen on + @param localHostName the interface address to listen on - pass an empty + string to listen on all addresses + @returns true if it manages to open the socket successfully. + + @see waitForNextConnection + */ + bool createListener (int portNumber, const String& localHostName = String()); + + /** When in "listener" mode, this waits for a connection and spawns it as a new + socket. + + The object that gets returned will be owned by the caller. + + This method can only be called after using createListener(). + + @see createListener + */ + StreamingSocket* waitForNextConnection() const; + +private: + //============================================================================== + String hostName; + int volatile portNumber, handle; + bool connected, isListener; + + StreamingSocket (const String& hostname, int portNumber, int handle); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StreamingSocket) +}; + + +//============================================================================== +/** + A wrapper for a datagram (UDP) socket. + + This allows low-level use of sockets; for an easier-to-use messaging layer on top of + sockets, you could also try the InterprocessConnection class. + + @see StreamingSocket, InterprocessConnection, InterprocessConnectionServer +*/ +class JUCE_API DatagramSocket +{ +public: + //============================================================================== + /** + Creates an (uninitialised) datagram socket. + + The localPortNumber is the port on which to bind this socket. If this value is 0, + the port number is assigned by the operating system. + + To use the socket for sending, call the connect() method. This will not immediately + make a connection, but will save the destination you've provided. After this, you can + call read() or write(). + + If enableBroadcasting is true, the socket will be allowed to send broadcast messages + (may require extra privileges on linux) + + To wait for other sockets to connect to this one, call waitForNextConnection(). + */ + DatagramSocket (int localPortNumber, + bool enableBroadcasting = false); + + /** Destructor. */ + ~DatagramSocket(); + + //============================================================================== + /** Binds the socket to the specified local port. + + @returns true on success; false may indicate that another socket is already bound + on the same port + */ + bool bindToPort (int localPortNumber); + + /** Tries to connect the socket to hostname:port. + + If timeOutMillisecs is 0, then this method will block until the operating system + rejects the connection (which could take a long time). + + @returns true if it succeeds. + @see isConnected + */ + bool connect (const String& remoteHostname, + int remotePortNumber, + int timeOutMillisecs = 3000); + + /** True if the socket is currently connected. */ + bool isConnected() const noexcept { return connected; } + + /** Closes the connection. */ + void close(); + + /** Returns the name of the currently connected host. */ + const String& getHostName() const noexcept { return hostName; } + + /** Returns the port number that's currently open. */ + int getPort() const noexcept { return portNumber; } + + /** True if the socket is connected to this machine rather than over the network. */ + bool isLocal() const noexcept; + + /** Returns the OS's socket handle that's currently open. */ + int getRawSocketHandle() const noexcept { return handle; } + + //============================================================================== + /** Waits until the socket is ready for reading or writing. + + If readyForReading is true, it will wait until the socket is ready for + reading; if false, it will wait until it's ready for writing. + + If the timeout is < 0, it will wait forever, or else will give up after + the specified time. + + If the socket is ready on return, this returns 1. If it times-out before + the socket becomes ready, it returns 0. If an error occurs, it returns -1. + */ + int waitUntilReady (bool readyForReading, + int timeoutMsecs) const; + + /** Reads bytes from the socket. + + If blockUntilSpecifiedAmountHasArrived is true, the method will block until + maxBytesToRead bytes have been read, (or until an error occurs). If this + flag is false, the method will return as much data as is currently available + without blocking. + + @returns the number of bytes read, or -1 if there was an error. + @see waitUntilReady + */ + int read (void* destBuffer, int maxBytesToRead, + bool blockUntilSpecifiedAmountHasArrived); + + /** Writes bytes to the socket from a buffer. + + Note that this method will block unless you have checked the socket is ready + for writing before calling it (see the waitUntilReady() method). + + @returns the number of bytes written, or -1 if there was an error. + */ + int write (const void* sourceBuffer, int numBytesToWrite); + + //============================================================================== + /** This waits for incoming data to be sent, and returns a socket that can be used + to read it. + + The object that gets returned is owned by the caller, and can't be used for + sending, but can be used to read the data. + */ + DatagramSocket* waitForNextConnection() const; + +private: + //============================================================================== + String hostName; + int volatile portNumber, handle; + bool connected, allowBroadcast; + void* serverAddress; + + DatagramSocket (const String& hostname, int portNumber, int handle, int localPortNumber); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DatagramSocket) +}; + + +#endif // JUCE_SOCKET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp b/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp new file mode 100644 index 0000000..a4cc02e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_URL.cpp @@ -0,0 +1,513 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +URL::URL() +{ +} + +URL::URL (const String& u) : url (u) +{ + int i = url.indexOfChar ('?'); + + if (i >= 0) + { + do + { + const int nextAmp = url.indexOfChar (i + 1, '&'); + const int equalsPos = url.indexOfChar (i + 1, '='); + + if (equalsPos > i + 1) + { + if (nextAmp < 0) + { + addParameter (removeEscapeChars (url.substring (i + 1, equalsPos)), + removeEscapeChars (url.substring (equalsPos + 1))); + } + else if (nextAmp > 0 && equalsPos < nextAmp) + { + addParameter (removeEscapeChars (url.substring (i + 1, equalsPos)), + removeEscapeChars (url.substring (equalsPos + 1, nextAmp))); + } + } + + i = nextAmp; + } + while (i >= 0); + + url = url.upToFirstOccurrenceOf ("?", false, false); + } +} + +URL::URL (const String& u, int) : url (u) {} + +URL URL::createWithoutParsing (const String& u) +{ + return URL (u, 0); +} + +URL::URL (const URL& other) + : url (other.url), + postData (other.postData), + parameterNames (other.parameterNames), + parameterValues (other.parameterValues), + filesToUpload (other.filesToUpload) +{ +} + +URL& URL::operator= (const URL& other) +{ + url = other.url; + postData = other.postData; + parameterNames = other.parameterNames; + parameterValues = other.parameterValues; + filesToUpload = other.filesToUpload; + + return *this; +} + +bool URL::operator== (const URL& other) const +{ + return url == other.url + && postData == other.postData + && parameterNames == other.parameterNames + && parameterValues == other.parameterValues + && filesToUpload == other.filesToUpload; +} + +bool URL::operator!= (const URL& other) const +{ + return ! operator== (other); +} + +URL::~URL() +{ +} + +namespace URLHelpers +{ + static String getMangledParameters (const URL& url) + { + jassert (url.getParameterNames().size() == url.getParameterValues().size()); + String p; + + for (int i = 0; i < url.getParameterNames().size(); ++i) + { + if (i > 0) + p << '&'; + + p << URL::addEscapeChars (url.getParameterNames()[i], true) + << '=' + << URL::addEscapeChars (url.getParameterValues()[i], true); + } + + return p; + } + + static int findEndOfScheme (const String& url) + { + int i = 0; + + while (CharacterFunctions::isLetterOrDigit (url[i]) + || url[i] == '+' || url[i] == '-' || url[i] == '.') + ++i; + + return url[i] == ':' ? i + 1 : 0; + } + + static int findStartOfNetLocation (const String& url) + { + int start = findEndOfScheme (url); + while (url[start] == '/') + ++start; + + return start; + } + + static int findStartOfPath (const String& url) + { + return url.indexOfChar (findStartOfNetLocation (url), '/') + 1; + } + + static void concatenatePaths (String& path, const String& suffix) + { + if (! path.endsWithChar ('/')) + path << '/'; + + if (suffix.startsWithChar ('/')) + path += suffix.substring (1); + else + path += suffix; + } +} + +void URL::addParameter (const String& name, const String& value) +{ + parameterNames.add (name); + parameterValues.add (value); +} + +String URL::toString (const bool includeGetParameters) const +{ + if (includeGetParameters && parameterNames.size() > 0) + return url + "?" + URLHelpers::getMangledParameters (*this); + + return url; +} + +bool URL::isEmpty() const noexcept +{ + return url.isEmpty(); +} + +bool URL::isWellFormed() const +{ + //xxx TODO + return url.isNotEmpty(); +} + +String URL::getDomain() const +{ + const int start = URLHelpers::findStartOfNetLocation (url); + const int end1 = url.indexOfChar (start, '/'); + const int end2 = url.indexOfChar (start, ':'); + + const int end = (end1 < 0 && end2 < 0) ? std::numeric_limits::max() + : ((end1 < 0 || end2 < 0) ? jmax (end1, end2) + : jmin (end1, end2)); + return url.substring (start, end); +} + +String URL::getSubPath() const +{ + const int startOfPath = URLHelpers::findStartOfPath (url); + + return startOfPath <= 0 ? String() + : url.substring (startOfPath); +} + +String URL::getScheme() const +{ + return url.substring (0, URLHelpers::findEndOfScheme (url) - 1); +} + +int URL::getPort() const +{ + const int colonPos = url.indexOfChar (URLHelpers::findStartOfNetLocation (url), ':'); + + return colonPos > 0 ? url.substring (colonPos + 1).getIntValue() : 0; +} + +URL URL::withNewSubPath (const String& newPath) const +{ + const int startOfPath = URLHelpers::findStartOfPath (url); + + URL u (*this); + + if (startOfPath > 0) + u.url = url.substring (0, startOfPath); + + URLHelpers::concatenatePaths (u.url, newPath); + return u; +} + +URL URL::getChildURL (const String& subPath) const +{ + URL u (*this); + URLHelpers::concatenatePaths (u.url, subPath); + return u; +} + +void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPostData) const +{ + MemoryOutputStream data (headersAndPostData, false); + + if (filesToUpload.size() > 0) + { + // (this doesn't currently support mixing custom post-data with uploads..) + jassert (postData.isEmpty()); + + const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); + + headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; + + data << "--" << boundary; + + for (int i = 0; i < parameterNames.size(); ++i) + { + data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i] + << "\"\r\n\r\n" << parameterValues[i] + << "\r\n--" << boundary; + } + + for (int i = 0; i < filesToUpload.size(); ++i) + { + const Upload& f = *filesToUpload.getObjectPointerUnchecked(i); + + data << "\r\nContent-Disposition: form-data; name=\"" << f.parameterName + << "\"; filename=\"" << f.filename << "\"\r\n"; + + if (f.mimeType.isNotEmpty()) + data << "Content-Type: " << f.mimeType << "\r\n"; + + data << "Content-Transfer-Encoding: binary\r\n\r\n"; + + if (f.data != nullptr) + data << *f.data; + else + data << f.file; + + data << "\r\n--" << boundary; + } + + data << "--\r\n"; + } + else + { + data << URLHelpers::getMangledParameters (*this) + << postData; + + // if the user-supplied headers didn't contain a content-type, add one now.. + if (! headers.containsIgnoreCase ("Content-Type")) + headers << "Content-Type: application/x-www-form-urlencoded\r\n"; + + headers << "Content-length: " << (int) data.getDataSize() << "\r\n"; + } +} + +//============================================================================== +bool URL::isProbablyAWebsiteURL (const String& possibleURL) +{ + static const char* validProtocols[] = { "http:", "ftp:", "https:" }; + + for (int i = 0; i < numElementsInArray (validProtocols); ++i) + if (possibleURL.startsWithIgnoreCase (validProtocols[i])) + return true; + + if (possibleURL.containsChar ('@') + || possibleURL.containsChar (' ')) + return false; + + const String topLevelDomain (possibleURL.upToFirstOccurrenceOf ("/", false, false) + .fromLastOccurrenceOf (".", false, false)); + + return topLevelDomain.isNotEmpty() && topLevelDomain.length() <= 3; +} + +bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress) +{ + const int atSign = possibleEmailAddress.indexOfChar ('@'); + + return atSign > 0 + && possibleEmailAddress.lastIndexOfChar ('.') > (atSign + 1) + && ! possibleEmailAddress.endsWithChar ('.'); +} + +//============================================================================== +InputStream* URL::createInputStream (const bool usePostCommand, + OpenStreamProgressCallback* const progressCallback, + void* const progressCallbackContext, + String headers, + const int timeOutMs, + StringPairArray* const responseHeaders, + int* statusCode) const +{ + MemoryBlock headersAndPostData; + + if (! headers.endsWithChar ('\n')) + headers << "\r\n"; + + if (usePostCommand) + createHeadersAndPostData (headers, headersAndPostData); + + if (! headers.endsWithChar ('\n')) + headers << "\r\n"; + + ScopedPointer wi (new WebInputStream (toString (! usePostCommand), + usePostCommand, headersAndPostData, + progressCallback, progressCallbackContext, + headers, timeOutMs, responseHeaders)); + + if (statusCode != nullptr) + *statusCode = wi->statusCode; + + return wi->isError() ? nullptr : wi.release(); +} + +//============================================================================== +bool URL::readEntireBinaryStream (MemoryBlock& destData, + const bool usePostCommand) const +{ + const ScopedPointer in (createInputStream (usePostCommand)); + + if (in != nullptr) + { + in->readIntoMemoryBlock (destData); + return true; + } + + return false; +} + +String URL::readEntireTextStream (const bool usePostCommand) const +{ + const ScopedPointer in (createInputStream (usePostCommand)); + + if (in != nullptr) + return in->readEntireStreamAsString(); + + return String(); +} + +XmlElement* URL::readEntireXmlStream (const bool usePostCommand) const +{ + return XmlDocument::parse (readEntireTextStream (usePostCommand)); +} + +//============================================================================== +URL URL::withParameter (const String& parameterName, + const String& parameterValue) const +{ + URL u (*this); + u.addParameter (parameterName, parameterValue); + return u; +} + +URL URL::withParameters (const StringPairArray& parametersToAdd) const +{ + URL u (*this); + + for (int i = 0; i < parametersToAdd.size(); ++i) + u.addParameter (parametersToAdd.getAllKeys()[i], + parametersToAdd.getAllValues()[i]); + + return u; +} + +URL URL::withPOSTData (const String& newPostData) const +{ + URL u (*this); + u.postData = newPostData; + return u; +} + +URL::Upload::Upload (const String& param, const String& name, + const String& mime, const File& f, MemoryBlock* mb) + : parameterName (param), filename (name), mimeType (mime), file (f), data (mb) +{ + jassert (mimeType.isNotEmpty()); // You need to supply a mime type! +} + +URL URL::withUpload (Upload* const f) const +{ + URL u (*this); + + for (int i = u.filesToUpload.size(); --i >= 0;) + if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName) + u.filesToUpload.remove (i); + + u.filesToUpload.add (f); + return u; +} + +URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload, + const String& mimeType) const +{ + return withUpload (new Upload (parameterName, fileToUpload.getFileName(), + mimeType, fileToUpload, nullptr)); +} + +URL URL::withDataToUpload (const String& parameterName, const String& filename, + const MemoryBlock& fileContentToUpload, const String& mimeType) const +{ + return withUpload (new Upload (parameterName, filename, mimeType, File(), + new MemoryBlock (fileContentToUpload))); +} + +//============================================================================== +String URL::removeEscapeChars (const String& s) +{ + String result (s.replaceCharacter ('+', ' ')); + + if (! result.containsChar ('%')) + return result; + + // We need to operate on the string as raw UTF8 chars, and then recombine them into unicode + // after all the replacements have been made, so that multi-byte chars are handled. + Array utf8 (result.toRawUTF8(), (int) result.getNumBytesAsUTF8()); + + for (int i = 0; i < utf8.size(); ++i) + { + if (utf8.getUnchecked(i) == '%') + { + const int hexDigit1 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 1]); + const int hexDigit2 = CharacterFunctions::getHexDigitValue ((juce_wchar) (uint8) utf8 [i + 2]); + + if (hexDigit1 >= 0 && hexDigit2 >= 0) + { + utf8.set (i, (char) ((hexDigit1 << 4) + hexDigit2)); + utf8.removeRange (i + 1, 2); + } + } + } + + return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); +} + +String URL::addEscapeChars (const String& s, const bool isParameter) +{ + const CharPointer_UTF8 legalChars (isParameter ? "_-.*!'()" + : ",$_-.*!'()"); + + Array utf8 (s.toRawUTF8(), (int) s.getNumBytesAsUTF8()); + + for (int i = 0; i < utf8.size(); ++i) + { + const char c = utf8.getUnchecked(i); + + if (! (CharacterFunctions::isLetterOrDigit (c) + || legalChars.indexOf ((juce_wchar) c) >= 0)) + { + utf8.set (i, '%'); + utf8.insert (++i, "0123456789ABCDEF" [((uint8) c) >> 4]); + utf8.insert (++i, "0123456789ABCDEF" [c & 15]); + } + } + + return String::fromUTF8 (utf8.getRawDataPointer(), utf8.size()); +} + +//============================================================================== +bool URL::launchInDefaultBrowser() const +{ + String u (toString (true)); + + if (u.containsChar ('@') && ! u.containsChar (':')) + u = "mailto:" + u; + + return Process::openDocument (u, String()); +} diff --git a/JuceLibraryCode/modules/juce_core/network/juce_URL.h b/JuceLibraryCode/modules/juce_core/network/juce_URL.h new file mode 100644 index 0000000..697ce0c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/network/juce_URL.h @@ -0,0 +1,384 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_URL_H_INCLUDED +#define JUCE_URL_H_INCLUDED + + +//============================================================================== +/** + Represents a URL and has a bunch of useful functions to manipulate it. + + This class can be used to launch URLs in browsers, and also to create + InputStreams that can read from remote http or ftp sources. +*/ +class JUCE_API URL +{ +public: + //============================================================================== + /** Creates an empty URL. */ + URL(); + + /** Creates a URL from a string. + This will parse any embedded parameters after a '?' character and store them + in the list (see getParameterNames etc). If you don't want this to happen, you + can use createWithoutParsing(). + */ + URL (const String& url); + + /** Creates a copy of another URL. */ + URL (const URL& other); + + /** Destructor. */ + ~URL(); + + /** Copies this URL from another one. */ + URL& operator= (const URL& other); + + /** Compares two URLs. + All aspects of the URLs must be identical for them to match, including any parameters, + upload files, etc. + */ + bool operator== (const URL&) const; + bool operator!= (const URL&) const; + + //============================================================================== + /** Returns a string version of the URL. + + If includeGetParameters is true and any parameters have been set with the + withParameter() method, then the string will have these appended on the + end and url-encoded. + */ + String toString (bool includeGetParameters) const; + + /** Returns true if the URL is an empty string. */ + bool isEmpty() const noexcept; + + /** True if it seems to be valid. */ + bool isWellFormed() const; + + /** Returns just the domain part of the URL. + + E.g. for "http://www.xyz.com/foobar", this will return "www.xyz.com". + */ + String getDomain() const; + + /** Returns the path part of the URL. + + E.g. for "http://www.xyz.com/foo/bar?x=1", this will return "foo/bar". + */ + String getSubPath() const; + + /** Returns the scheme of the URL. + + E.g. for "http://www.xyz.com/foobar", this will return "http". (It won't + include the colon). + */ + String getScheme() const; + + /** Attempts to read a port number from the URL. + @returns the port number, or 0 if none is explicitly specified. + */ + int getPort() const; + + /** Returns a new version of this URL that uses a different sub-path. + + E.g. if the URL is "http://www.xyz.com/foo?x=1" and you call this with + "bar", it'll return "http://www.xyz.com/bar?x=1". + */ + URL withNewSubPath (const String& newPath) const; + + /** Returns a new URL that refers to a sub-path relative to this one. + + E.g. if the URL is "http://www.xyz.com/foo" and you call this with + "bar", it'll return "http://www.xyz.com/foo/bar". Note that there's no way for + this method to know whether the original URL is a file or directory, so it's + up to you to make sure it's a directory. It also won't attempt to be smart about + the content of the childPath string, so if this string is an absolute URL, it'll + still just get bolted onto the end of the path. + + @see File::getChildFile + */ + URL getChildURL (const String& subPath) const; + + //============================================================================== + /** Returns a copy of this URL, with a GET or POST parameter added to the end. + + Any control characters in the value will be encoded. + + e.g. calling "withParameter ("amount", "some fish") for the url "www.fish.com" + would produce a new url whose toString(true) method would return + "www.fish.com?amount=some+fish". + + @see getParameterNames, getParameterValues + */ + URL withParameter (const String& parameterName, + const String& parameterValue) const; + + /** Returns a copy of this URL, with a set of GET or POST parameters added. + This is a convenience method, equivalent to calling withParameter for each value. + @see withParameter + */ + URL withParameters (const StringPairArray& parametersToAdd) const; + + /** Returns a copy of this URL, with a file-upload type parameter added to it. + + When performing a POST where one of your parameters is a binary file, this + lets you specify the file. + + Note that the filename is stored, but the file itself won't actually be read + until this URL is later used to create a network input stream. If you want to + upload data from memory, use withDataToUpload(). + + @see withDataToUpload + */ + URL withFileToUpload (const String& parameterName, + const File& fileToUpload, + const String& mimeType) const; + + /** Returns a copy of this URL, with a file-upload type parameter added to it. + + When performing a POST where one of your parameters is a binary file, this + lets you specify the file content. + Note that the filename parameter should not be a full path, it's just the + last part of the filename. + + @see withFileToUpload + */ + URL withDataToUpload (const String& parameterName, + const String& filename, + const MemoryBlock& fileContentToUpload, + const String& mimeType) const; + + /** Returns an array of the names of all the URL's parameters. + + E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would + contain two items: "type" and "amount". + + You can call getParameterValues() to get the corresponding value of each + parameter. Note that the list can contain multiple parameters with the same name. + + @see getParameterValues, withParameter + */ + const StringArray& getParameterNames() const noexcept { return parameterNames; } + + /** Returns an array of the values of all the URL's parameters. + + E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would + contain two items: "haddock" and "some fish". + + The values returned will have been cleaned up to remove any escape characters. + + You can call getParameterNames() to get the corresponding name of each + parameter. Note that the list can contain multiple parameters with the same name. + + @see getParameterNames, withParameter + */ + const StringArray& getParameterValues() const noexcept { return parameterValues; } + + /** Returns a copy of this URL, with a block of data to send as the POST data. + + If you're setting the POST data, be careful not to have any parameters set + as well, otherwise it'll all get thrown in together, and might not have the + desired effect. + + If the URL already contains some POST data, this will replace it, rather + than being appended to it. + + This data will only be used if you specify a post operation when you call + createInputStream(). + */ + URL withPOSTData (const String& postData) const; + + /** Returns the data that was set using withPOSTData(). */ + const String& getPostData() const noexcept { return postData; } + + //============================================================================== + /** Tries to launch the system's default browser to open the URL. + + Returns true if this seems to have worked. + */ + bool launchInDefaultBrowser() const; + + //============================================================================== + /** Takes a guess as to whether a string might be a valid website address. + + This isn't foolproof! + */ + static bool isProbablyAWebsiteURL (const String& possibleURL); + + /** Takes a guess as to whether a string might be a valid email address. + + This isn't foolproof! + */ + static bool isProbablyAnEmailAddress (const String& possibleEmailAddress); + + //============================================================================== + /** This callback function can be used by the createInputStream() method. + + It allows your app to receive progress updates during a lengthy POST operation. If you + want to continue the operation, this should return true, or false to abort. + */ + typedef bool (OpenStreamProgressCallback) (void* context, int bytesSent, int totalBytes); + + /** Attempts to open a stream that can read from this URL. + + @param usePostCommand if true, it will try to do use a http 'POST' to pass + the parameters, otherwise it'll encode them into the + URL and do a 'GET'. + @param progressCallback if this is non-zero, it lets you supply a callback function + to keep track of the operation's progress. This can be useful + for lengthy POST operations, so that you can provide user feedback. + @param progressCallbackContext if a callback is specified, this value will be passed to + the function + @param extraHeaders if not empty, this string is appended onto the headers that + are used for the request. It must therefore be a valid set of HTML + header directives, separated by newlines. + @param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If + a negative number, it will be infinite. Otherwise it specifies a + time in milliseconds. + @param responseHeaders if this is non-null, all the (key, value) pairs received as headers + in the response will be stored in this array + @param statusCode if this is non-null, it will get set to the http status code, if one + is known, or 0 if a code isn't available + @returns an input stream that the caller must delete, or a null pointer if there was an + error trying to open it. + */ + InputStream* createInputStream (bool usePostCommand, + OpenStreamProgressCallback* progressCallback = nullptr, + void* progressCallbackContext = nullptr, + String extraHeaders = String(), + int connectionTimeOutMs = 0, + StringPairArray* responseHeaders = nullptr, + int* statusCode = nullptr) const; + + + //============================================================================== + /** Tries to download the entire contents of this URL into a binary data block. + + If it succeeds, this will return true and append the data it read onto the end + of the memory block. + + @param destData the memory block to append the new data to + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + @see readEntireTextStream, readEntireXmlStream + */ + bool readEntireBinaryStream (MemoryBlock& destData, + bool usePostCommand = false) const; + + /** Tries to download the entire contents of this URL as a string. + + If it fails, this will return an empty string, otherwise it will return the + contents of the downloaded file. If you need to distinguish between a read + operation that fails and one that returns an empty string, you'll need to use + a different method, such as readEntireBinaryStream(). + + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + @see readEntireBinaryStream, readEntireXmlStream + */ + String readEntireTextStream (bool usePostCommand = false) const; + + /** Tries to download the entire contents of this URL and parse it as XML. + + If it fails, or if the text that it reads can't be parsed as XML, this will + return 0. + + When it returns a valid XmlElement object, the caller is responsibile for deleting + this object when no longer needed. + + @param usePostCommand whether to use a POST command to get the data (uses + a GET command if this is false) + + @see readEntireBinaryStream, readEntireTextStream + */ + XmlElement* readEntireXmlStream (bool usePostCommand = false) const; + + //============================================================================== + /** Adds escape sequences to a string to encode any characters that aren't + legal in a URL. + + E.g. any spaces will be replaced with "%20". + + This is the opposite of removeEscapeChars(). + + If isParameter is true, it means that the string is going to be used + as a parameter, so it also encodes '$' and ',' (which would otherwise + be legal in a URL. + + @see removeEscapeChars + */ + static String addEscapeChars (const String& stringToAddEscapeCharsTo, + bool isParameter); + + /** Replaces any escape character sequences in a string with their original + character codes. + + E.g. any instances of "%20" will be replaced by a space. + + This is the opposite of addEscapeChars(). + + @see addEscapeChars + */ + static String removeEscapeChars (const String& stringToRemoveEscapeCharsFrom); + + /** Returns a URL without attempting to remove any embedded parameters from the string. + This may be necessary if you need to create a request that involves both POST + parameters and parameters which are embedded in the URL address itself. + */ + static URL createWithoutParsing (const String& url); + +private: + //============================================================================== + String url, postData; + StringArray parameterNames, parameterValues; + + struct Upload : public ReferenceCountedObject + { + Upload (const String&, const String&, const String&, const File&, MemoryBlock*); + String parameterName, filename, mimeType; + File file; + ScopedPointer data; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload) + }; + + friend struct ContainerDeletePolicy; + ReferenceCountedArray filesToUpload; + + URL (const String&, int); + void addParameter (const String&, const String&); + void createHeadersAndPostData (String&, MemoryBlock&) const; + URL withUpload (Upload*) const; + + JUCE_LEAK_DETECTOR (URL) +}; + + +#endif // JUCE_URL_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.cpp new file mode 100644 index 0000000..ec01f8e --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.cpp @@ -0,0 +1,198 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +namespace +{ + int calcBufferStreamBufferSize (int requestedSize, InputStream* const source) noexcept + { + // You need to supply a real stream when creating a BufferedInputStream + jassert (source != nullptr); + + requestedSize = jmax (256, requestedSize); + + const int64 sourceSize = source->getTotalLength(); + if (sourceSize >= 0 && sourceSize < requestedSize) + requestedSize = jmax (32, (int) sourceSize); + + return requestedSize; + } +} + +//============================================================================== +BufferedInputStream::BufferedInputStream (InputStream* const sourceStream, const int bufferSize_, + const bool deleteSourceWhenDestroyed) + : source (sourceStream, deleteSourceWhenDestroyed), + bufferSize (calcBufferStreamBufferSize (bufferSize_, sourceStream)), + position (sourceStream->getPosition()), + lastReadPos (0), + bufferStart (position), + bufferOverlap (128) +{ + buffer.malloc ((size_t) bufferSize); +} + +BufferedInputStream::BufferedInputStream (InputStream& sourceStream, const int bufferSize_) + : source (&sourceStream, false), + bufferSize (calcBufferStreamBufferSize (bufferSize_, &sourceStream)), + position (sourceStream.getPosition()), + lastReadPos (0), + bufferStart (position), + bufferOverlap (128) +{ + buffer.malloc ((size_t) bufferSize); +} + +BufferedInputStream::~BufferedInputStream() +{ +} + +//============================================================================== +int64 BufferedInputStream::getTotalLength() +{ + return source->getTotalLength(); +} + +int64 BufferedInputStream::getPosition() +{ + return position; +} + +bool BufferedInputStream::setPosition (int64 newPosition) +{ + position = jmax ((int64) 0, newPosition); + return true; +} + +bool BufferedInputStream::isExhausted() +{ + return position >= lastReadPos && source->isExhausted(); +} + +void BufferedInputStream::ensureBuffered() +{ + const int64 bufferEndOverlap = lastReadPos - bufferOverlap; + + if (position < bufferStart || position >= bufferEndOverlap) + { + int bytesRead; + + if (position < lastReadPos + && position >= bufferEndOverlap + && position >= bufferStart) + { + const int bytesToKeep = (int) (lastReadPos - position); + memmove (buffer, buffer + (int) (position - bufferStart), (size_t) bytesToKeep); + + bufferStart = position; + + bytesRead = source->read (buffer + bytesToKeep, + (int) (bufferSize - bytesToKeep)); + + lastReadPos += bytesRead; + bytesRead += bytesToKeep; + } + else + { + bufferStart = position; + source->setPosition (bufferStart); + bytesRead = source->read (buffer, bufferSize); + lastReadPos = bufferStart + bytesRead; + } + + while (bytesRead < bufferSize) + buffer [bytesRead++] = 0; + } +} + +int BufferedInputStream::read (void* destBuffer, int maxBytesToRead) +{ + jassert (destBuffer != nullptr && maxBytesToRead >= 0); + + if (position >= bufferStart + && position + maxBytesToRead <= lastReadPos) + { + memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) maxBytesToRead); + position += maxBytesToRead; + + return maxBytesToRead; + } + else + { + if (position < bufferStart || position >= lastReadPos) + ensureBuffered(); + + int bytesRead = 0; + + while (maxBytesToRead > 0) + { + const int bytesAvailable = jmin (maxBytesToRead, (int) (lastReadPos - position)); + + if (bytesAvailable > 0) + { + memcpy (destBuffer, buffer + (int) (position - bufferStart), (size_t) bytesAvailable); + maxBytesToRead -= bytesAvailable; + bytesRead += bytesAvailable; + position += bytesAvailable; + destBuffer = static_cast (destBuffer) + bytesAvailable; + } + + const int64 oldLastReadPos = lastReadPos; + ensureBuffered(); + + if (oldLastReadPos == lastReadPos) + break; // if ensureBuffered() failed to read any more data, bail out + + if (isExhausted()) + break; + } + + return bytesRead; + } +} + +String BufferedInputStream::readString() +{ + if (position >= bufferStart + && position < lastReadPos) + { + const int maxChars = (int) (lastReadPos - position); + + const char* const src = buffer + (int) (position - bufferStart); + + for (int i = 0; i < maxChars; ++i) + { + if (src[i] == 0) + { + position += i + 1; + return String::fromUTF8 (src, i); + } + } + } + + return InputStream::readString(); +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.h new file mode 100644 index 0000000..8f1e633 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_BufferedInputStream.h @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED +#define JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** Wraps another input stream, and reads from it using an intermediate buffer + + If you're using an input stream such as a file input stream, and making lots of + small read accesses to it, it's probably sensible to wrap it in one of these, + so that the source stream gets accessed in larger chunk sizes, meaning less + work for the underlying stream. +*/ +class JUCE_API BufferedInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a BufferedInputStream from an input source. + + @param sourceStream the source stream to read from + @param bufferSize the size of reservoir to use to buffer the source + @param deleteSourceWhenDestroyed whether the sourceStream that is passed in should be + deleted by this object when it is itself deleted. + */ + BufferedInputStream (InputStream* sourceStream, + int bufferSize, + bool deleteSourceWhenDestroyed); + + /** Creates a BufferedInputStream from an input source. + + @param sourceStream the source stream to read from - the source stream must not + be deleted until this object has been destroyed. + @param bufferSize the size of reservoir to use to buffer the source + */ + BufferedInputStream (InputStream& sourceStream, int bufferSize); + + /** Destructor. + + This may also delete the source stream, if that option was chosen when the + buffered stream was created. + */ + ~BufferedInputStream(); + + + //============================================================================== + int64 getTotalLength() override; + int64 getPosition() override; + bool setPosition (int64 newPosition) override; + int read (void* destBuffer, int maxBytesToRead) override; + String readString() override; + bool isExhausted() override; + + +private: + //============================================================================== + OptionalScopedPointer source; + int bufferSize; + int64 position, lastReadPos, bufferStart, bufferOverlap; + HeapBlock buffer; + void ensureBuffered(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferedInputStream) +}; + +#endif // JUCE_BUFFEREDINPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.cpp new file mode 100644 index 0000000..51b9db9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.cpp @@ -0,0 +1,56 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +FileInputSource::FileInputSource (const File& f, bool useFileTimeInHash) + : file (f), useFileTimeInHashGeneration (useFileTimeInHash) +{ +} + +FileInputSource::~FileInputSource() +{ +} + +InputStream* FileInputSource::createInputStream() +{ + return file.createInputStream(); +} + +InputStream* FileInputSource::createInputStreamFor (const String& relatedItemPath) +{ + return file.getSiblingFile (relatedItemPath).createInputStream(); +} + +int64 FileInputSource::hashCode() const +{ + int64 h = file.hashCode(); + + if (useFileTimeInHashGeneration) + h ^= file.getLastModificationTime().toMilliseconds(); + + return h; +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.h b/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.h new file mode 100644 index 0000000..d2e6d86 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_FileInputSource.h @@ -0,0 +1,66 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_FILEINPUTSOURCE_H_INCLUDED +#define JUCE_FILEINPUTSOURCE_H_INCLUDED + + +//============================================================================== +/** + A type of InputSource that represents a normal file. + + @see InputSource +*/ +class JUCE_API FileInputSource : public InputSource +{ +public: + //============================================================================== + /** Creates a FileInputSource for a file. + If the useFileTimeInHashGeneration parameter is true, then this object's + hashCode() method will incorporate the file time into its hash code; if + false, only the file name will be used for the hash. + */ + FileInputSource (const File& file, bool useFileTimeInHashGeneration = false); + + /** Destructor. */ + ~FileInputSource(); + + InputStream* createInputStream(); + InputStream* createInputStreamFor (const String& relatedItemPath); + int64 hashCode() const; + +private: + //============================================================================== + const File file; + bool useFileTimeInHashGeneration; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputSource) +}; + + +#endif // JUCE_FILEINPUTSOURCE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_InputSource.h b/JuceLibraryCode/modules/juce_core/streams/juce_InputSource.h new file mode 100644 index 0000000..0e13ac5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_InputSource.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INPUTSOURCE_H_INCLUDED +#define JUCE_INPUTSOURCE_H_INCLUDED + + +//============================================================================== +/** + A lightweight object that can create a stream to read some kind of resource. + + This may be used to refer to a file, or some other kind of source, allowing a + caller to create an input stream that can read from it when required. + + @see FileInputSource +*/ +class JUCE_API InputSource +{ +public: + //============================================================================== + InputSource() noexcept {} + + /** Destructor. */ + virtual ~InputSource() {} + + //============================================================================== + /** Returns a new InputStream to read this item. + + @returns an inputstream that the caller will delete, or nullptr if + the filename isn't found. + */ + virtual InputStream* createInputStream() = 0; + + /** Returns a new InputStream to read an item, relative. + + @param relatedItemPath the relative pathname of the resource that is required + @returns an inputstream that the caller will delete, or nullptr if + the item isn't found. + */ + virtual InputStream* createInputStreamFor (const String& relatedItemPath) = 0; + + /** Returns a hash code that uniquely represents this item. + */ + virtual int64 hashCode() const = 0; + + +private: + //============================================================================== + JUCE_LEAK_DETECTOR (InputSource) +}; + + +#endif // JUCE_INPUTSOURCE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.cpp new file mode 100644 index 0000000..07b9976 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.cpp @@ -0,0 +1,236 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +int64 InputStream::getNumBytesRemaining() +{ + int64 len = getTotalLength(); + + if (len >= 0) + len -= getPosition(); + + return len; +} + +char InputStream::readByte() +{ + char temp = 0; + read (&temp, 1); + return temp; +} + +bool InputStream::readBool() +{ + return readByte() != 0; +} + +short InputStream::readShort() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::littleEndianShort (temp); + + return 0; +} + +short InputStream::readShortBigEndian() +{ + char temp[2]; + + if (read (temp, 2) == 2) + return (short) ByteOrder::bigEndianShort (temp); + + return 0; +} + +int InputStream::readInt() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::littleEndianInt (temp); + + return 0; +} + +int InputStream::readIntBigEndian() +{ + char temp[4]; + + if (read (temp, 4) == 4) + return (int) ByteOrder::bigEndianInt (temp); + + return 0; +} + +int InputStream::readCompressedInt() +{ + const uint8 sizeByte = (uint8) readByte(); + if (sizeByte == 0) + return 0; + + const int numBytes = (sizeByte & 0x7f); + if (numBytes > 4) + { + jassertfalse; // trying to read corrupt data - this method must only be used + // to read data that was written by OutputStream::writeCompressedInt() + return 0; + } + + char bytes[4] = { 0, 0, 0, 0 }; + if (read (bytes, numBytes) != numBytes) + return 0; + + const int num = (int) ByteOrder::littleEndianInt (bytes); + return (sizeByte >> 7) ? -num : num; +} + +int64 InputStream::readInt64() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfBigEndian (n.asInt64); + + return 0; +} + +int64 InputStream::readInt64BigEndian() +{ + union { uint8 asBytes[8]; uint64 asInt64; } n; + + if (read (n.asBytes, 8) == 8) + return (int64) ByteOrder::swapIfLittleEndian (n.asInt64); + + return 0; +} + +float InputStream::readFloat() +{ + // the union below relies on these types being the same size... + static_jassert (sizeof (int32) == sizeof (float)); + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readInt(); + return n.asFloat; +} + +float InputStream::readFloatBigEndian() +{ + union { int32 asInt; float asFloat; } n; + n.asInt = (int32) readIntBigEndian(); + return n.asFloat; +} + +double InputStream::readDouble() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64(); + return n.asDouble; +} + +double InputStream::readDoubleBigEndian() +{ + union { int64 asInt; double asDouble; } n; + n.asInt = readInt64BigEndian(); + return n.asDouble; +} + +String InputStream::readString() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +String InputStream::readNextLine() +{ + MemoryBlock buffer (256); + char* data = static_cast (buffer.getData()); + size_t i = 0; + + while ((data[i] = readByte()) != 0) + { + if (data[i] == '\n') + break; + + if (data[i] == '\r') + { + const int64 lastPos = getPosition(); + + if (readByte() != '\n') + setPosition (lastPos); + + break; + } + + if (++i >= buffer.getSize()) + { + buffer.setSize (buffer.getSize() + 512); + data = static_cast (buffer.getData()); + } + } + + return String::fromUTF8 (data, (int) i); +} + +size_t InputStream::readIntoMemoryBlock (MemoryBlock& block, ssize_t numBytes) +{ + MemoryOutputStream mo (block, true); + return (size_t) mo.writeFromInputStream (*this, numBytes); +} + +String InputStream::readEntireStreamAsString() +{ + MemoryOutputStream mo; + mo << *this; + return mo.toString(); +} + +//============================================================================== +void InputStream::skipNextBytes (int64 numBytesToSkip) +{ + if (numBytesToSkip > 0) + { + const int skipBufferSize = (int) jmin (numBytesToSkip, (int64) 16384); + HeapBlock temp ((size_t) skipBufferSize); + + while (numBytesToSkip > 0 && ! isExhausted()) + numBytesToSkip -= read (temp, (int) jmin (numBytesToSkip, (int64) skipBufferSize)); + } +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.h new file mode 100644 index 0000000..ebdc795 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_InputStream.h @@ -0,0 +1,266 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INPUTSTREAM_H_INCLUDED +#define JUCE_INPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** The base class for streams that read data. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see OutputStream, MemoryInputStream, BufferedInputStream, FileInputStream +*/ +class JUCE_API InputStream +{ +public: + /** Destructor. */ + virtual ~InputStream() {} + + //============================================================================== + /** Returns the total number of bytes available for reading in this stream. + + Note that this is the number of bytes available from the start of the + stream, not from the current position. + + If the size of the stream isn't actually known, this will return -1. + + @see getNumBytesRemaining + */ + virtual int64 getTotalLength() = 0; + + /** Returns the number of bytes available for reading, or a negative value if + the remaining length is not known. + @see getTotalLength + */ + int64 getNumBytesRemaining(); + + /** Returns true if the stream has no more data to read. */ + virtual bool isExhausted() = 0; + + //============================================================================== + /** Reads some data from the stream into a memory buffer. + + This is the only read method that subclasses actually need to implement, as the + InputStream base class implements the other read methods in terms of this one (although + it's often more efficient for subclasses to implement them directly). + + @param destBuffer the destination buffer for the data. This must not be null. + @param maxBytesToRead the maximum number of bytes to read - make sure the + memory block passed in is big enough to contain this + many bytes. This value must not be negative. + + @returns the actual number of bytes that were read, which may be less than + maxBytesToRead if the stream is exhausted before it gets that far + */ + virtual int read (void* destBuffer, int maxBytesToRead) = 0; + + /** Reads a byte from the stream. + If the stream is exhausted, this will return zero. + @see OutputStream::writeByte + */ + virtual char readByte(); + + /** Reads a boolean from the stream. + The bool is encoded as a single byte - non-zero for true, 0 for false. + If the stream is exhausted, this will return false. + @see OutputStream::writeBool + */ + virtual bool readBool(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + If the next two bytes read are byte1 and byte2, this returns (byte1 | (byte2 << 8)). + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeShort, readShortBigEndian + */ + virtual short readShort(); + + /** Reads two bytes from the stream as a little-endian 16-bit value. + If the next two bytes read are byte1 and byte2, this returns (byte2 | (byte1 << 8)). + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeShortBigEndian, readShort + */ + virtual short readShortBigEndian(); + + /** Reads four bytes from the stream as a little-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt, readIntBigEndian + */ + virtual int readInt(); + + /** Reads four bytes from the stream as a big-endian 32-bit value. + + If the next four bytes are byte1 to byte4, this returns + (byte4 | (byte3 << 8) | (byte2 << 16) | (byte1 << 24)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeIntBigEndian, readInt + */ + virtual int readIntBigEndian(); + + /** Reads eight bytes from the stream as a little-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24) | (byte5 << 32) | (byte6 << 40) | (byte7 << 48) | (byte8 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64, readInt64BigEndian + */ + virtual int64 readInt64(); + + /** Reads eight bytes from the stream as a big-endian 64-bit value. + + If the next eight bytes are byte1 to byte8, this returns + (byte8 | (byte7 << 8) | (byte6 << 16) | (byte5 << 24) | (byte4 << 32) | (byte3 << 40) | (byte2 << 48) | (byte1 << 56)). + + If the stream is exhausted partway through reading the bytes, this will return zero. + + @see OutputStream::writeInt64BigEndian, readInt64 + */ + virtual int64 readInt64BigEndian(); + + /** Reads four bytes as a 32-bit floating point value. + The raw 32-bit encoding of the float is read from the stream as a little-endian int. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeFloat, readDouble + */ + virtual float readFloat(); + + /** Reads four bytes as a 32-bit floating point value. + The raw 32-bit encoding of the float is read from the stream as a big-endian int. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeFloatBigEndian, readDoubleBigEndian + */ + virtual float readFloatBigEndian(); + + /** Reads eight bytes as a 64-bit floating point value. + The raw 64-bit encoding of the double is read from the stream as a little-endian int64. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeDouble, readFloat + */ + virtual double readDouble(); + + /** Reads eight bytes as a 64-bit floating point value. + The raw 64-bit encoding of the double is read from the stream as a big-endian int64. + If the stream is exhausted partway through reading the bytes, this will return zero. + @see OutputStream::writeDoubleBigEndian, readFloatBigEndian + */ + virtual double readDoubleBigEndian(); + + /** Reads an encoded 32-bit number from the stream using a space-saving compressed format. + For small values, this is more space-efficient than using readInt() and OutputStream::writeInt() + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + @see OutputStream::writeCompressedInt() + */ + virtual int readCompressedInt(); + + //============================================================================== + /** Reads a UTF-8 string from the stream, up to the next linefeed or carriage return. + + This will read up to the next "\n" or "\r\n" or end-of-stream. + + After this call, the stream's position will be left pointing to the next character + following the line-feed, but the linefeeds aren't included in the string that + is returned. + */ + virtual String readNextLine(); + + /** Reads a zero-terminated UTF-8 string from the stream. + + This will read characters from the stream until it hits a null character + or end-of-stream. + + @see OutputStream::writeString, readEntireStreamAsString + */ + virtual String readString(); + + /** Tries to read the whole stream and turn it into a string. + + This will read from the stream's current position until the end-of-stream. + It can read from UTF-8 data, or UTF-16 if it detects suitable header-bytes. + */ + virtual String readEntireStreamAsString(); + + /** Reads from the stream and appends the data to a MemoryBlock. + + @param destBlock the block to append the data onto + @param maxNumBytesToRead if this is a positive value, it sets a limit to the number + of bytes that will be read - if it's negative, data + will be read until the stream is exhausted. + @returns the number of bytes that were added to the memory block + */ + virtual size_t readIntoMemoryBlock (MemoryBlock& destBlock, + ssize_t maxNumBytesToRead = -1); + + //============================================================================== + /** Returns the offset of the next byte that will be read from the stream. + @see setPosition + */ + virtual int64 getPosition() = 0; + + /** Tries to move the current read position of the stream. + + The position is an absolute number of bytes from the stream's start. + + Some streams might not be able to do this, in which case they should do + nothing and return false. Others might be able to manage it by resetting + themselves and skipping to the correct position, although this is + obviously a bit slow. + + @returns true if the stream manages to reposition itself correctly + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Reads and discards a number of bytes from the stream. + + Some input streams might implement this efficiently, but the base + class will just keep reading data until the requisite number of bytes + have been done. + */ + virtual void skipNextBytes (int64 numBytesToSkip); + + +protected: + //============================================================================== + InputStream() noexcept {} + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputStream) +}; + +#endif // JUCE_INPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp new file mode 100644 index 0000000..82b2d14 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp @@ -0,0 +1,159 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +MemoryInputStream::MemoryInputStream (const void* const sourceData, + const size_t sourceDataSize, + const bool keepInternalCopy) + : data (sourceData), + dataSize (sourceDataSize), + position (0) +{ + if (keepInternalCopy) + createInternalCopy(); +} + +MemoryInputStream::MemoryInputStream (const MemoryBlock& sourceData, + const bool keepInternalCopy) + : data (sourceData.getData()), + dataSize (sourceData.getSize()), + position (0) +{ + if (keepInternalCopy) + createInternalCopy(); +} + +void MemoryInputStream::createInternalCopy() +{ + internalCopy.malloc (dataSize); + memcpy (internalCopy, data, dataSize); + data = internalCopy; +} + +MemoryInputStream::~MemoryInputStream() +{ +} + +int64 MemoryInputStream::getTotalLength() +{ + return (int64) dataSize; +} + +int MemoryInputStream::read (void* const buffer, const int howMany) +{ + jassert (buffer != nullptr && howMany >= 0); + + const int num = jmin (howMany, (int) (dataSize - position)); + if (num <= 0) + return 0; + + memcpy (buffer, addBytesToPointer (data, position), (size_t) num); + position += (unsigned int) num; + return num; +} + +bool MemoryInputStream::isExhausted() +{ + return position >= dataSize; +} + +bool MemoryInputStream::setPosition (const int64 pos) +{ + position = (size_t) jlimit ((int64) 0, (int64) dataSize, pos); + return true; +} + +int64 MemoryInputStream::getPosition() +{ + return (int64) position; +} + + +//============================================================================== +#if JUCE_UNIT_TESTS + +class MemoryStreamTests : public UnitTest +{ +public: + MemoryStreamTests() : UnitTest ("MemoryInputStream & MemoryOutputStream") {} + + void runTest() + { + beginTest ("Basics"); + Random r = getRandom(); + + int randomInt = r.nextInt(); + int64 randomInt64 = r.nextInt64(); + double randomDouble = r.nextDouble(); + String randomString (createRandomWideCharString (r)); + + MemoryOutputStream mo; + mo.writeInt (randomInt); + mo.writeIntBigEndian (randomInt); + mo.writeCompressedInt (randomInt); + mo.writeString (randomString); + mo.writeInt64 (randomInt64); + mo.writeInt64BigEndian (randomInt64); + mo.writeDouble (randomDouble); + mo.writeDoubleBigEndian (randomDouble); + + MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); + expect (mi.readInt() == randomInt); + expect (mi.readIntBigEndian() == randomInt); + expect (mi.readCompressedInt() == randomInt); + expectEquals (mi.readString(), randomString); + expect (mi.readInt64() == randomInt64); + expect (mi.readInt64BigEndian() == randomInt64); + expect (mi.readDouble() == randomDouble); + expect (mi.readDoubleBigEndian() == randomDouble); + } + + static String createRandomWideCharString (Random& r) + { + juce_wchar buffer [50] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } +}; + +static MemoryStreamTests memoryInputStreamUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.h new file mode 100644 index 0000000..013bc39 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.h @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYINPUTSTREAM_H_INCLUDED +#define JUCE_MEMORYINPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + Allows a block of data to be accessed as a stream. + + This can either be used to refer to a shared block of memory, or can make its + own internal copy of the data when the MemoryInputStream is created. +*/ +class JUCE_API MemoryInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a MemoryInputStream. + + @param sourceData the block of data to use as the stream's source + @param sourceDataSize the number of bytes in the source data block + @param keepInternalCopyOfData if false, the stream will just keep a pointer to + the source data, so this data shouldn't be changed + for the lifetime of the stream; if this parameter is + true, the stream will make its own copy of the + data and use that. + */ + MemoryInputStream (const void* sourceData, + size_t sourceDataSize, + bool keepInternalCopyOfData); + + /** Creates a MemoryInputStream. + + @param data a block of data to use as the stream's source + @param keepInternalCopyOfData if false, the stream will just keep a reference to + the source data, so this data shouldn't be changed + for the lifetime of the stream; if this parameter is + true, the stream will make its own copy of the + data and use that. + */ + MemoryInputStream (const MemoryBlock& data, + bool keepInternalCopyOfData); + + /** Destructor. */ + ~MemoryInputStream(); + + /** Returns a pointer to the source data block from which this stream is reading. */ + const void* getData() const noexcept { return data; } + + /** Returns the number of bytes of source data in the block from which this stream is reading. */ + size_t getDataSize() const noexcept { return dataSize; } + + //============================================================================== + int64 getPosition() override; + bool setPosition (int64 pos) override; + int64 getTotalLength() override; + bool isExhausted() override; + int read (void* destBuffer, int maxBytesToRead) override; + +private: + //============================================================================== + const void* data; + size_t dataSize, position; + HeapBlock internalCopy; + + void createInternalCopy(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryInputStream) +}; + +#endif // JUCE_MEMORYINPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.cpp new file mode 100644 index 0000000..adbaed2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.cpp @@ -0,0 +1,214 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +MemoryOutputStream::MemoryOutputStream (const size_t initialSize) + : blockToUse (&internalBlock), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + internalBlock.setSize (initialSize, false); +} + +MemoryOutputStream::MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + const bool appendToExistingBlockContent) + : blockToUse (&memoryBlockToWriteTo), externalData (nullptr), + position (0), size (0), availableSize (0) +{ + if (appendToExistingBlockContent) + position = size = memoryBlockToWriteTo.getSize(); +} + +MemoryOutputStream::MemoryOutputStream (void* destBuffer, size_t destBufferSize) + : blockToUse (nullptr), externalData (destBuffer), + position (0), size (0), availableSize (destBufferSize) +{ + jassert (externalData != nullptr); // This must be a valid pointer. +} + +MemoryOutputStream::~MemoryOutputStream() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::flush() +{ + trimExternalBlockSize(); +} + +void MemoryOutputStream::trimExternalBlockSize() +{ + if (blockToUse != &internalBlock && blockToUse != nullptr) + blockToUse->setSize (size, false); +} + +void MemoryOutputStream::preallocate (const size_t bytesToPreallocate) +{ + if (blockToUse != nullptr) + blockToUse->ensureSize (bytesToPreallocate + 1); +} + +void MemoryOutputStream::reset() noexcept +{ + position = 0; + size = 0; +} + +char* MemoryOutputStream::prepareToWrite (size_t numBytes) +{ + jassert ((ssize_t) numBytes >= 0); + size_t storageNeeded = position + numBytes; + + char* data; + + if (blockToUse != nullptr) + { + if (storageNeeded >= blockToUse->getSize()) + blockToUse->ensureSize ((storageNeeded + jmin (storageNeeded / 2, (size_t) (1024 * 1024)) + 32) & ~31u); + + data = static_cast (blockToUse->getData()); + } + else + { + if (storageNeeded > availableSize) + return nullptr; + + data = static_cast (externalData); + } + + char* const writePointer = data + position; + position += numBytes; + size = jmax (size, position); + return writePointer; +} + +bool MemoryOutputStream::write (const void* const buffer, size_t howMany) +{ + jassert (buffer != nullptr); + + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memcpy (dest, buffer, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::writeRepeatedByte (uint8 byte, size_t howMany) +{ + if (howMany == 0) + return true; + + if (char* dest = prepareToWrite (howMany)) + { + memset (dest, byte, howMany); + return true; + } + + return false; +} + +bool MemoryOutputStream::appendUTF8Char (juce_wchar c) +{ + if (char* dest = prepareToWrite (CharPointer_UTF8::getBytesRequiredFor (c))) + { + CharPointer_UTF8 (dest).write (c); + return true; + } + + return false; +} + +MemoryBlock MemoryOutputStream::getMemoryBlock() const +{ + return MemoryBlock (getData(), getDataSize()); +} + +const void* MemoryOutputStream::getData() const noexcept +{ + if (blockToUse == nullptr) + return externalData; + + if (blockToUse->getSize() > size) + static_cast (blockToUse->getData()) [size] = 0; + + return blockToUse->getData(); +} + +bool MemoryOutputStream::setPosition (int64 newPosition) +{ + if (newPosition <= (int64) size) + { + // ok to seek backwards + position = jlimit ((size_t) 0, size, (size_t) newPosition); + return true; + } + + // can't move beyond the end of the stream.. + return false; +} + +int64 MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite) +{ + // before writing from an input, see if we can preallocate to make it more efficient.. + int64 availableData = source.getTotalLength() - source.getPosition(); + + if (availableData > 0) + { + if (maxNumBytesToWrite > availableData || maxNumBytesToWrite < 0) + maxNumBytesToWrite = availableData; + + if (blockToUse != nullptr) + preallocate (blockToUse->getSize() + (size_t) maxNumBytesToWrite); + } + + return OutputStream::writeFromInputStream (source, maxNumBytesToWrite); +} + +String MemoryOutputStream::toUTF8() const +{ + const char* const d = static_cast (getData()); + return String (CharPointer_UTF8 (d), CharPointer_UTF8 (d + getDataSize())); +} + +String MemoryOutputStream::toString() const +{ + return String::createStringFromData (getData(), (int) getDataSize()); +} + +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead) +{ + const size_t dataSize = streamToRead.getDataSize(); + + if (dataSize > 0) + stream.write (streamToRead.getData(), dataSize); + + return stream; +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.h new file mode 100644 index 0000000..2e764ef --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryOutputStream.h @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED +#define JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + Writes data to an internal memory buffer, which grows as required. + + The data that was written into the stream can then be accessed later as + a contiguous block of memory. +*/ +class JUCE_API MemoryOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates an empty memory stream, ready to be written into. + @param initialSize the intial amount of capacity to allocate for writing into + */ + MemoryOutputStream (size_t initialSize = 256); + + /** Creates a memory stream for writing into into a pre-existing MemoryBlock object. + + Note that the destination block will always be larger than the amount of data + that has been written to the stream, because the MemoryOutputStream keeps some + spare capactity at its end. To trim the block's size down to fit the actual + data, call flush(), or delete the MemoryOutputStream. + + @param memoryBlockToWriteTo the block into which new data will be written. + @param appendToExistingBlockContent if this is true, the contents of the block will be + kept, and new data will be appended to it. If false, + the block will be cleared before use + */ + MemoryOutputStream (MemoryBlock& memoryBlockToWriteTo, + bool appendToExistingBlockContent); + + /** Creates a MemoryOutputStream that will write into a user-supplied, fixed-size + block of memory. + When using this mode, the stream will write directly into this memory area until + it's full, at which point write operations will fail. + */ + MemoryOutputStream (void* destBuffer, size_t destBufferSize); + + /** Destructor. + This will free any data that was written to it. + */ + ~MemoryOutputStream(); + + //============================================================================== + /** Returns a pointer to the data that has been written to the stream. + @see getDataSize + */ + const void* getData() const noexcept; + + /** Returns the number of bytes of data that have been written to the stream. + @see getData + */ + size_t getDataSize() const noexcept { return size; } + + /** Resets the stream, clearing any data that has been written to it so far. */ + void reset() noexcept; + + /** Increases the internal storage capacity to be able to contain at least the specified + amount of data without needing to be resized. + */ + void preallocate (size_t bytesToPreallocate); + + /** Appends the utf-8 bytes for a unicode character */ + bool appendUTF8Char (juce_wchar character); + + /** Returns a String created from the (UTF8) data that has been written to the stream. */ + String toUTF8() const; + + /** Attempts to detect the encoding of the data and convert it to a string. + @see String::createStringFromData + */ + String toString() const; + + /** Returns a copy of the stream's data as a memory block. */ + MemoryBlock getMemoryBlock() const; + + //============================================================================== + /** If the stream is writing to a user-supplied MemoryBlock, this will trim any excess + capacity off the block, so that its length matches the amount of actual data that + has been written so far. + */ + void flush(); + + bool write (const void*, size_t) override; + int64 getPosition() override { return (int64) position; } + bool setPosition (int64) override; + int64 writeFromInputStream (InputStream&, int64 maxNumBytesToWrite) override; + bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) override; + +private: + //============================================================================== + MemoryBlock* const blockToUse; + MemoryBlock internalBlock; + void* externalData; + size_t position, size, availableSize; + + void trimExternalBlockSize(); + char* prepareToWrite (size_t); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryOutputStream) +}; + +/** Copies all the data that has been written to a MemoryOutputStream into another stream. */ +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryOutputStream& streamToRead); + + +#endif // JUCE_MEMORYOUTPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.cpp new file mode 100644 index 0000000..c67e3fb --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.cpp @@ -0,0 +1,351 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if JUCE_DEBUG + +struct DanglingStreamChecker +{ + DanglingStreamChecker() {} + + ~DanglingStreamChecker() + { + /* + It's always a bad idea to leak any object, but if you're leaking output + streams, then there's a good chance that you're failing to flush a file + to disk properly, which could result in corrupted data and other similar + nastiness.. + */ + jassert (activeStreams.size() == 0); + } + + Array activeStreams; +}; + +static DanglingStreamChecker danglingStreamChecker; +#endif + +//============================================================================== +OutputStream::OutputStream() + : newLineString (NewLine::getDefault()) +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.add (this); + #endif +} + +OutputStream::~OutputStream() +{ + #if JUCE_DEBUG + danglingStreamChecker.activeStreams.removeFirstMatchingValue (this); + #endif +} + +//============================================================================== +bool OutputStream::writeBool (const bool b) +{ + return writeByte (b ? (char) 1 + : (char) 0); +} + +bool OutputStream::writeByte (char byte) +{ + return write (&byte, 1); +} + +bool OutputStream::writeRepeatedByte (uint8 byte, size_t numTimesToRepeat) +{ + for (size_t i = 0; i < numTimesToRepeat; ++i) + if (! writeByte ((char) byte)) + return false; + + return true; +} + +bool OutputStream::writeShort (short value) +{ + const unsigned short v = ByteOrder::swapIfBigEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeShortBigEndian (short value) +{ + const unsigned short v = ByteOrder::swapIfLittleEndian ((unsigned short) value); + return write (&v, 2); +} + +bool OutputStream::writeInt (int value) +{ + const unsigned int v = ByteOrder::swapIfBigEndian ((unsigned int) value); + return write (&v, 4); +} + +bool OutputStream::writeIntBigEndian (int value) +{ + const unsigned int v = ByteOrder::swapIfLittleEndian ((unsigned int) value); + return write (&v, 4); +} + +bool OutputStream::writeCompressedInt (int value) +{ + unsigned int un = (value < 0) ? (unsigned int) -value + : (unsigned int) value; + + uint8 data[5]; + int num = 0; + + while (un > 0) + { + data[++num] = (uint8) un; + un >>= 8; + } + + data[0] = (uint8) num; + + if (value < 0) + data[0] |= 0x80; + + return write (data, (size_t) num + 1); +} + +bool OutputStream::writeInt64 (int64 value) +{ + const uint64 v = ByteOrder::swapIfBigEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeInt64BigEndian (int64 value) +{ + const uint64 v = ByteOrder::swapIfLittleEndian ((uint64) value); + return write (&v, 8); +} + +bool OutputStream::writeFloat (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeInt (n.asInt); +} + +bool OutputStream::writeFloatBigEndian (float value) +{ + union { int asInt; float asFloat; } n; + n.asFloat = value; + return writeIntBigEndian (n.asInt); +} + +bool OutputStream::writeDouble (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64 (n.asInt); +} + +bool OutputStream::writeDoubleBigEndian (double value) +{ + union { int64 asInt; double asDouble; } n; + n.asDouble = value; + return writeInt64BigEndian (n.asInt); +} + +bool OutputStream::writeString (const String& text) +{ + #if (JUCE_STRING_UTF_TYPE == 8) + return write (text.toRawUTF8(), text.getNumBytesAsUTF8() + 1); + #else + // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind + // if lots of large, persistent strings were to be written to streams). + const size_t numBytes = text.getNumBytesAsUTF8() + 1; + HeapBlock temp (numBytes); + text.copyToUTF8 (temp, numBytes); + return write (temp, numBytes); + #endif +} + +bool OutputStream::writeText (const String& text, const bool asUTF16, + const bool writeUTF16ByteOrderMark) +{ + if (asUTF16) + { + if (writeUTF16ByteOrderMark) + write ("\x0ff\x0fe", 2); + + String::CharPointerType src (text.getCharPointer()); + bool lastCharWasReturn = false; + + for (;;) + { + const juce_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + if (c == '\n' && ! lastCharWasReturn) + writeShort ((short) '\r'); + + lastCharWasReturn = (c == L'\r'); + + if (! writeShort ((short) c)) + return false; + } + } + else + { + const char* src = text.toUTF8(); + const char* t = src; + + for (;;) + { + if (*t == '\n') + { + if (t > src) + if (! write (src, (size_t) (t - src))) + return false; + + if (! write ("\r\n", 2)) + return false; + + src = t + 1; + } + else if (*t == '\r') + { + if (t[1] == '\n') + ++t; + } + else if (*t == 0) + { + if (t > src) + if (! write (src, (size_t) (t - src))) + return false; + + break; + } + + ++t; + } + } + + return true; +} + +int64 OutputStream::writeFromInputStream (InputStream& source, int64 numBytesToWrite) +{ + if (numBytesToWrite < 0) + numBytesToWrite = std::numeric_limits::max(); + + int64 numWritten = 0; + + while (numBytesToWrite > 0) + { + char buffer [8192]; + const int num = source.read (buffer, (int) jmin (numBytesToWrite, (int64) sizeof (buffer))); + + if (num <= 0) + break; + + write (buffer, (size_t) num); + + numBytesToWrite -= num; + numWritten += num; + } + + return numWritten; +} + +//============================================================================== +void OutputStream::setNewLineString (const String& newLineString_) +{ + newLineString = newLineString_; +} + +//============================================================================== +template +static void writeIntToStream (OutputStream& stream, IntegerType number) +{ + char buffer [NumberToStringConverters::charsNeededForInt]; + char* end = buffer + numElementsInArray (buffer); + const char* start = NumberToStringConverters::numberToString (end, number); + stream.write (start, (size_t) (end - start - 1)); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int number) +{ + writeIntToStream (stream, number); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const int64 number) +{ + writeIntToStream (stream, number); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const double number) +{ + return stream << String (number); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char character) +{ + stream.writeByte (character); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* const text) +{ + stream.write (text, strlen (text)); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data) +{ + if (data.getSize() > 0) + stream.write (data.getData(), data.getSize()); + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead) +{ + FileInputStream in (fileToRead); + + if (in.openedOk()) + return stream << in; + + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead) +{ + stream.writeFromInputStream (streamToRead, -1); + return stream; +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&) +{ + return stream << stream.getNewLineString(); +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.h new file mode 100644 index 0000000..102ab5a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_OutputStream.h @@ -0,0 +1,278 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_OUTPUTSTREAM_H_INCLUDED +#define JUCE_OUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + The base class for streams that write data to some kind of destination. + + Input and output streams are used throughout the library - subclasses can override + some or all of the virtual functions to implement their behaviour. + + @see InputStream, MemoryOutputStream, FileOutputStream +*/ +class JUCE_API OutputStream +{ +protected: + //============================================================================== + OutputStream(); + +public: + /** Destructor. + + Some subclasses might want to do things like call flush() during their + destructors. + */ + virtual ~OutputStream(); + + //============================================================================== + /** If the stream is using a buffer, this will ensure it gets written + out to the destination. */ + virtual void flush() = 0; + + /** Tries to move the stream's output position. + + Not all streams will be able to seek to a new position - this will return + false if it fails to work. + + @see getPosition + */ + virtual bool setPosition (int64 newPosition) = 0; + + /** Returns the stream's current position. + + @see setPosition + */ + virtual int64 getPosition() = 0; + + //============================================================================== + /** Writes a block of data to the stream. + + When creating a subclass of OutputStream, this is the only write method + that needs to be overloaded - the base class has methods for writing other + types of data which use this to do the work. + + @param dataToWrite the target buffer to receive the data. This must not be null. + @param numberOfBytes the number of bytes to write. + @returns false if the write operation fails for some reason + */ + virtual bool write (const void* dataToWrite, + size_t numberOfBytes) = 0; + + //============================================================================== + /** Writes a single byte to the stream. + @returns false if the write operation fails for some reason + @see InputStream::readByte + */ + virtual bool writeByte (char byte); + + /** Writes a boolean to the stream as a single byte. + This is encoded as a binary byte (not as text) with a value of 1 or 0. + @returns false if the write operation fails for some reason + @see InputStream::readBool + */ + virtual bool writeBool (bool boolValue); + + /** Writes a 16-bit integer to the stream in a little-endian byte order. + This will write two bytes to the stream: (value & 0xff), then (value >> 8). + @returns false if the write operation fails for some reason + @see InputStream::readShort + */ + virtual bool writeShort (short value); + + /** Writes a 16-bit integer to the stream in a big-endian byte order. + This will write two bytes to the stream: (value >> 8), then (value & 0xff). + @returns false if the write operation fails for some reason + @see InputStream::readShortBigEndian + */ + virtual bool writeShortBigEndian (short value); + + /** Writes a 32-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt + */ + virtual bool writeInt (int value); + + /** Writes a 32-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readIntBigEndian + */ + virtual bool writeIntBigEndian (int value); + + /** Writes a 64-bit integer to the stream in a little-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64 + */ + virtual bool writeInt64 (int64 value); + + /** Writes a 64-bit integer to the stream in a big-endian byte order. + @returns false if the write operation fails for some reason + @see InputStream::readInt64BigEndian + */ + virtual bool writeInt64BigEndian (int64 value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a little-endian int. + @returns false if the write operation fails for some reason + @see InputStream::readFloat + */ + virtual bool writeFloat (float value); + + /** Writes a 32-bit floating point value to the stream in a binary format. + The binary 32-bit encoding of the float is written as a big-endian int. + @returns false if the write operation fails for some reason + @see InputStream::readFloatBigEndian + */ + virtual bool writeFloatBigEndian (float value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a little-endian 64-bit int. + @returns false if the write operation fails for some reason + @see InputStream::readDouble + */ + virtual bool writeDouble (double value); + + /** Writes a 64-bit floating point value to the stream in a binary format. + The eight raw bytes of the double value are written out as a big-endian 64-bit int. + @see InputStream::readDoubleBigEndian + @returns false if the write operation fails for some reason + */ + virtual bool writeDoubleBigEndian (double value); + + /** Writes a byte to the output stream a given number of times. + @returns false if the write operation fails for some reason + */ + virtual bool writeRepeatedByte (uint8 byte, size_t numTimesToRepeat); + + /** Writes a condensed binary encoding of a 32-bit integer. + + If you're storing a lot of integers which are unlikely to have very large values, + this can save a lot of space, because values under 0xff will only take up 2 bytes, + under 0xffff only 3 bytes, etc. + + The format used is: number of significant bytes + up to 4 bytes in little-endian order. + + @returns false if the write operation fails for some reason + @see InputStream::readCompressedInt + */ + virtual bool writeCompressedInt (int value); + + /** Stores a string in the stream in a binary format. + + This isn't the method to use if you're trying to append text to the end of a + text-file! It's intended for storing a string so that it can be retrieved later + by InputStream::readString(). + + It writes the string to the stream as UTF8, including the null termination character. + + For appending text to a file, instead use writeText, or operator<< + + @returns false if the write operation fails for some reason + @see InputStream::readString, writeText, operator<< + */ + virtual bool writeString (const String& text); + + /** Writes a string of text to the stream. + + It can either write the text as UTF-8 or UTF-16, and can also add the UTF-16 byte-order-mark + bytes (0xff, 0xfe) to indicate the endianness (these should only be used at the start + of a file). + + The method also replaces '\\n' characters in the text with '\\r\\n'. + @returns false if the write operation fails for some reason + */ + virtual bool writeText (const String& text, + bool asUTF16, + bool writeUTF16ByteOrderMark); + + /** Reads data from an input stream and writes it to this stream. + + @param source the stream to read from + @param maxNumBytesToWrite the number of bytes to read from the stream (if this is + less than zero, it will keep reading until the input + is exhausted) + @returns the number of bytes written + */ + virtual int64 writeFromInputStream (InputStream& source, int64 maxNumBytesToWrite); + + //============================================================================== + /** Sets the string that will be written to the stream when the writeNewLine() + method is called. + By default this will be set the value of NewLine::getDefault(). + */ + void setNewLineString (const String& newLineString); + + /** Returns the current new-line string that was set by setNewLineString(). */ + const String& getNewLineString() const noexcept { return newLineString; } + +private: + //============================================================================== + String newLineString; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OutputStream) +}; + +//============================================================================== +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, int64 number); + +/** Writes a number to a stream as 8-bit characters in the default system encoding. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, double number); + +/** Writes a character to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, char character); + +/** Writes a null-terminated text string to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const char* text); + +/** Writes a block of data from a MemoryBlock to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const MemoryBlock& data); + +/** Writes the contents of a file to a stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const File& fileToRead); + +/** Writes the complete contents of an input stream to an output stream. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, InputStream& streamToRead); + +/** Writes a new-line to a stream. + You can use the predefined symbol 'newLine' to invoke this, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + @see OutputStream::setNewLineString +*/ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const NewLine&); + + +#endif // JUCE_OUTPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.cpp new file mode 100644 index 0000000..c998c79 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.cpp @@ -0,0 +1,82 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +SubregionStream::SubregionStream (InputStream* const sourceStream, + const int64 start, const int64 length, + const bool deleteSourceWhenDestroyed) + : source (sourceStream, deleteSourceWhenDestroyed), + startPositionInSourceStream (start), + lengthOfSourceStream (length) +{ + SubregionStream::setPosition (0); +} + +SubregionStream::~SubregionStream() +{ +} + +int64 SubregionStream::getTotalLength() +{ + const int64 srcLen = source->getTotalLength() - startPositionInSourceStream; + + return lengthOfSourceStream >= 0 ? jmin (lengthOfSourceStream, srcLen) + : srcLen; +} + +int64 SubregionStream::getPosition() +{ + return source->getPosition() - startPositionInSourceStream; +} + +bool SubregionStream::setPosition (int64 newPosition) +{ + return source->setPosition (jmax ((int64) 0, newPosition + startPositionInSourceStream)); +} + +int SubregionStream::read (void* destBuffer, int maxBytesToRead) +{ + jassert (destBuffer != nullptr && maxBytesToRead >= 0); + + if (lengthOfSourceStream < 0) + return source->read (destBuffer, maxBytesToRead); + + maxBytesToRead = (int) jmin ((int64) maxBytesToRead, lengthOfSourceStream - getPosition()); + + if (maxBytesToRead <= 0) + return 0; + + return source->read (destBuffer, maxBytesToRead); +} + +bool SubregionStream::isExhausted() +{ + if (lengthOfSourceStream >= 0 && getPosition() >= lengthOfSourceStream) + return true; + + return source->isExhausted(); +} diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.h b/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.h new file mode 100644 index 0000000..d291a03 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/streams/juce_SubregionStream.h @@ -0,0 +1,88 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SUBREGIONSTREAM_H_INCLUDED +#define JUCE_SUBREGIONSTREAM_H_INCLUDED + + +//============================================================================== +/** Wraps another input stream, and reads from a specific part of it. + + This lets you take a subsection of a stream and present it as an entire + stream in its own right. +*/ +class JUCE_API SubregionStream : public InputStream +{ +public: + //============================================================================== + /** Creates a SubregionStream from an input source. + + @param sourceStream the source stream to read from + @param startPositionInSourceStream this is the position in the source stream that + corresponds to position 0 in this stream + @param lengthOfSourceStream this specifies the maximum number of bytes + from the source stream that will be passed through + by this stream. When the position of this stream + exceeds lengthOfSourceStream, it will cause an end-of-stream. + If the length passed in here is greater than the length + of the source stream (as returned by getTotalLength()), + then the smaller value will be used. + Passing a negative value for this parameter means it + will keep reading until the source's end-of-stream. + @param deleteSourceWhenDestroyed whether the sourceStream that is passed in should be + deleted by this object when it is itself deleted. + */ + SubregionStream (InputStream* sourceStream, + int64 startPositionInSourceStream, + int64 lengthOfSourceStream, + bool deleteSourceWhenDestroyed); + + /** Destructor. + + This may also delete the source stream, if that option was chosen when the + buffered stream was created. + */ + ~SubregionStream(); + + + //============================================================================== + int64 getTotalLength() override; + int64 getPosition() override; + bool setPosition (int64 newPosition) override; + int read (void* destBuffer, int maxBytesToRead) override; + bool isExhausted() override; + +private: + //============================================================================== + OptionalScopedPointer source; + const int64 startPositionInSourceStream, lengthOfSourceStream; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubregionStream) +}; + +#endif // JUCE_SUBREGIONSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/system/juce_CompilerSupport.h b/JuceLibraryCode/modules/juce_core/system/juce_CompilerSupport.h new file mode 100644 index 0000000..d090956 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_CompilerSupport.h @@ -0,0 +1,147 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_COMPILERSUPPORT_H_INCLUDED +#define JUCE_COMPILERSUPPORT_H_INCLUDED + +/* This file has some checks to see whether the compiler supports various C++11/14 features, + When these aren't available, the code defines a few workarounds, so that we can still use + some of the newer language features like nullptr and noexcept, even on old compilers. +*/ + +//============================================================================== +// GCC +#if (__cplusplus >= 201103L || defined (__GXX_EXPERIMENTAL_CXX0X__)) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 + #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_DELETED_FUNCTION) + #define JUCE_DELETED_FUNCTION = delete + #endif + + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && ! defined (JUCE_COMPILER_SUPPORTS_LAMBDAS) + #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 + #endif +#endif + +//============================================================================== +// Clang +#if JUCE_CLANG && defined (__has_feature) + #if __has_feature (cxx_nullptr) + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #endif + + #if __has_feature (cxx_noexcept) + #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #endif + + #if __has_feature (cxx_rvalue_references) + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #endif + + #if __has_feature (cxx_deleted_functions) + #define JUCE_DELETED_FUNCTION = delete + #endif + + #if __has_feature (cxx_lambdas) \ + && ((JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8) \ + || (JUCE_IOS && defined (__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) \ + || ! (JUCE_MAC || JUCE_IOS)) + #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 + #endif + + #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #endif + + #ifndef JUCE_COMPILER_SUPPORTS_ARC + #define JUCE_COMPILER_SUPPORTS_ARC 1 + #endif +#endif + +//============================================================================== +// MSVC +#ifdef _MSC_VER + #if _MSC_VER >= 1600 + #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 + #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #endif + + #if _MSC_VER >= 1700 + #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 + #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 + #endif + + #if _MSC_VER >= 1900 + #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 + #define JUCE_DELETED_FUNCTION = delete + #endif +#endif + +//============================================================================== +// Declare some fake versions of nullptr and noexcept, for older compilers: + +#ifndef JUCE_DELETED_FUNCTION + /** This macro can be placed after a method declaration to allow the use of + the C++11 feature "= delete" on all compilers. + On newer compilers that support it, it does the C++11 "= delete", but on + older ones it's just an empty definition. + */ + #define JUCE_DELETED_FUNCTION +#endif + +#if ! DOXYGEN + #if ! JUCE_COMPILER_SUPPORTS_NOEXCEPT + #ifdef noexcept + #undef noexcept + #endif + #define noexcept throw() + #if defined (_MSC_VER) && _MSC_VER > 1600 + #define _ALLOW_KEYWORD_MACROS 1 // (to stop VC2012 complaining) + #endif + #endif + + #if ! JUCE_COMPILER_SUPPORTS_NULLPTR + #ifdef nullptr + #undef nullptr + #endif + #define nullptr (0) + #endif + + #if ! JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL + #undef override + #define override + #endif +#endif + +#endif // JUCE_COMPILERSUPPORT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h b/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h new file mode 100644 index 0000000..3252fdb --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h @@ -0,0 +1,339 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PLATFORMDEFS_H_INCLUDED +#define JUCE_PLATFORMDEFS_H_INCLUDED + +//============================================================================== +/* This file defines miscellaneous macros for debugging, assertions, etc. +*/ + +//============================================================================== +#ifdef JUCE_FORCE_DEBUG + #undef JUCE_DEBUG + + #if JUCE_FORCE_DEBUG + #define JUCE_DEBUG 1 + #endif +#endif + +/** This macro defines the C calling convention used as the standard for Juce calls. */ +#if JUCE_MSVC + #define JUCE_CALLTYPE __stdcall + #define JUCE_CDECL __cdecl +#else + #define JUCE_CALLTYPE + #define JUCE_CDECL +#endif + +//============================================================================== +// Debugging and assertion macros + +#if JUCE_LOG_ASSERTIONS || JUCE_DEBUG + #define juce_LogCurrentAssertion juce::logAssertion (__FILE__, __LINE__); +#else + #define juce_LogCurrentAssertion +#endif + +//============================================================================== +#if JUCE_IOS || JUCE_LINUX || JUCE_ANDROID || JUCE_PPC + /** This will try to break into the debugger if the app is currently being debugged. + If called by an app that's not being debugged, the behaiour isn't defined - it may crash or not, depending + on the platform. + @see jassert() + */ + #define juce_breakDebugger { ::kill (0, SIGTRAP); } +#elif JUCE_USE_MSVC_INTRINSICS + #ifndef __INTEL_COMPILER + #pragma intrinsic (__debugbreak) + #endif + #define juce_breakDebugger { __debugbreak(); } +#elif JUCE_GCC || JUCE_MAC + #if JUCE_NO_INLINE_ASM + #define juce_breakDebugger { } + #else + #define juce_breakDebugger { asm ("int $3"); } + #endif +#else + #define juce_breakDebugger { __asm int 3 } +#endif + +#if JUCE_CLANG && defined (__has_feature) && ! defined (JUCE_ANALYZER_NORETURN) + #if __has_feature (attribute_analyzer_noreturn) + inline void __attribute__((analyzer_noreturn)) juce_assert_noreturn() {} + #define JUCE_ANALYZER_NORETURN juce_assert_noreturn(); + #endif +#endif + +#ifndef JUCE_ANALYZER_NORETURN + #define JUCE_ANALYZER_NORETURN +#endif + +//============================================================================== +#if JUCE_MSVC && ! DOXYGEN + #define MACRO_WITH_FORCED_SEMICOLON(x) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + do { x } while (false) \ + __pragma(warning(pop)) +#else + /** This is the good old C++ trick for creating a macro that forces the user to put + a semicolon after it when they use it. + */ + #define MACRO_WITH_FORCED_SEMICOLON(x) do { x } while (false) +#endif + +//============================================================================== +#if JUCE_DEBUG || DOXYGEN + /** Writes a string to the standard error stream. + This is only compiled in a debug build. + @see Logger::outputDebugString + */ + #define DBG(dbgtext) MACRO_WITH_FORCED_SEMICOLON (juce::String tempDbgBuf; tempDbgBuf << dbgtext; juce::Logger::outputDebugString (tempDbgBuf);) + + //============================================================================== + /** This will always cause an assertion failure. + It is only compiled in a debug build, (unless JUCE_LOG_ASSERTIONS is enabled for your build). + @see jassert + */ + #define jassertfalse MACRO_WITH_FORCED_SEMICOLON (juce_LogCurrentAssertion; if (juce::juce_isRunningUnderDebugger()) juce_breakDebugger; JUCE_ANALYZER_NORETURN) + + //============================================================================== + /** Platform-independent assertion macro. + + This macro gets turned into a no-op when you're building with debugging turned off, so be + careful that the expression you pass to it doesn't perform any actions that are vital for the + correct behaviour of your program! + @see jassertfalse + */ + #define jassert(expression) MACRO_WITH_FORCED_SEMICOLON (if (! (expression)) jassertfalse;) + +#else + //============================================================================== + // If debugging is disabled, these dummy debug and assertion macros are used.. + + #define DBG(dbgtext) + #define jassertfalse MACRO_WITH_FORCED_SEMICOLON (juce_LogCurrentAssertion) + + #if JUCE_LOG_ASSERTIONS + #define jassert(expression) MACRO_WITH_FORCED_SEMICOLON (if (! (expression)) jassertfalse;) + #else + #define jassert(a) MACRO_WITH_FORCED_SEMICOLON ( ; ) + #endif + +#endif + +//============================================================================== +#ifndef DOXYGEN +namespace juce +{ + template struct JuceStaticAssert; + template <> struct JuceStaticAssert { static void dummy() {} }; +} +#endif + +/** A compile-time assertion macro. + If the expression parameter is false, the macro will cause a compile error. (The actual error + message that the compiler generates may be completely bizarre and seem to have no relation to + the place where you put the static_assert though!) +*/ +#define static_jassert(expression) juce::JuceStaticAssert::dummy(); + +/** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. + + For example, instead of + @code + class MyClass + { + etc.. + + private: + MyClass (const MyClass&); + MyClass& operator= (const MyClass&); + };@endcode + + ..you can just write: + + @code + class MyClass + { + etc.. + + private: + JUCE_DECLARE_NON_COPYABLE (MyClass) + };@endcode +*/ +#define JUCE_DECLARE_NON_COPYABLE(className) \ + className (const className&) JUCE_DELETED_FUNCTION;\ + className& operator= (const className&) JUCE_DELETED_FUNCTION; + +/** This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and + JUCE_LEAK_DETECTOR macro for a class. +*/ +#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className) \ + JUCE_DECLARE_NON_COPYABLE(className) \ + JUCE_LEAK_DETECTOR(className) + +/** This macro can be added to class definitions to disable the use of new/delete to + allocate the object on the heap, forcing it to only be used as a stack or member variable. +*/ +#define JUCE_PREVENT_HEAP_ALLOCATION \ + private: \ + static void* operator new (size_t) JUCE_DELETED_FUNCTION; \ + static void operator delete (void*) JUCE_DELETED_FUNCTION; + + +//============================================================================== +#if ! DOXYGEN + #define JUCE_JOIN_MACRO_HELPER(a, b) a ## b + #define JUCE_STRINGIFY_MACRO_HELPER(a) #a +#endif + +/** A good old-fashioned C macro concatenation helper. + This combines two items (which may themselves be macros) into a single string, + avoiding the pitfalls of the ## macro operator. +*/ +#define JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) + +/** A handy C macro for stringifying any symbol, rather than just a macro parameter. +*/ +#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) + + +//============================================================================== +#if JUCE_MSVC && ! defined (DOXYGEN) + #define JUCE_WARNING_HELPER(file, line, mess) message(file "(" JUCE_STRINGIFY (line) ") : Warning: " #mess) + #define JUCE_COMPILER_WARNING(message) __pragma(JUCE_WARNING_HELPER (__FILE__, __LINE__, message)); +#else + #ifndef DOXYGEN + #define JUCE_WARNING_HELPER(mess) message(#mess) + #endif + + /** This macro allows you to emit a custom compiler warning message. + Very handy for marking bits of code as "to-do" items, or for shaming + code written by your co-workers in a way that's hard to ignore. + + GCC and Clang provide the \#warning directive, but MSVC doesn't, so this macro + is a cross-compiler way to get the same functionality as \#warning. + */ + #define JUCE_COMPILER_WARNING(message) _Pragma(JUCE_STRINGIFY (JUCE_WARNING_HELPER (message))); +#endif + + +//============================================================================== +#if JUCE_CATCH_UNHANDLED_EXCEPTIONS + + #define JUCE_TRY try + + #define JUCE_CATCH_ALL catch (...) {} + #define JUCE_CATCH_ALL_ASSERT catch (...) { jassertfalse; } + + #if ! JUCE_MODULE_AVAILABLE_juce_gui_basics + #define JUCE_CATCH_EXCEPTION JUCE_CATCH_ALL + #else + /** Used in try-catch blocks, this macro will send exceptions to the JUCEApplicationBase + object so they can be logged by the application if it wants to. + */ + #define JUCE_CATCH_EXCEPTION \ + catch (const std::exception& e) \ + { \ + juce::JUCEApplicationBase::sendUnhandledException (&e, __FILE__, __LINE__); \ + } \ + catch (...) \ + { \ + juce::JUCEApplicationBase::sendUnhandledException (nullptr, __FILE__, __LINE__); \ + } + #endif + +#else + + #define JUCE_TRY + #define JUCE_CATCH_EXCEPTION + #define JUCE_CATCH_ALL + #define JUCE_CATCH_ALL_ASSERT + +#endif + +//============================================================================== +#if JUCE_DEBUG || DOXYGEN + /** A platform-independent way of forcing an inline function. + Use the syntax: @code + forcedinline void myfunction (int x) + @endcode + */ + #define forcedinline inline +#else + #if JUCE_MSVC + #define forcedinline __forceinline + #else + #define forcedinline inline __attribute__((always_inline)) + #endif +#endif + +#if JUCE_MSVC || DOXYGEN + /** This can be placed before a stack or member variable declaration to tell the compiler + to align it to the specified number of bytes. */ + #define JUCE_ALIGN(bytes) __declspec (align (bytes)) +#else + #define JUCE_ALIGN(bytes) __attribute__ ((aligned (bytes))) +#endif + +//============================================================================== +// Cross-compiler deprecation macros.. +#ifdef DOXYGEN + /** This macro can be used to wrap a function which has been deprecated. */ + #define JUCE_DEPRECATED(functionDef) + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) +#elif JUCE_MSVC && ! JUCE_NO_DEPRECATION_WARNINGS + #define JUCE_DEPRECATED(functionDef) __declspec(deprecated) functionDef + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) __declspec(deprecated) functionDef body +#elif JUCE_GCC && ! JUCE_NO_DEPRECATION_WARNINGS + #define JUCE_DEPRECATED(functionDef) functionDef __attribute__ ((deprecated)) + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef __attribute__ ((deprecated)) body +#else + #define JUCE_DEPRECATED(functionDef) functionDef + #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef body +#endif + +//============================================================================== +#if JUCE_ANDROID && ! DOXYGEN + #define JUCE_MODAL_LOOPS_PERMITTED 0 +#elif ! defined (JUCE_MODAL_LOOPS_PERMITTED) + /** Some operating environments don't provide a modal loop mechanism, so this flag can be + used to disable any functions that try to run a modal loop. */ + #define JUCE_MODAL_LOOPS_PERMITTED 1 +#endif + +//============================================================================== +#if JUCE_GCC + #define JUCE_PACKED __attribute__((packed)) +#elif ! DOXYGEN + #define JUCE_PACKED +#endif + +#endif // JUCE_PLATFORMDEFS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h new file mode 100644 index 0000000..412ee53 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h @@ -0,0 +1,159 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STANDARDHEADER_H_INCLUDED +#define JUCE_STANDARDHEADER_H_INCLUDED + +//============================================================================== +/** Current JUCE version number. + + See also SystemStats::getJUCEVersion() for a string version. +*/ +#define JUCE_MAJOR_VERSION 3 +#define JUCE_MINOR_VERSION 1 +#define JUCE_BUILDNUMBER 1 + +/** Current Juce version number. + + Bits 16 to 32 = major version. + Bits 8 to 16 = minor version. + Bits 0 to 8 = point release. + + See also SystemStats::getJUCEVersion() for a string version. +*/ +#define JUCE_VERSION ((JUCE_MAJOR_VERSION << 16) + (JUCE_MINOR_VERSION << 8) + JUCE_BUILDNUMBER) + + +//============================================================================== +#include "juce_PlatformDefs.h" +#include "juce_CompilerSupport.h" + +//============================================================================== +// Now we'll include some common OS headers.. +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4245 4100) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if JUCE_USE_MSVC_INTRINSICS + #include +#endif + +#if JUCE_MAC || JUCE_IOS + #include +#endif + +#if JUCE_LINUX + #include + + #if __INTEL_COMPILER + #if __ia64__ + #include + #else + #include + #endif + #endif +#endif + +#if JUCE_MSVC && JUCE_DEBUG + #include +#endif + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#if JUCE_ANDROID + #include + #include +#endif + +// undef symbols that are sometimes set by misguided 3rd-party headers.. +#undef check +#undef TYPE_BOOL +#undef max +#undef min + +//============================================================================== +// DLL building settings on Windows +#if JUCE_MSVC + #ifdef JUCE_DLL_BUILD + #define JUCE_API __declspec (dllexport) + #pragma warning (disable: 4251) + #elif defined (JUCE_DLL) + #define JUCE_API __declspec (dllimport) + #pragma warning (disable: 4251) + #endif + #ifdef __INTEL_COMPILER + #pragma warning (disable: 1125) // (virtual override warning) + #endif +#elif defined (JUCE_DLL) || defined (JUCE_DLL_BUILD) + #define JUCE_API __attribute__ ((visibility("default"))) +#endif + +//============================================================================== +#ifndef JUCE_API + #define JUCE_API /**< This macro is added to all juce public class declarations. */ +#endif + +#if JUCE_MSVC && JUCE_DLL_BUILD + #define JUCE_PUBLIC_IN_DLL_BUILD(declaration) public: declaration; private: +#else + #define JUCE_PUBLIC_IN_DLL_BUILD(declaration) declaration; +#endif + +/** This macro is added to all juce public function declarations. */ +#define JUCE_PUBLIC_FUNCTION JUCE_API JUCE_CALLTYPE + +#if (! defined (JUCE_CATCH_DEPRECATED_CODE_MISUSE)) && JUCE_DEBUG && ! DOXYGEN + /** This turns on some non-essential bits of code that should prevent old code from compiling + in cases where method signatures have changed, etc. + */ + #define JUCE_CATCH_DEPRECATED_CODE_MISUSE 1 +#endif + +#ifndef DOXYGEN + #define JUCE_NAMESPACE juce // This old macro is deprecated: you should just use the juce namespace directly. +#endif + +#endif // JUCE_STANDARDHEADER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp new file mode 100644 index 0000000..365e94d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp @@ -0,0 +1,183 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +String SystemStats::getJUCEVersion() +{ + // Some basic tests, to keep an eye on things and make sure these types work ok + // on all platforms. Let me know if any of these assertions fail on your system! + static_jassert (sizeof (pointer_sized_int) == sizeof (void*)); + static_jassert (sizeof (int8) == 1); + static_jassert (sizeof (uint8) == 1); + static_jassert (sizeof (int16) == 2); + static_jassert (sizeof (uint16) == 2); + static_jassert (sizeof (int32) == 4); + static_jassert (sizeof (uint32) == 4); + static_jassert (sizeof (int64) == 8); + static_jassert (sizeof (uint64) == 8); + + return "JUCE v" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) + "." JUCE_STRINGIFY(JUCE_MINOR_VERSION) + "." JUCE_STRINGIFY(JUCE_BUILDNUMBER); +} + +#if JUCE_ANDROID && ! defined (JUCE_DISABLE_JUCE_VERSION_PRINTING) + #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 +#endif + +#if JUCE_DEBUG && ! JUCE_DISABLE_JUCE_VERSION_PRINTING + struct JuceVersionPrinter + { + JuceVersionPrinter() + { + DBG (SystemStats::getJUCEVersion()); + } + }; + + static JuceVersionPrinter juceVersionPrinter; +#endif + + +//============================================================================== +struct CPUInformation +{ + CPUInformation() noexcept + : numCpus (0), hasMMX (false), hasSSE (false), + hasSSE2 (false), hasSSE3 (false), has3DNow (false) + { + initialise(); + } + + void initialise() noexcept; + + int numCpus; + bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow; +}; + +static const CPUInformation& getCPUInformation() noexcept +{ + static CPUInformation info; + return info; +} + +int SystemStats::getNumCpus() noexcept { return getCPUInformation().numCpus; } +bool SystemStats::hasMMX() noexcept { return getCPUInformation().hasMMX; } +bool SystemStats::hasSSE() noexcept { return getCPUInformation().hasSSE; } +bool SystemStats::hasSSE2() noexcept { return getCPUInformation().hasSSE2; } +bool SystemStats::hasSSE3() noexcept { return getCPUInformation().hasSSE3; } +bool SystemStats::has3DNow() noexcept { return getCPUInformation().has3DNow; } + + +//============================================================================== +String SystemStats::getStackBacktrace() +{ + String result; + + #if JUCE_ANDROID || JUCE_MINGW + jassertfalse; // sorry, not implemented yet! + + #elif JUCE_WINDOWS + HANDLE process = GetCurrentProcess(); + SymInitialize (process, nullptr, TRUE); + + void* stack[128]; + int frames = (int) CaptureStackBackTrace (0, numElementsInArray (stack), stack, nullptr); + + HeapBlock symbol; + symbol.calloc (sizeof (SYMBOL_INFO) + 256, 1); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof (SYMBOL_INFO); + + for (int i = 0; i < frames; ++i) + { + DWORD64 displacement = 0; + + if (SymFromAddr (process, (DWORD64) stack[i], &displacement, symbol)) + { + result << i << ": "; + + IMAGEHLP_MODULE64 moduleInfo; + zerostruct (moduleInfo); + moduleInfo.SizeOfStruct = sizeof (moduleInfo); + + if (::SymGetModuleInfo64 (process, symbol->ModBase, &moduleInfo)) + result << moduleInfo.ModuleName << ": "; + + result << symbol->Name << " + 0x" << String::toHexString ((int64) displacement) << newLine; + } + } + + #else + void* stack[128]; + int frames = backtrace (stack, numElementsInArray (stack)); + char** frameStrings = backtrace_symbols (stack, frames); + + for (int i = 0; i < frames; ++i) + result << frameStrings[i] << newLine; + + ::free (frameStrings); + #endif + + return result; +} + +//============================================================================== +static SystemStats::CrashHandlerFunction globalCrashHandler = nullptr; + +#if JUCE_WINDOWS +static LONG WINAPI handleCrash (LPEXCEPTION_POINTERS) +{ + globalCrashHandler(); + return EXCEPTION_EXECUTE_HANDLER; +} +#else +static void handleCrash (int) +{ + globalCrashHandler(); + kill (getpid(), SIGKILL); +} + +int juce_siginterrupt (int sig, int flag); +#endif + +void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) +{ + jassert (handler != nullptr); // This must be a valid function. + globalCrashHandler = handler; + + #if JUCE_WINDOWS + SetUnhandledExceptionFilter (handleCrash); + #else + const int signals[] = { SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGSYS }; + + for (int i = 0; i < numElementsInArray (signals); ++i) + { + ::signal (signals[i], handleCrash); + juce_siginterrupt (signals[i], 1); + } + #endif +} diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h new file mode 100644 index 0000000..023dcc0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h @@ -0,0 +1,196 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SYSTEMSTATS_H_INCLUDED +#define JUCE_SYSTEMSTATS_H_INCLUDED + + +//============================================================================== +/** + Contains methods for finding out about the current hardware and OS configuration. +*/ +class JUCE_API SystemStats +{ +public: + //============================================================================== + /** Returns the current version of JUCE, + See also the JUCE_VERSION, JUCE_MAJOR_VERSION and JUCE_MINOR_VERSION macros. + */ + static String getJUCEVersion(); + + //============================================================================== + /** The set of possible results of the getOperatingSystemType() method. */ + enum OperatingSystemType + { + UnknownOS = 0, + + Linux = 0x2000, + Android = 0x3000, + iOS = 0x8000, + + MacOSX_10_4 = 0x1004, + MacOSX_10_5 = 0x1005, + MacOSX_10_6 = 0x1006, + MacOSX_10_7 = 0x1007, + MacOSX_10_8 = 0x1008, + MacOSX_10_9 = 0x1009, + MacOSX_10_10 = 0x100a, + + Win2000 = 0x4105, + WinXP = 0x4106, + WinVista = 0x4107, + Windows7 = 0x4108, + Windows8_0 = 0x4109, + Windows8_1 = 0x410a, + + Windows = 0x4000, /**< To test whether any version of Windows is running, + you can use the expression ((getOperatingSystemType() & Windows) != 0). */ + }; + + /** Returns the type of operating system we're running on. + + @returns one of the values from the OperatingSystemType enum. + @see getOperatingSystemName + */ + static OperatingSystemType getOperatingSystemType(); + + /** Returns the name of the type of operating system we're running on. + + @returns a string describing the OS type. + @see getOperatingSystemType + */ + static String getOperatingSystemName(); + + /** Returns true if the OS is 64-bit, or false for a 32-bit OS. */ + static bool isOperatingSystem64Bit(); + + /** Returns an environment variable. + If the named value isn't set, this will return the defaultValue string instead. + */ + static String getEnvironmentVariable (const String& name, const String& defaultValue); + + //============================================================================== + /** Returns the current user's name, if available. + @see getFullUserName() + */ + static String getLogonName(); + + /** Returns the current user's full name, if available. + On some OSes, this may just return the same value as getLogonName(). + @see getLogonName() + */ + static String getFullUserName(); + + /** Returns the host-name of the computer. */ + static String getComputerName(); + + /** Returns the language of the user's locale. + The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2) + */ + static String getUserLanguage(); + + /** Returns the region of the user's locale. + The return value is a 2 letter country code (ISO 3166-1 alpha-2). + */ + static String getUserRegion(); + + /** Returns the user's display language. + The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2). + Note that depending on the OS and region, this may also be followed by a dash + and a sub-region code, e.g "en-GB" + */ + static String getDisplayLanguage(); + + /** This will attempt to return some kind of string describing the device. + If no description is available, it'll just return an empty string. You may + want to use this for things like determining the type of phone/iPad, etc. + */ + static String getDeviceDescription(); + + //============================================================================== + // CPU and memory information.. + + /** Returns the number of CPU cores. */ + static int getNumCpus() noexcept; + + /** Returns the approximate CPU speed. + @returns the speed in megahertz, e.g. 1500, 2500, 32000 (depending on + what year you're reading this...) + */ + static int getCpuSpeedInMegaherz(); + + /** Returns a string to indicate the CPU vendor. + Might not be known on some systems. + */ + static String getCpuVendor(); + + static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ + static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ + static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ + static bool hasSSE3() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ + static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ + + //============================================================================== + /** Finds out how much RAM is in the machine. + @returns the approximate number of megabytes of memory, or zero if + something goes wrong when finding out. + */ + static int getMemorySizeInMegabytes(); + + /** Returns the system page-size. + This is only used by programmers with beards. + */ + static int getPageSize(); + + //============================================================================== + /** Returns a backtrace of the current call-stack. + The usefulness of the result will depend on the level of debug symbols + that are available in the executable. + */ + static String getStackBacktrace(); + + /** A void() function type, used by setApplicationCrashHandler(). */ + typedef void (*CrashHandlerFunction)(); + + /** Sets up a global callback function that will be called if the application + executes some kind of illegal instruction. + + You may want to call getStackBacktrace() in your handler function, to find out + where the problem happened and log it, etc. + */ + static void setApplicationCrashHandler (CrashHandlerFunction); + +private: + //============================================================================== + SystemStats(); + + JUCE_DECLARE_NON_COPYABLE (SystemStats) +}; + + +#endif // JUCE_SYSTEMSTATS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h b/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h new file mode 100644 index 0000000..63470b6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h @@ -0,0 +1,201 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TARGETPLATFORM_H_INCLUDED +#define JUCE_TARGETPLATFORM_H_INCLUDED + +//============================================================================== +/* This file figures out which platform is being built, and defines some macros + that the rest of the code can use for OS-specific compilation. + + Macros that will be set here are: + + - One of JUCE_WINDOWS, JUCE_MAC JUCE_LINUX, JUCE_IOS, JUCE_ANDROID, etc. + - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. + - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. + - Either JUCE_INTEL or JUCE_PPC + - Either JUCE_GCC or JUCE_MSVC +*/ + +//============================================================================== +#if (defined (_WIN32) || defined (_WIN64)) + #define JUCE_WIN32 1 + #define JUCE_WINDOWS 1 +#elif defined (JUCE_ANDROID) + #undef JUCE_ANDROID + #define JUCE_ANDROID 1 +#elif defined (LINUX) || defined (__linux__) + #define JUCE_LINUX 1 +#elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) + #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) + #define Component CarbonDummyCompName + #include // (needed to find out what platform we're using) + #undef Point + #undef Component + + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define JUCE_IPHONE 1 + #define JUCE_IOS 1 + #else + #define JUCE_MAC 1 + #endif +#elif defined (__FreeBSD__) + #define JUCE_BSD 1 +#else + #error "Unknown platform!" +#endif + +//============================================================================== +#if JUCE_WINDOWS + #ifdef _MSC_VER + #ifdef _WIN64 + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + #endif + + #ifdef _DEBUG + #define JUCE_DEBUG 1 + #endif + + #ifdef __MINGW32__ + #define JUCE_MINGW 1 + #ifdef __MINGW64__ + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + #endif + + /** If defined, this indicates that the processor is little-endian. */ + #define JUCE_LITTLE_ENDIAN 1 + + #define JUCE_INTEL 1 +#endif + +//============================================================================== +#if JUCE_MAC || JUCE_IOS + + #if defined (DEBUG) || defined (_DEBUG) || ! (defined (NDEBUG) || defined (_NDEBUG)) + #define JUCE_DEBUG 1 + #endif + + #if ! (defined (DEBUG) || defined (_DEBUG) || defined (NDEBUG) || defined (_NDEBUG)) + #warning "Neither NDEBUG or DEBUG has been defined - you should set one of these to make it clear whether this is a release build," + #endif + + #ifdef __LITTLE_ENDIAN__ + #define JUCE_LITTLE_ENDIAN 1 + #else + #define JUCE_BIG_ENDIAN 1 + #endif + + #ifdef __LP64__ + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + + #if defined (__ppc__) || defined (__ppc64__) + #define JUCE_PPC 1 + #elif defined (__arm__) || defined (__arm64__) + #define JUCE_ARM 1 + #else + #define JUCE_INTEL 1 + #endif + + #if JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4 + #error "Building for OSX 10.3 is no longer supported!" + #endif + + #if JUCE_MAC && ! defined (MAC_OS_X_VERSION_10_5) + #error "To build with 10.4 compatibility, use a 10.5 or 10.6 SDK and set the deployment target to 10.4" + #endif +#endif + +//============================================================================== +#if JUCE_LINUX || JUCE_ANDROID + + #ifdef _DEBUG + #define JUCE_DEBUG 1 + #endif + + // Allow override for big-endian Linux platforms + #if defined (__LITTLE_ENDIAN__) || ! defined (JUCE_BIG_ENDIAN) + #define JUCE_LITTLE_ENDIAN 1 + #undef JUCE_BIG_ENDIAN + #else + #undef JUCE_LITTLE_ENDIAN + #define JUCE_BIG_ENDIAN 1 + #endif + + #if defined (__LP64__) || defined (_LP64) || defined (__arm64__) + #define JUCE_64BIT 1 + #else + #define JUCE_32BIT 1 + #endif + + #if defined (__arm__) || defined (__arm64__) + #define JUCE_ARM 1 + #elif __MMX__ || __SSE__ || __amd64__ + #define JUCE_INTEL 1 + #endif +#endif + +//============================================================================== +// Compiler type macros. + +#ifdef __clang__ + #define JUCE_CLANG 1 + #define JUCE_GCC 1 +#elif defined (__GNUC__) + #define JUCE_GCC 1 +#elif defined (_MSC_VER) + #define JUCE_MSVC 1 + + #if _MSC_VER < 1500 + #define JUCE_VC8_OR_EARLIER 1 + + #if _MSC_VER < 1400 + #define JUCE_VC7_OR_EARLIER 1 + + #if _MSC_VER < 1300 + #warning "MSVC 6.0 is no longer supported!" + #endif + #endif + #endif + + #if JUCE_64BIT || ! JUCE_VC7_OR_EARLIER + #define JUCE_USE_MSVC_INTRINSICS 1 + #endif +#else + #error unknown compiler +#endif + +#endif // JUCE_TARGETPLATFORM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_ASCII.h b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_ASCII.h new file mode 100644 index 0000000..a966543 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_ASCII.h @@ -0,0 +1,387 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_ASCII_H_INCLUDED +#define JUCE_CHARPOINTER_ASCII_H_INCLUDED + + +//============================================================================== +/** + Wraps a pointer to a null-terminated ASCII character string, and provides + various methods to operate on the data. + + A valid ASCII string is assumed to not contain any characters above 127. + + @see CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32 +*/ +class CharPointer_ASCII +{ +public: + typedef char CharType; + + inline explicit CharPointer_ASCII (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_ASCII (const CharPointer_ASCII& other) noexcept + : data (other.data) + { + } + + inline CharPointer_ASCII operator= (const CharPointer_ASCII other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_ASCII operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_ASCII other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_ASCII other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_ASCII other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_ASCII other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_ASCII other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_ASCII other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + inline juce_wchar operator*() const noexcept { return (juce_wchar) (uint8) *data; } + + /** Moves this pointer along to the next character in the string. */ + inline CharPointer_ASCII operator++() noexcept + { + ++data; + return *this; + } + + /** Moves this pointer to the previous character in the string. */ + inline CharPointer_ASCII operator--() noexcept + { + --data; + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + inline juce_wchar getAndAdvance() noexcept { return (juce_wchar) (uint8) *data++; } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_ASCII operator++ (int) noexcept + { + CharPointer_ASCII temp (*this); + ++data; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + inline void operator+= (const int numToSkip) noexcept + { + data += numToSkip; + } + + inline void operator-= (const int numToSkip) noexcept + { + data -= numToSkip; + } + + /** Returns the character at a given character index from the start of the string. */ + inline juce_wchar operator[] (const int characterIndex) const noexcept + { + return (juce_wchar) (unsigned char) data [characterIndex]; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_ASCII operator+ (const int numToSkip) const noexcept + { + return CharPointer_ASCII (data + numToSkip); + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_ASCII operator- (const int numToSkip) const noexcept + { + return CharPointer_ASCII (data - numToSkip); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + inline void write (const juce_wchar charToWrite) noexcept + { + *data++ = (char) charToWrite; + } + + inline void replaceChar (const juce_wchar newChar) noexcept + { + *data = (char) newChar; + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + return (size_t) strlen (data); + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_ASCII end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return length() + 1; + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static inline size_t getBytesRequiredFor (const juce_wchar) noexcept + { + return 1; + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (const CharPointer text) noexcept + { + return text.length(); + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_ASCII findTerminatingNull() const noexcept + { + return CharPointer_ASCII (data + length()); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_ASCII src) noexcept + { + strcpy (data, src.data); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one. */ + int compare (const CharPointer_ASCII other) const noexcept + { + return strcmp (data, other.data); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one, up to a specified number of characters. */ + int compareUpTo (const CharPointer_ASCII other, const int maxChars) const noexcept + { + return strncmp (data, other.data, (size_t) maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + int compareIgnoreCase (const CharPointer_ASCII other) const + { + #if JUCE_WINDOWS + return stricmp (data, other.data); + #else + return strcasecmp (data, other.data); + #endif + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind) const noexcept + { + int i = 0; + + while (data[i] != 0) + { + if (data[i] == (char) charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const { return CharacterFunctions::isUpperCase ((juce_wchar) (uint8) *data) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const { return CharacterFunctions::isLowerCase ((juce_wchar) (uint8) *data) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase ((juce_wchar) (uint8) *data); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase ((juce_wchar) (uint8) *data); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return atoi (data); } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if JUCE_LINUX || JUCE_ANDROID + return atoll (data); + #elif JUCE_WINDOWS + return _atoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_ASCII findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (juce_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 128; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + while (--maxBytesToRead >= 0) + { + if (((signed char) *dataToTest) <= 0) + return *dataToTest == 0; + + ++dataToTest; + } + + return true; + } + +private: + CharType* data; +}; + + +#endif // JUCE_CHARPOINTER_ASCII_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF16.h b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF16.h new file mode 100644 index 0000000..d0eba5c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF16.h @@ -0,0 +1,525 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF16_H_INCLUDED +#define JUCE_CHARPOINTER_UTF16_H_INCLUDED + + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-16 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF8, CharPointer_UTF32 +*/ +class CharPointer_UTF16 +{ +public: + #if JUCE_NATIVE_WCHAR_IS_UTF16 + typedef wchar_t CharType; + #else + typedef int16 CharType; + #endif + + inline explicit CharPointer_UTF16 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF16 (const CharPointer_UTF16& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF16 operator= (CharPointer_UTF16 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF16 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF16 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF16 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF16 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF16 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF16 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF16 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + juce_wchar operator*() const noexcept + { + uint32 n = (uint32) (uint16) *data; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) data[1]) >= 0xdc00) + n = 0x10000 + (((n - 0xd800) << 10) | (((uint32) (uint16) data[1]) - 0xdc00)); + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF16 operator++() noexcept + { + const juce_wchar n = *data++; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00) + ++data; + + return *this; + } + + /** Moves this pointer back to the previous character in the string. */ + CharPointer_UTF16 operator--() noexcept + { + const juce_wchar n = *--data; + + if (n >= 0xdc00 && n <= 0xdfff) + --data; + + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + juce_wchar getAndAdvance() noexcept + { + uint32 n = (uint32) (uint16) *data++; + + if (n >= 0xd800 && n <= 0xdfff && ((uint32) (uint16) *data) >= 0xdc00) + n = 0x10000 + ((((n - 0xd800) << 10) | (((uint32) (uint16) *data++) - 0xdc00))); + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF16 operator++ (int) noexcept + { + CharPointer_UTF16 temp (*this); + ++*this; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + void operator+= (int numToSkip) noexcept + { + if (numToSkip < 0) + { + while (++numToSkip <= 0) + --*this; + } + else + { + while (--numToSkip >= 0) + ++*this; + } + } + + /** Moves this pointer backwards by the specified number of characters. */ + void operator-= (int numToSkip) noexcept + { + operator+= (-numToSkip); + } + + /** Returns the character at a given character index from the start of the string. */ + juce_wchar operator[] (const int characterIndex) const noexcept + { + CharPointer_UTF16 p (*this); + p += characterIndex; + return *p; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF16 operator+ (const int numToSkip) const noexcept + { + CharPointer_UTF16 p (*this); + p += numToSkip; + return p; + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF16 operator- (const int numToSkip) const noexcept + { + CharPointer_UTF16 p (*this); + p += -numToSkip; + return p; + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + void write (juce_wchar charToWrite) noexcept + { + if (charToWrite >= 0x10000) + { + charToWrite -= 0x10000; + *data++ = (CharType) (0xd800 + (charToWrite >> 10)); + *data++ = (CharType) (0xdc00 + (charToWrite & 0x3ff)); + } + else + { + *data++ = (CharType) charToWrite; + } + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + const CharType* d = data; + size_t count = 0; + + for (;;) + { + const int n = *d++; + + if (n >= 0xd800 && n <= 0xdfff) + { + if (*d++ == 0) + break; + } + else if (n == 0) + break; + + ++count; + } + + return count; + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF16 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return sizeof (CharType) * (findNullIndex (data) + 1); + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static size_t getBytesRequiredFor (const juce_wchar charToWrite) noexcept + { + return (charToWrite >= 0x10000) ? (sizeof (CharType) * 2) : sizeof (CharType); + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (CharPointer text) noexcept + { + size_t count = 0; + juce_wchar n; + + while ((n = text.getAndAdvance()) != 0) + count += getBytesRequiredFor (n); + + return count; + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF16 findTerminatingNull() const noexcept + { + const CharType* t = data; + + while (*t != 0) + ++t; + + return CharPointer_UTF16 (t); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF16 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + #if JUCE_WINDOWS && ! DOXYGEN + int compareIgnoreCase (const CharPointer_UTF16 other) const noexcept + { + return _wcsicmp (data, other.data); + } + + int compareIgnoreCaseUpTo (const CharPointer_UTF16 other, int maxChars) const noexcept + { + return _wcsnicmp (data, other.data, (size_t) maxChars); + } + + int indexOf (const CharPointer_UTF16 stringToFind) const noexcept + { + const CharType* const t = wcsstr (data, stringToFind.getAddress()); + return t == nullptr ? -1 : (int) (t - data); + } + #endif + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind) const noexcept + { + return CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const noexcept { return CharacterFunctions::isWhitespace (operator*()) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const noexcept { return CharacterFunctions::isDigit (operator*()) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept + { + #if JUCE_WINDOWS + return _wtoi (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if JUCE_WINDOWS + return _wtoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF16 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (juce_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff + && (((unsigned int) character) < 0xd800 || ((unsigned int) character) > 0xdfff); + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + maxBytesToRead /= (int) sizeof (CharType); + + while (--maxBytesToRead >= 0 && *dataToTest != 0) + { + const uint32 n = (uint32) (uint16) *dataToTest++; + + if (n >= 0xd800) + { + if (n > 0x10ffff) + return false; + + if (n <= 0xdfff) + { + if (n > 0xdc00) + return false; + + const uint32 nextChar = (uint32) (uint16) *dataToTest++; + + if (nextChar < 0xdc00 || nextChar > 0xdfff) + return false; + } + } + } + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF16 atomicSwap (const CharPointer_UTF16 newValue) + { + return CharPointer_UTF16 (reinterpret_cast &> (data).exchange (newValue.data)); + } + + /** These values are the byte-order-mark (BOM) values for a UTF-16 stream. */ + enum + { + byteOrderMarkBE1 = 0xfe, + byteOrderMarkBE2 = 0xff, + byteOrderMarkLE1 = 0xff, + byteOrderMarkLE2 = 0xfe + }; + + /** Returns true if the first pair of bytes in this pointer are the UTF16 byte-order mark (big endian). + The pointer must not be null, and must contain at least two valid bytes. + */ + static bool isByteOrderMarkBigEndian (const void* possibleByteOrder) noexcept + { + jassert (possibleByteOrder != nullptr); + const uint8* const c = static_cast (possibleByteOrder); + + return c[0] == (uint8) byteOrderMarkBE1 + && c[1] == (uint8) byteOrderMarkBE2; + } + + /** Returns true if the first pair of bytes in this pointer are the UTF16 byte-order mark (little endian). + The pointer must not be null, and must contain at least two valid bytes. + */ + static bool isByteOrderMarkLittleEndian (const void* possibleByteOrder) noexcept + { + jassert (possibleByteOrder != nullptr); + const uint8* const c = static_cast (possibleByteOrder); + + return c[0] == (uint8) byteOrderMarkLE1 + && c[1] == (uint8) byteOrderMarkLE2; + } + +private: + CharType* data; + + static unsigned int findNullIndex (const CharType* const t) noexcept + { + unsigned int n = 0; + + while (t[n] != 0) + ++n; + + return n; + } +}; + + +#endif // JUCE_CHARPOINTER_UTF16_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF32.h b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF32.h new file mode 100644 index 0000000..42cfe48 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF32.h @@ -0,0 +1,378 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF32_H_INCLUDED +#define JUCE_CHARPOINTER_UTF32_H_INCLUDED + + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-32 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF8, CharPointer_UTF16 +*/ +class CharPointer_UTF32 +{ +public: + typedef juce_wchar CharType; + + inline explicit CharPointer_UTF32 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF32 (const CharPointer_UTF32& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF32 operator= (CharPointer_UTF32 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF32 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF32 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF32 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF32 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF32 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF32 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF32 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + inline juce_wchar operator*() const noexcept { return *data; } + + /** Moves this pointer along to the next character in the string. */ + inline CharPointer_UTF32 operator++() noexcept + { + ++data; + return *this; + } + + /** Moves this pointer to the previous character in the string. */ + inline CharPointer_UTF32 operator--() noexcept + { + --data; + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + inline juce_wchar getAndAdvance() noexcept { return *data++; } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF32 operator++ (int) noexcept + { + CharPointer_UTF32 temp (*this); + ++data; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + inline void operator+= (const int numToSkip) noexcept + { + data += numToSkip; + } + + inline void operator-= (const int numToSkip) noexcept + { + data -= numToSkip; + } + + /** Returns the character at a given character index from the start of the string. */ + inline juce_wchar& operator[] (const int characterIndex) const noexcept + { + return data [characterIndex]; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF32 operator+ (const int numToSkip) const noexcept + { + return CharPointer_UTF32 (data + numToSkip); + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF32 operator- (const int numToSkip) const noexcept + { + return CharPointer_UTF32 (data - numToSkip); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + inline void write (const juce_wchar charToWrite) noexcept + { + *data++ = charToWrite; + } + + inline void replaceChar (const juce_wchar newChar) noexcept + { + *data = newChar; + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + #if JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_ANDROID + return wcslen (data); + #else + size_t n = 0; + while (data[n] != 0) + ++n; + return n; + #endif + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF32 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + return sizeof (CharType) * (length() + 1); + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static inline size_t getBytesRequiredFor (const juce_wchar) noexcept + { + return sizeof (CharType); + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (const CharPointer text) noexcept + { + return sizeof (CharType) * text.length(); + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF32 findTerminatingNull() const noexcept + { + return CharPointer_UTF32 (data + length()); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF32 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + #if JUCE_NATIVE_WCHAR_IS_UTF32 && ! JUCE_ANDROID + /** Compares this string with another one. */ + int compare (const CharPointer_UTF32 other) const noexcept + { + return wcscmp (data, other.data); + } + #endif + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind) const noexcept + { + int i = 0; + + while (data[i] != 0) + { + if (data[i] == charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const { return CharacterFunctions::isWhitespace (*data) != 0; } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const { return CharacterFunctions::isDigit (*data) != 0; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const { return CharacterFunctions::isLetter (*data) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const { return CharacterFunctions::isLetterOrDigit (*data) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const { return CharacterFunctions::isUpperCase (*data) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const { return CharacterFunctions::isLowerCase (*data) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (*data); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (*data); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return CharacterFunctions::getIntValue (*this); } + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept { return CharacterFunctions::getIntValue (*this); } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF32 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (juce_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + maxBytesToRead /= (int) sizeof (CharType); + + while (--maxBytesToRead >= 0 && *dataToTest != 0) + if (! canRepresent (*dataToTest++)) + return false; + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF32 atomicSwap (const CharPointer_UTF32 newValue) + { + return CharPointer_UTF32 (reinterpret_cast &> (data).exchange (newValue.data)); + } + +private: + CharType* data; +}; + + +#endif // JUCE_CHARPOINTER_UTF32_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF8.h b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF8.h new file mode 100644 index 0000000..78d9a97 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharPointer_UTF8.h @@ -0,0 +1,572 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARPOINTER_UTF8_H_INCLUDED +#define JUCE_CHARPOINTER_UTF8_H_INCLUDED + +//============================================================================== +/** + Wraps a pointer to a null-terminated UTF-8 character string, and provides + various methods to operate on the data. + @see CharPointer_UTF16, CharPointer_UTF32 +*/ +class CharPointer_UTF8 +{ +public: + typedef char CharType; + + inline explicit CharPointer_UTF8 (const CharType* const rawPointer) noexcept + : data (const_cast (rawPointer)) + { + } + + inline CharPointer_UTF8 (const CharPointer_UTF8& other) noexcept + : data (other.data) + { + } + + inline CharPointer_UTF8 operator= (CharPointer_UTF8 other) noexcept + { + data = other.data; + return *this; + } + + inline CharPointer_UTF8 operator= (const CharType* text) noexcept + { + data = const_cast (text); + return *this; + } + + /** This is a pointer comparison, it doesn't compare the actual text. */ + inline bool operator== (CharPointer_UTF8 other) const noexcept { return data == other.data; } + inline bool operator!= (CharPointer_UTF8 other) const noexcept { return data != other.data; } + inline bool operator<= (CharPointer_UTF8 other) const noexcept { return data <= other.data; } + inline bool operator< (CharPointer_UTF8 other) const noexcept { return data < other.data; } + inline bool operator>= (CharPointer_UTF8 other) const noexcept { return data >= other.data; } + inline bool operator> (CharPointer_UTF8 other) const noexcept { return data > other.data; } + + /** Returns the address that this pointer is pointing to. */ + inline CharType* getAddress() const noexcept { return data; } + + /** Returns the address that this pointer is pointing to. */ + inline operator const CharType*() const noexcept { return data; } + + /** Returns true if this pointer is pointing to a null character. */ + inline bool isEmpty() const noexcept { return *data == 0; } + + /** Returns the unicode character that this pointer is pointing to. */ + juce_wchar operator*() const noexcept + { + const signed char byte = (signed char) *data; + + if (byte >= 0) + return (juce_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + size_t numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x10) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + for (size_t i = 1; i <= numExtraValues; ++i) + { + const uint8 nextByte = (uint8) data [i]; + + if ((nextByte & 0xc0) != 0x80) + break; + + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8& operator++() noexcept + { + jassert (*data != 0); // trying to advance past the end of the string? + const signed char n = (signed char) *data++; + + if (n < 0) + { + juce_wchar bit = 0x40; + + while ((n & bit) != 0 && bit > 0x8) + { + ++data; + bit >>= 1; + } + } + + return *this; + } + + /** Moves this pointer back to the previous character in the string. */ + CharPointer_UTF8 operator--() noexcept + { + int count = 0; + + while ((*--data & 0xc0) == 0x80 && ++count < 4) + {} + + return *this; + } + + /** Returns the character that this pointer is currently pointing to, and then + advances the pointer to point to the next character. */ + juce_wchar getAndAdvance() noexcept + { + const signed char byte = (signed char) *data++; + + if (byte >= 0) + return (juce_wchar) (uint8) byte; + + uint32 n = (uint32) (uint8) byte; + uint32 mask = 0x7f; + uint32 bit = 0x40; + int numExtraValues = 0; + + while ((n & bit) != 0 && bit > 0x8) + { + mask >>= 1; + ++numExtraValues; + bit >>= 1; + } + + n &= mask; + + while (--numExtraValues >= 0) + { + const uint32 nextByte = (uint32) (uint8) *data; + + if ((nextByte & 0xc0) != 0x80) + break; + + ++data; + n <<= 6; + n |= (nextByte & 0x3f); + } + + return (juce_wchar) n; + } + + /** Moves this pointer along to the next character in the string. */ + CharPointer_UTF8 operator++ (int) noexcept + { + CharPointer_UTF8 temp (*this); + ++*this; + return temp; + } + + /** Moves this pointer forwards by the specified number of characters. */ + void operator+= (int numToSkip) noexcept + { + if (numToSkip < 0) + { + while (++numToSkip <= 0) + --*this; + } + else + { + while (--numToSkip >= 0) + ++*this; + } + } + + /** Moves this pointer backwards by the specified number of characters. */ + void operator-= (int numToSkip) noexcept + { + operator+= (-numToSkip); + } + + /** Returns the character at a given character index from the start of the string. */ + juce_wchar operator[] (int characterIndex) const noexcept + { + CharPointer_UTF8 p (*this); + p += characterIndex; + return *p; + } + + /** Returns a pointer which is moved forwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator+ (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += numToSkip; + return p; + } + + /** Returns a pointer which is moved backwards from this one by the specified number of characters. */ + CharPointer_UTF8 operator- (int numToSkip) const noexcept + { + CharPointer_UTF8 p (*this); + p += -numToSkip; + return p; + } + + /** Returns the number of characters in this string. */ + size_t length() const noexcept + { + const CharType* d = data; + size_t count = 0; + + for (;;) + { + const uint32 n = (uint32) (uint8) *d++; + + if ((n & 0x80) != 0) + { + while ((*d & 0xc0) == 0x80) + ++d; + } + else if (n == 0) + break; + + ++count; + } + + return count; + } + + /** Returns the number of characters in this string, or the given value, whichever is lower. */ + size_t lengthUpTo (const size_t maxCharsToCount) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, maxCharsToCount); + } + + /** Returns the number of characters in this string, or up to the given end pointer, whichever is lower. */ + size_t lengthUpTo (const CharPointer_UTF8 end) const noexcept + { + return CharacterFunctions::lengthUpTo (*this, end); + } + + /** Returns the number of bytes that are used to represent this string. + This includes the terminating null character. + */ + size_t sizeInBytes() const noexcept + { + jassert (data != nullptr); + return strlen (data) + 1; + } + + /** Returns the number of bytes that would be needed to represent the given + unicode character in this encoding format. + */ + static size_t getBytesRequiredFor (const juce_wchar charToWrite) noexcept + { + size_t num = 1; + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + ++num; + if (c >= 0x800) + { + ++num; + if (c >= 0x10000) + ++num; + } + } + + return num; + } + + /** Returns the number of bytes that would be needed to represent the given + string in this encoding format. + The value returned does NOT include the terminating null character. + */ + template + static size_t getBytesRequiredFor (CharPointer text) noexcept + { + size_t count = 0; + juce_wchar n; + + while ((n = text.getAndAdvance()) != 0) + count += getBytesRequiredFor (n); + + return count; + } + + /** Returns a pointer to the null character that terminates this string. */ + CharPointer_UTF8 findTerminatingNull() const noexcept + { + return CharPointer_UTF8 (data + strlen (data)); + } + + /** Writes a unicode character to this string, and advances this pointer to point to the next position. */ + void write (const juce_wchar charToWrite) noexcept + { + const uint32 c = (uint32) charToWrite; + + if (c >= 0x80) + { + int numExtraBytes = 1; + if (c >= 0x800) + { + ++numExtraBytes; + if (c >= 0x10000) + ++numExtraBytes; + } + + *data++ = (CharType) ((uint32) (0xff << (7 - numExtraBytes)) | (c >> (numExtraBytes * 6))); + + while (--numExtraBytes >= 0) + *data++ = (CharType) (0x80 | (0x3f & (c >> (numExtraBytes * 6)))); + } + else + { + *data++ = (CharType) c; + } + } + + /** Writes a null character to this string (leaving the pointer's position unchanged). */ + inline void writeNull() const noexcept + { + *data = 0; + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + template + void writeAll (const CharPointer src) noexcept + { + CharacterFunctions::copyAll (*this, src); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. */ + void writeAll (const CharPointer_UTF8 src) noexcept + { + const CharType* s = src.data; + + while ((*data = *s) != 0) + { + ++data; + ++s; + } + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxDestBytes parameter specifies the maximum number of bytes that can be written + to the destination buffer before stopping. + */ + template + size_t writeWithDestByteLimit (const CharPointer src, const size_t maxDestBytes) noexcept + { + return CharacterFunctions::copyWithDestByteLimit (*this, src, maxDestBytes); + } + + /** Copies a source string to this pointer, advancing this pointer as it goes. + The maxChars parameter specifies the maximum number of characters that can be + written to the destination buffer before stopping (including the terminating null). + */ + template + void writeWithCharLimit (const CharPointer src, const int maxChars) noexcept + { + CharacterFunctions::copyWithCharLimit (*this, src, maxChars); + } + + /** Compares this string with another one. */ + template + int compare (const CharPointer other) const noexcept + { + return CharacterFunctions::compare (*this, other); + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareUpTo (*this, other, maxChars); + } + + /** Compares this string with another one. */ + template + int compareIgnoreCase (const CharPointer other) const noexcept + { + return CharacterFunctions::compareIgnoreCase (*this, other); + } + + /** Compares this string with another one. */ + int compareIgnoreCase (const CharPointer_UTF8 other) const noexcept + { + #if JUCE_WINDOWS + return stricmp (data, other.data); + #else + return strcasecmp (data, other.data); + #endif + } + + /** Compares this string with another one, up to a specified number of characters. */ + template + int compareIgnoreCaseUpTo (const CharPointer other, const int maxChars) const noexcept + { + return CharacterFunctions::compareIgnoreCaseUpTo (*this, other, maxChars); + } + + /** Returns the character index of a substring, or -1 if it isn't found. */ + template + int indexOf (const CharPointer stringToFind) const noexcept + { + return CharacterFunctions::indexOf (*this, stringToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind) const noexcept + { + return CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns the character index of a unicode character, or -1 if it isn't found. */ + int indexOf (const juce_wchar charToFind, const bool ignoreCase) const noexcept + { + return ignoreCase ? CharacterFunctions::indexOfCharIgnoreCase (*this, charToFind) + : CharacterFunctions::indexOfChar (*this, charToFind); + } + + /** Returns true if the first character of this string is whitespace. */ + bool isWhitespace() const noexcept { return *data == ' ' || (*data <= 13 && *data >= 9); } + /** Returns true if the first character of this string is a digit. */ + bool isDigit() const noexcept { return *data >= '0' && *data <= '9'; } + /** Returns true if the first character of this string is a letter. */ + bool isLetter() const noexcept { return CharacterFunctions::isLetter (operator*()) != 0; } + /** Returns true if the first character of this string is a letter or digit. */ + bool isLetterOrDigit() const noexcept { return CharacterFunctions::isLetterOrDigit (operator*()) != 0; } + /** Returns true if the first character of this string is upper-case. */ + bool isUpperCase() const noexcept { return CharacterFunctions::isUpperCase (operator*()) != 0; } + /** Returns true if the first character of this string is lower-case. */ + bool isLowerCase() const noexcept { return CharacterFunctions::isLowerCase (operator*()) != 0; } + + /** Returns an upper-case version of the first character of this string. */ + juce_wchar toUpperCase() const noexcept { return CharacterFunctions::toUpperCase (operator*()); } + /** Returns a lower-case version of the first character of this string. */ + juce_wchar toLowerCase() const noexcept { return CharacterFunctions::toLowerCase (operator*()); } + + /** Parses this string as a 32-bit integer. */ + int getIntValue32() const noexcept { return atoi (data); } + + /** Parses this string as a 64-bit integer. */ + int64 getIntValue64() const noexcept + { + #if JUCE_LINUX || JUCE_ANDROID + return atoll (data); + #elif JUCE_WINDOWS + return _atoi64 (data); + #else + return CharacterFunctions::getIntValue (*this); + #endif + } + + /** Parses this string as a floating point double. */ + double getDoubleValue() const noexcept { return CharacterFunctions::getDoubleValue (*this); } + + /** Returns the first non-whitespace character in the string. */ + CharPointer_UTF8 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + + /** Returns true if the given unicode character can be represented in this encoding. */ + static bool canRepresent (juce_wchar character) noexcept + { + return ((unsigned int) character) < (unsigned int) 0x10ffff; + } + + /** Returns true if this data contains a valid string in this encoding. */ + static bool isValidString (const CharType* dataToTest, int maxBytesToRead) + { + while (--maxBytesToRead >= 0 && *dataToTest != 0) + { + const signed char byte = (signed char) *dataToTest++; + + if (byte < 0) + { + uint8 bit = 0x40; + int numExtraValues = 0; + + while ((byte & bit) != 0) + { + if (bit < 8) + return false; + + ++numExtraValues; + bit >>= 1; + + if (bit == 8 && (numExtraValues > maxBytesToRead + || *CharPointer_UTF8 (dataToTest - 1) > 0x10ffff)) + return false; + } + + maxBytesToRead -= numExtraValues; + if (maxBytesToRead < 0) + return false; + + while (--numExtraValues >= 0) + if ((*dataToTest++ & 0xc0) != 0x80) + return false; + } + } + + return true; + } + + /** Atomically swaps this pointer for a new value, returning the previous value. */ + CharPointer_UTF8 atomicSwap (const CharPointer_UTF8 newValue) + { + return CharPointer_UTF8 (reinterpret_cast &> (data).exchange (newValue.data)); + } + + /** These values are the byte-order mark (BOM) values for a UTF-8 stream. */ + enum + { + byteOrderMark1 = 0xef, + byteOrderMark2 = 0xbb, + byteOrderMark3 = 0xbf + }; + + /** Returns true if the first three bytes in this pointer are the UTF8 byte-order mark (BOM). + The pointer must not be null, and must point to at least 3 valid bytes. + */ + static bool isByteOrderMark (const void* possibleByteOrder) noexcept + { + jassert (possibleByteOrder != nullptr); + const uint8* const c = static_cast (possibleByteOrder); + + return c[0] == (uint8) byteOrderMark1 + && c[1] == (uint8) byteOrderMark2 + && c[2] == (uint8) byteOrderMark3; + } + +private: + CharType* data; +}; + +#endif // JUCE_CHARPOINTER_UTF8_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp new file mode 100644 index 0000000..11eaa18 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -0,0 +1,154 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +//============================================================================== +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +juce_wchar CharacterFunctions::toUpperCase (const juce_wchar character) noexcept +{ + return towupper ((wchar_t) character); +} + +juce_wchar CharacterFunctions::toLowerCase (const juce_wchar character) noexcept +{ + return towlower ((wchar_t) character); +} + +bool CharacterFunctions::isUpperCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswupper ((wchar_t) character) != 0; + #else + return toLowerCase (character) != character; + #endif +} + +bool CharacterFunctions::isLowerCase (const juce_wchar character) noexcept +{ + #if JUCE_WINDOWS + return iswlower ((wchar_t) character) != 0; + #else + return toUpperCase (character) != character; + #endif +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +bool CharacterFunctions::isWhitespace (const char character) noexcept +{ + return character == ' ' || (character <= 13 && character >= 9); +} + +bool CharacterFunctions::isWhitespace (const juce_wchar character) noexcept +{ + return iswspace ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isDigit (const char character) noexcept +{ + return (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isDigit (const juce_wchar character) noexcept +{ + return iswdigit ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isLetter (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z'); +} + +bool CharacterFunctions::isLetter (const juce_wchar character) noexcept +{ + return iswalpha ((wchar_t) character) != 0; +} + +bool CharacterFunctions::isLetterOrDigit (const char character) noexcept +{ + return (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9'); +} + +bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept +{ + return iswalnum ((wchar_t) character) != 0; +} + +int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept +{ + unsigned int d = (unsigned int) digit - '0'; + if (d < (unsigned int) 10) + return (int) d; + + d += (unsigned int) ('0' - 'a'); + if (d < (unsigned int) 6) + return (int) d + 10; + + d += (unsigned int) ('a' - 'A'); + if (d < (unsigned int) 6) + return (int) d + 10; + + return -1; +} + +double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept +{ + if (exponent == 0) + return value; + + if (value == 0) + return 0; + + const bool negative = (exponent < 0); + if (negative) + exponent = -exponent; + + double result = 1.0, power = 10.0; + for (int bit = 1; exponent != 0; bit <<= 1) + { + if ((exponent & bit) != 0) + { + exponent ^= bit; + result *= power; + if (exponent == 0) + break; + } + power *= power; + } + + return negative ? (value / result) : (value * result); +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.h b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.h new file mode 100644 index 0000000..bade0dd --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.h @@ -0,0 +1,629 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHARACTERFUNCTIONS_H_INCLUDED +#define JUCE_CHARACTERFUNCTIONS_H_INCLUDED + + +//============================================================================== +#if JUCE_WINDOWS && ! DOXYGEN + #define JUCE_NATIVE_WCHAR_IS_UTF8 0 + #define JUCE_NATIVE_WCHAR_IS_UTF16 1 + #define JUCE_NATIVE_WCHAR_IS_UTF32 0 +#else + /** This macro will be set to 1 if the compiler's native wchar_t is an 8-bit type. */ + #define JUCE_NATIVE_WCHAR_IS_UTF8 0 + /** This macro will be set to 1 if the compiler's native wchar_t is a 16-bit type. */ + #define JUCE_NATIVE_WCHAR_IS_UTF16 0 + /** This macro will be set to 1 if the compiler's native wchar_t is a 32-bit type. */ + #define JUCE_NATIVE_WCHAR_IS_UTF32 1 +#endif + +#if JUCE_NATIVE_WCHAR_IS_UTF32 || DOXYGEN + /** A platform-independent 32-bit unicode character type. */ + typedef wchar_t juce_wchar; +#else + typedef uint32 juce_wchar; +#endif + +#ifndef DOXYGEN + /** This macro is deprecated, but preserved for compatibility with old code. */ + #define JUCE_T(stringLiteral) (L##stringLiteral) +#endif + +#if JUCE_DEFINE_T_MACRO + /** The 'T' macro is an alternative for using the "L" prefix in front of a string literal. + + This macro is deprecated, but available for compatibility with old code if you set + JUCE_DEFINE_T_MACRO = 1. The fastest, most portable and best way to write your string + literals is as standard char strings, using escaped utf-8 character sequences for extended + characters, rather than trying to store them as wide-char strings. + */ + #define T(stringLiteral) JUCE_T(stringLiteral) +#endif + +//============================================================================== +/** + A collection of functions for manipulating characters and character strings. + + Most of these methods are designed for internal use by the String and CharPointer + classes, but some of them may be useful to call directly. + + @see String, CharPointer_UTF8, CharPointer_UTF16, CharPointer_UTF32 +*/ +class JUCE_API CharacterFunctions +{ +public: + //============================================================================== + /** Converts a character to upper-case. */ + static juce_wchar toUpperCase (juce_wchar character) noexcept; + /** Converts a character to lower-case. */ + static juce_wchar toLowerCase (juce_wchar character) noexcept; + + /** Checks whether a unicode character is upper-case. */ + static bool isUpperCase (juce_wchar character) noexcept; + /** Checks whether a unicode character is lower-case. */ + static bool isLowerCase (juce_wchar character) noexcept; + + /** Checks whether a character is whitespace. */ + static bool isWhitespace (char character) noexcept; + /** Checks whether a character is whitespace. */ + static bool isWhitespace (juce_wchar character) noexcept; + + /** Checks whether a character is a digit. */ + static bool isDigit (char character) noexcept; + /** Checks whether a character is a digit. */ + static bool isDigit (juce_wchar character) noexcept; + + /** Checks whether a character is alphabetic. */ + static bool isLetter (char character) noexcept; + /** Checks whether a character is alphabetic. */ + static bool isLetter (juce_wchar character) noexcept; + + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (char character) noexcept; + /** Checks whether a character is alphabetic or numeric. */ + static bool isLetterOrDigit (juce_wchar character) noexcept; + + /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ + static int getHexDigitValue (juce_wchar digit) noexcept; + + //============================================================================== + /** Parses a character string to read a floating-point number. + Note that this will advance the pointer that is passed in, leaving it at + the end of the number. + */ + template + static double readDoubleValue (CharPointerType& text) noexcept + { + double result[3] = { 0 }, accumulator[2] = { 0 }; + int exponentAdjustment[2] = { 0 }, exponentAccumulator[2] = { -1, -1 }; + int exponent = 0, decPointIndex = 0, digit = 0; + int lastDigit = 0, numSignificantDigits = 0; + bool isNegative = false, digitsFound = false; + const int maxSignificantDigits = 15 + 2; + + text = text.findEndOfWhitespace(); + juce_wchar c = *text; + + switch (c) + { + case '-': isNegative = true; // fall-through.. + case '+': c = *++text; + } + + switch (c) + { + case 'n': + case 'N': + if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N')) + return std::numeric_limits::quiet_NaN(); + break; + + case 'i': + case 'I': + if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F')) + return std::numeric_limits::infinity(); + break; + } + + for (;;) + { + if (text.isDigit()) + { + lastDigit = digit; + digit = (int) text.getAndAdvance() - '0'; + digitsFound = true; + + if (decPointIndex != 0) + exponentAdjustment[1]++; + + if (numSignificantDigits == 0 && digit == 0) + continue; + + if (++numSignificantDigits > maxSignificantDigits) + { + if (digit > 5) + ++accumulator [decPointIndex]; + else if (digit == 5 && (lastDigit & 1) != 0) + ++accumulator [decPointIndex]; + + if (decPointIndex > 0) + exponentAdjustment[1]--; + else + exponentAdjustment[0]++; + + while (text.isDigit()) + { + ++text; + if (decPointIndex == 0) + exponentAdjustment[0]++; + } + } + else + { + const double maxAccumulatorValue = (double) ((std::numeric_limits::max() - 9) / 10); + if (accumulator [decPointIndex] > maxAccumulatorValue) + { + result [decPointIndex] = mulexp10 (result [decPointIndex], exponentAccumulator [decPointIndex]) + + accumulator [decPointIndex]; + accumulator [decPointIndex] = 0; + exponentAccumulator [decPointIndex] = 0; + } + + accumulator [decPointIndex] = accumulator[decPointIndex] * 10 + digit; + exponentAccumulator [decPointIndex]++; + } + } + else if (decPointIndex == 0 && *text == '.') + { + ++text; + decPointIndex = 1; + + if (numSignificantDigits > maxSignificantDigits) + { + while (text.isDigit()) + ++text; + break; + } + } + else + { + break; + } + } + + result[0] = mulexp10 (result[0], exponentAccumulator[0]) + accumulator[0]; + + if (decPointIndex != 0) + result[1] = mulexp10 (result[1], exponentAccumulator[1]) + accumulator[1]; + + c = *text; + if ((c == 'e' || c == 'E') && digitsFound) + { + bool negativeExponent = false; + + switch (*++text) + { + case '-': negativeExponent = true; // fall-through.. + case '+': ++text; + } + + while (text.isDigit()) + exponent = (exponent * 10) + ((int) text.getAndAdvance() - '0'); + + if (negativeExponent) + exponent = -exponent; + } + + double r = mulexp10 (result[0], exponent + exponentAdjustment[0]); + if (decPointIndex != 0) + r += mulexp10 (result[1], exponent - exponentAdjustment[1]); + + return isNegative ? -r : r; + } + + /** Parses a character string, to read a floating-point value. */ + template + static double getDoubleValue (CharPointerType text) noexcept + { + return readDoubleValue (text); + } + + //============================================================================== + /** Parses a character string, to read an integer value. */ + template + static IntType getIntValue (const CharPointerType text) noexcept + { + IntType v = 0; + CharPointerType s (text.findEndOfWhitespace()); + + const bool isNeg = *s == '-'; + if (isNeg) + ++s; + + for (;;) + { + const juce_wchar c = s.getAndAdvance(); + + if (c >= '0' && c <= '9') + v = v * 10 + (IntType) (c - '0'); + else + break; + } + + return isNeg ? -v : v; + } + + template + struct HexParser + { + template + static ResultType parse (CharPointerType t) noexcept + { + ResultType result = 0; + + while (! t.isEmpty()) + { + const int hexValue = CharacterFunctions::getHexDigitValue (t.getAndAdvance()); + + if (hexValue >= 0) + result = (result << 4) | hexValue; + } + + return result; + } + }; + + //============================================================================== + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified limit. */ + template + static size_t lengthUpTo (CharPointerType text, const size_t maxCharsToCount) noexcept + { + size_t len = 0; + + while (len < maxCharsToCount && text.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Counts the number of characters in a given string, stopping if the count exceeds + a specified end-pointer. */ + template + static size_t lengthUpTo (CharPointerType start, const CharPointerType end) noexcept + { + size_t len = 0; + + while (start < end && start.getAndAdvance() != 0) + ++len; + + return len; + } + + /** Copies null-terminated characters from one string to another. */ + template + static void copyAll (DestCharPointerType& dest, SrcCharPointerType src) noexcept + { + for (;;) + { + const juce_wchar c = src.getAndAdvance(); + + if (c == 0) + break; + + dest.write (c); + } + + dest.writeNull(); + } + + /** Copies characters from one string to another, up to a null terminator + or a given byte size limit. */ + template + static size_t copyWithDestByteLimit (DestCharPointerType& dest, SrcCharPointerType src, size_t maxBytesToWrite) noexcept + { + typename DestCharPointerType::CharType const* const startAddress = dest.getAddress(); + ssize_t maxBytes = (ssize_t) maxBytesToWrite; + maxBytes -= sizeof (typename DestCharPointerType::CharType); // (allow for a terminating null) + + for (;;) + { + const juce_wchar c = src.getAndAdvance(); + const size_t bytesNeeded = DestCharPointerType::getBytesRequiredFor (c); + + maxBytes -= bytesNeeded; + if (c == 0 || maxBytes < 0) + break; + + dest.write (c); + } + + dest.writeNull(); + + return (size_t) getAddressDifference (dest.getAddress(), startAddress) + + sizeof (typename DestCharPointerType::CharType); + } + + /** Copies characters from one string to another, up to a null terminator + or a given maximum number of characters. */ + template + static void copyWithCharLimit (DestCharPointerType& dest, SrcCharPointerType src, int maxChars) noexcept + { + while (--maxChars > 0) + { + const juce_wchar c = src.getAndAdvance(); + if (c == 0) + break; + + dest.write (c); + } + + dest.writeNull(); + } + + /** Compares two null-terminated character strings. */ + template + static int compare (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const int c1 = (int) s1.getAndAdvance(); + const int c2 = (int) s2.getAndAdvance(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Compares two null-terminated character strings, up to a given number of characters. */ + template + static int compareUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const int c1 = (int) s1.getAndAdvance(); + const int c2 = (int) s2.getAndAdvance(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; + } + + /** Compares two null-terminated character strings, using a case-independant match. */ + template + static int compareIgnoreCase (CharPointerType1 s1, CharPointerType2 s2) noexcept + { + for (;;) + { + const int c1 = (int) s1.toUpperCase(); + const int c2 = (int) s2.toUpperCase(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + + ++s1; ++s2; + } + + return 0; + } + + /** Compares two null-terminated character strings, using a case-independent match. */ + template + static int compareIgnoreCaseUpTo (CharPointerType1 s1, CharPointerType2 s2, int maxChars) noexcept + { + while (--maxChars >= 0) + { + const int c1 = (int) s1.toUpperCase(); + const int c2 = (int) s2.toUpperCase(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + + ++s1; ++s2; + } + + return 0; + } + + /** Finds the character index of a given substring in another string. + Returns -1 if the substring is not found. + */ + template + static int indexOf (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + int index = 0; + const int substringLength = (int) substringToLookFor.length(); + + for (;;) + { + if (textToSearch.compareUpTo (substringToLookFor, substringLength) == 0) + return index; + + if (textToSearch.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Returns a pointer to the first occurrence of a substring in a string. + If the substring is not found, this will return a pointer to the string's + null terminator. + */ + template + static CharPointerType1 find (CharPointerType1 textToSearch, const CharPointerType2 substringToLookFor) noexcept + { + const int substringLength = (int) substringToLookFor.length(); + + while (textToSearch.compareUpTo (substringToLookFor, substringLength) != 0 + && ! textToSearch.isEmpty()) + ++textToSearch; + + return textToSearch; + } + + /** Returns a pointer to the first occurrence of a substring in a string. + If the substring is not found, this will return a pointer to the string's + null terminator. + */ + template + static CharPointerType find (CharPointerType textToSearch, const juce_wchar charToLookFor) noexcept + { + for (;; ++textToSearch) + { + const juce_wchar c = *textToSearch; + + if (c == charToLookFor || c == 0) + break; + } + + return textToSearch; + } + + /** Finds the character index of a given substring in another string, using + a case-independent match. + Returns -1 if the substring is not found. + */ + template + static int indexOfIgnoreCase (CharPointerType1 haystack, const CharPointerType2 needle) noexcept + { + int index = 0; + const int needleLength = (int) needle.length(); + + for (;;) + { + if (haystack.compareIgnoreCaseUpTo (needle, needleLength) == 0) + return index; + + if (haystack.getAndAdvance() == 0) + return -1; + + ++index; + } + } + + /** Finds the character index of a given character in another string. + Returns -1 if the character is not found. + */ + template + static int indexOfChar (Type text, const juce_wchar charToFind) noexcept + { + int i = 0; + + while (! text.isEmpty()) + { + if (text.getAndAdvance() == charToFind) + return i; + + ++i; + } + + return -1; + } + + /** Finds the character index of a given character in another string, using + a case-independent match. + Returns -1 if the character is not found. + */ + template + static int indexOfCharIgnoreCase (Type text, juce_wchar charToFind) noexcept + { + charToFind = CharacterFunctions::toLowerCase (charToFind); + int i = 0; + + while (! text.isEmpty()) + { + if (text.toLowerCase() == charToFind) + return i; + + ++text; + ++i; + } + + return -1; + } + + /** Returns a pointer to the first non-whitespace character in a string. + If the string contains only whitespace, this will return a pointer + to its null terminator. + */ + template + static Type findEndOfWhitespace (Type text) noexcept + { + while (text.isWhitespace()) + ++text; + + return text; + } + + /** Returns a pointer to the first character in the string which is found in + the breakCharacters string. + */ + template + static Type findEndOfToken (Type text, const BreakType breakCharacters, const Type quoteCharacters) + { + juce_wchar currentQuoteChar = 0; + + while (! text.isEmpty()) + { + const juce_wchar c = text.getAndAdvance(); + + if (currentQuoteChar == 0 && breakCharacters.indexOf (c) >= 0) + { + --text; + break; + } + + if (quoteCharacters.indexOf (c) >= 0) + { + if (currentQuoteChar == 0) + currentQuoteChar = c; + else if (currentQuoteChar == c) + currentQuoteChar = 0; + } + } + + return text; + } + +private: + static double mulexp10 (const double value, int exponent) noexcept; +}; + + +#endif // JUCE_CHARACTERFUNCTIONS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_Identifier.cpp b/JuceLibraryCode/modules/juce_core/text/juce_Identifier.cpp new file mode 100644 index 0000000..bac4dd5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_Identifier.cpp @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Identifier::Identifier() noexcept {} +Identifier::~Identifier() noexcept {} + +Identifier::Identifier (const Identifier& other) noexcept : name (other.name) {} + +Identifier& Identifier::operator= (const Identifier other) noexcept +{ + name = other.name; + return *this; +} + +Identifier::Identifier (const String& nm) + : name (StringPool::getGlobalPool().getPooledString (nm)) +{ + /* An Identifier string must be suitable for use as a script variable or XML + attribute, so it can only contain this limited set of characters.. */ + jassert (isValidIdentifier (toString())); +} + +Identifier::Identifier (const char* nm) + : name (StringPool::getGlobalPool().getPooledString (nm)) +{ + /* An Identifier string must be suitable for use as a script variable or XML + attribute, so it can only contain this limited set of characters.. */ + jassert (isValidIdentifier (toString())); +} + +Identifier::Identifier (String::CharPointerType start, String::CharPointerType end) + : name (StringPool::getGlobalPool().getPooledString (start, end)) +{ + /* An Identifier string must be suitable for use as a script variable or XML + attribute, so it can only contain this limited set of characters.. */ + jassert (isValidIdentifier (toString())); +} + +Identifier Identifier::null; + +bool Identifier::isValidIdentifier (const String& possibleIdentifier) noexcept +{ + return possibleIdentifier.isNotEmpty() + && possibleIdentifier.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:#@$%"); +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_Identifier.h b/JuceLibraryCode/modules/juce_core/text/juce_Identifier.h new file mode 100644 index 0000000..f60eec8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_Identifier.h @@ -0,0 +1,120 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_IDENTIFIER_H_INCLUDED +#define JUCE_IDENTIFIER_H_INCLUDED + + +//============================================================================== +/** + Represents a string identifier, designed for accessing properties by name. + + Comparing two Identifier objects is very fast (an O(1) operation), but creating + them can be slower than just using a String directly, so the optimal way to use them + is to keep some static Identifier objects for the things you use often. + + @see NamedValueSet, ValueTree +*/ +class JUCE_API Identifier +{ +public: + /** Creates a null identifier. */ + Identifier() noexcept; + + /** Creates an identifier with a specified name. + Because this name may need to be used in contexts such as script variables or XML + tags, it must only contain ascii letters and digits, or the underscore character. + */ + Identifier (const char* name); + + /** Creates an identifier with a specified name. + Because this name may need to be used in contexts such as script variables or XML + tags, it must only contain ascii letters and digits, or the underscore character. + */ + Identifier (const String& name); + + /** Creates an identifier with a specified name. + Because this name may need to be used in contexts such as script variables or XML + tags, it must only contain ascii letters and digits, or the underscore character. + */ + Identifier (String::CharPointerType nameStart, String::CharPointerType nameEnd); + + /** Creates a copy of another identifier. */ + Identifier (const Identifier& other) noexcept; + + /** Creates a copy of another identifier. */ + Identifier& operator= (const Identifier other) noexcept; + + /** Destructor */ + ~Identifier() noexcept; + + /** Compares two identifiers. This is a very fast operation. */ + inline bool operator== (Identifier other) const noexcept { return name.getCharPointer() == other.name.getCharPointer(); } + + /** Compares two identifiers. This is a very fast operation. */ + inline bool operator!= (Identifier other) const noexcept { return name.getCharPointer() != other.name.getCharPointer(); } + + /** Compares the identifier with a string. */ + inline bool operator== (StringRef other) const noexcept { return name == other; } + + /** Compares the identifier with a string. */ + inline bool operator!= (StringRef other) const noexcept { return name != other; } + + /** Returns this identifier as a string. */ + const String& toString() const noexcept { return name; } + + /** Returns this identifier's raw string pointer. */ + operator String::CharPointerType() const noexcept { return name.getCharPointer(); } + + /** Returns this identifier's raw string pointer. */ + String::CharPointerType getCharPointer() const noexcept { return name.getCharPointer(); } + + /** Returns this identifier as a StringRef. */ + operator StringRef() const noexcept { return name; } + + /** Returns true if this Identifier is not null */ + bool isValid() const noexcept { return name.isNotEmpty(); } + + /** Returns true if this Identifier is null */ + bool isNull() const noexcept { return name.isEmpty(); } + + /** A null identifier. */ + static Identifier null; + + /** Checks a given string for characters that might not be valid in an Identifier. + Since Identifiers are used as a script variables and XML attributes, they should only contain + alphanumeric characters, underscores, or the '-' and ':' characters. + */ + static bool isValidIdentifier (const String& possibleIdentifier) noexcept; + +private: + String name; +}; + + +#endif // JUCE_IDENTIFIER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp new file mode 100644 index 0000000..e6118d3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.cpp @@ -0,0 +1,209 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +LocalisedStrings::LocalisedStrings (const String& fileContents, bool ignoreCase) +{ + loadFromText (fileContents, ignoreCase); +} + +LocalisedStrings::LocalisedStrings (const File& fileToLoad, bool ignoreCase) +{ + loadFromText (fileToLoad.loadFileAsString(), ignoreCase); +} + +LocalisedStrings::LocalisedStrings (const LocalisedStrings& other) + : languageName (other.languageName), countryCodes (other.countryCodes), + translations (other.translations), fallback (createCopyIfNotNull (other.fallback.get())) +{ +} + +LocalisedStrings& LocalisedStrings::operator= (const LocalisedStrings& other) +{ + languageName = other.languageName; + countryCodes = other.countryCodes; + translations = other.translations; + fallback = createCopyIfNotNull (other.fallback.get()); + return *this; +} + +LocalisedStrings::~LocalisedStrings() +{ +} + +//============================================================================== +String LocalisedStrings::translate (const String& text) const +{ + if (fallback != nullptr && ! translations.containsKey (text)) + return fallback->translate (text); + + return translations.getValue (text, text); +} + +String LocalisedStrings::translate (const String& text, const String& resultIfNotFound) const +{ + if (fallback != nullptr && ! translations.containsKey (text)) + return fallback->translate (text, resultIfNotFound); + + return translations.getValue (text, resultIfNotFound); +} + +namespace +{ + #if JUCE_CHECK_MEMORY_LEAKS + // By using this object to force a LocalisedStrings object to be created + // before the currentMappings object, we can force the static order-of-destruction to + // delete the currentMappings object first, which avoids a bogus leak warning. + // (Oddly, just creating a LocalisedStrings on the stack doesn't work in gcc, it + // has to be created with 'new' for this to work..) + struct LeakAvoidanceTrick + { + LeakAvoidanceTrick() + { + const ScopedPointer dummy (new LocalisedStrings (String(), false)); + } + }; + + LeakAvoidanceTrick leakAvoidanceTrick; + #endif + + SpinLock currentMappingsLock; + ScopedPointer currentMappings; + + static int findCloseQuote (const String& text, int startPos) + { + juce_wchar lastChar = 0; + String::CharPointerType t (text.getCharPointer() + startPos); + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + + if (c == 0 || (c == '"' && lastChar != '\\')) + break; + + lastChar = c; + ++startPos; + } + + return startPos; + } + + static String unescapeString (const String& s) + { + return s.replace ("\\\"", "\"") + .replace ("\\\'", "\'") + .replace ("\\t", "\t") + .replace ("\\r", "\r") + .replace ("\\n", "\n"); + } +} + +void LocalisedStrings::loadFromText (const String& fileContents, bool ignoreCase) +{ + translations.setIgnoresCase (ignoreCase); + + StringArray lines; + lines.addLines (fileContents); + + for (int i = 0; i < lines.size(); ++i) + { + String line (lines[i].trim()); + + if (line.startsWithChar ('"')) + { + int closeQuote = findCloseQuote (line, 1); + + const String originalText (unescapeString (line.substring (1, closeQuote))); + + if (originalText.isNotEmpty()) + { + const int openingQuote = findCloseQuote (line, closeQuote + 1); + closeQuote = findCloseQuote (line, openingQuote + 1); + + const String newText (unescapeString (line.substring (openingQuote + 1, closeQuote))); + + if (newText.isNotEmpty()) + translations.set (originalText, newText); + } + } + else if (line.startsWithIgnoreCase ("language:")) + { + languageName = line.substring (9).trim(); + } + else if (line.startsWithIgnoreCase ("countries:")) + { + countryCodes.addTokens (line.substring (10).trim(), true); + countryCodes.trim(); + countryCodes.removeEmptyStrings(); + } + } + + translations.minimiseStorageOverheads(); +} + +void LocalisedStrings::addStrings (const LocalisedStrings& other) +{ + jassert (languageName == other.languageName); + jassert (countryCodes == other.countryCodes); + + translations.addArray (other.translations); +} + +void LocalisedStrings::setFallback (LocalisedStrings* f) +{ + fallback = f; +} + +//============================================================================== +void LocalisedStrings::setCurrentMappings (LocalisedStrings* newTranslations) +{ + const SpinLock::ScopedLockType sl (currentMappingsLock); + currentMappings = newTranslations; +} + +LocalisedStrings* LocalisedStrings::getCurrentMappings() +{ + return currentMappings; +} + +String LocalisedStrings::translateWithCurrentMappings (const String& text) { return juce::translate (text); } +String LocalisedStrings::translateWithCurrentMappings (const char* text) { return juce::translate (text); } + +JUCE_API String translate (const String& text) { return juce::translate (text, text); } +JUCE_API String translate (const char* text) { return juce::translate (String (text)); } +JUCE_API String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); } + +JUCE_API String translate (const String& text, const String& resultIfNotFound) +{ + const SpinLock::ScopedLockType sl (currentMappingsLock); + + if (const LocalisedStrings* const mappings = LocalisedStrings::getCurrentMappings()) + return mappings->translate (text, resultIfNotFound); + + return resultIfNotFound; +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h new file mode 100644 index 0000000..a594919 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h @@ -0,0 +1,247 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_LOCALISEDSTRINGS_H_INCLUDED +#define JUCE_LOCALISEDSTRINGS_H_INCLUDED + + +//============================================================================== +/** + Used to convert strings to localised foreign-language versions. + + This is basically a look-up table of strings and their translated equivalents. + It can be loaded from a text file, so that you can supply a set of localised + versions of strings that you use in your app. + + To use it in your code, simply call the translate() method on each string that + might have foreign versions, and if none is found, the method will just return + the original string. + + The translation file should start with some lines specifying a description of + the language it contains, and also a list of ISO country codes where it might + be appropriate to use the file. After that, each line of the file should contain + a pair of quoted strings with an '=' sign. + + E.g. for a french translation, the file might be: + + @code + language: French + countries: fr be mc ch lu + + "hello" = "bonjour" + "goodbye" = "au revoir" + @endcode + + If the strings need to contain a quote character, they can use '\"' instead, and + if the first non-whitespace character on a line isn't a quote, then it's ignored, + (you can use this to add comments). + + Note that this is a singleton class, so don't create or destroy the object directly. + There's also a TRANS(text) macro defined to make it easy to use the this. + + E.g. @code + printSomething (TRANS("hello")); + @endcode + + This macro is used in the Juce classes themselves, so your application has a chance to + intercept and translate any internal Juce text strings that might be shown. (You can easily + get a list of all the messages by searching for the TRANS() macro in the Juce source + code). +*/ +class JUCE_API LocalisedStrings +{ +public: + //============================================================================== + /** Creates a set of translations from the text of a translation file. + + When you create one of these, you can call setCurrentMappings() to make it + the set of mappings that the system's using. + */ + LocalisedStrings (const String& fileContents, bool ignoreCaseOfKeys); + + /** Creates a set of translations from a file. + + When you create one of these, you can call setCurrentMappings() to make it + the set of mappings that the system's using. + */ + LocalisedStrings (const File& fileToLoad, bool ignoreCaseOfKeys); + + LocalisedStrings (const LocalisedStrings&); + LocalisedStrings& operator= (const LocalisedStrings&); + + /** Destructor. */ + ~LocalisedStrings(); + + //============================================================================== + /** Selects the current set of mappings to be used by the system. + + The object you pass in will be automatically deleted when no longer needed, so + don't keep a pointer to it. You can also pass in nullptr to remove the current + mappings. + + See also the TRANS() macro, which uses the current set to do its translation. + + @see translateWithCurrentMappings + */ + static void setCurrentMappings (LocalisedStrings* newTranslations); + + /** Returns the currently selected set of mappings. + + This is the object that was last passed to setCurrentMappings(). It may + be nullptr if none has been created. + */ + static LocalisedStrings* getCurrentMappings(); + + /** Tries to translate a string using the currently selected set of mappings. + + If no mapping has been set, or if the mapping doesn't contain a translation + for the string, this will just return the original string. + + See also the TRANS() macro, which uses this method to do its translation. + + @see setCurrentMappings, getCurrentMappings + */ + static String translateWithCurrentMappings (const String& text); + + /** Tries to translate a string using the currently selected set of mappings. + + If no mapping has been set, or if the mapping doesn't contain a translation + for the string, this will just return the original string. + + See also the TRANS() macro, which uses this method to do its translation. + + @see setCurrentMappings, getCurrentMappings + */ + static String translateWithCurrentMappings (const char* text); + + //============================================================================== + /** Attempts to look up a string and return its localised version. + If the string isn't found in the list, the original string will be returned. + */ + String translate (const String& text) const; + + /** Attempts to look up a string and return its localised version. + If the string isn't found in the list, the resultIfNotFound string will be returned. + */ + String translate (const String& text, const String& resultIfNotFound) const; + + /** Returns the name of the language specified in the translation file. + + This is specified in the file using a line starting with "language:", e.g. + @code + language: german + @endcode + */ + String getLanguageName() const { return languageName; } + + /** Returns the list of suitable country codes listed in the translation file. + + These is specified in the file using a line starting with "countries:", e.g. + @code + countries: fr be mc ch lu + @endcode + + The country codes are supposed to be 2-character ISO complient codes. + */ + const StringArray& getCountryCodes() const { return countryCodes; } + + /** Provides access to the actual list of mappings. */ + const StringPairArray& getMappings() const { return translations; } + + //============================================================================== + /** Adds and merges another set of translations into this set. + + Note that the language name and country codes of the new LocalisedStrings + object must match that of this object - an assertion will be thrown if they + don't match. + + Any existing values will have their mappings overwritten by the new ones. + */ + void addStrings (const LocalisedStrings&); + + /** Gives this object a set of strings to use as a fallback if a string isn't found. + The object that is passed-in will be owned and deleted by this object + when no longer needed. It can be nullptr to clear the existing fallback object. + */ + void setFallback (LocalisedStrings* fallbackStrings); + +private: + //============================================================================== + String languageName; + StringArray countryCodes; + StringPairArray translations; + ScopedPointer fallback; + friend struct ContainerDeletePolicy; + + void loadFromText (const String&, bool ignoreCase); + + JUCE_LEAK_DETECTOR (LocalisedStrings) +}; + +//============================================================================== +#ifndef TRANS + /** Uses the LocalisedStrings class to translate the given string literal. + This macro is provided for backwards-compatibility, and just calls the translate() + function. In new code, it's recommended that you just call translate() directly + instead, and avoid using macros. + @see translate(), LocalisedStrings + */ + #define TRANS(stringLiteral) juce::translate (stringLiteral) +#endif + +/** A dummy version of the TRANS macro, used to indicate a string literal that should be + added to the translation file by source-code scanner tools. + + Wrapping a string literal in this macro has no effect, but by using it around strings + that your app needs to translate at a later stage, it lets automatic code-scanning tools + find this string and add it to the list of strings that need translation. +*/ +#define NEEDS_TRANS(stringLiteral) (stringLiteral) + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +JUCE_API String translate (const String& stringLiteral); + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +JUCE_API String translate (const char* stringLiteral); + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +JUCE_API String translate (CharPointer_UTF8 stringLiteral); + +/** Uses the LocalisedStrings class to translate the given string literal. + @see LocalisedStrings +*/ +JUCE_API String translate (const String& stringLiteral, const String& resultIfNotFound); + + +#endif // JUCE_LOCALISEDSTRINGS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_NewLine.h b/JuceLibraryCode/modules/juce_core/text/juce_NewLine.h new file mode 100644 index 0000000..980ac23 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_NewLine.h @@ -0,0 +1,86 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_NEWLINE_H_INCLUDED +#define JUCE_NEWLINE_H_INCLUDED + + +//============================================================================== +/** This class is used for represent a new-line character sequence. + + To write a new-line to a stream, you can use the predefined 'newLine' variable, e.g. + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode + + The exact character sequence that will be used for the new-line can be set and + retrieved with OutputStream::setNewLineString() and OutputStream::getNewLineString(). +*/ +class JUCE_API NewLine +{ +public: + /** Returns the default new-line sequence that the library uses. + @see OutputStream::setNewLineString() + */ + static const char* getDefault() noexcept { return "\r\n"; } + + /** Returns the default new-line sequence that the library uses. + @see getDefault() + */ + operator String() const { return getDefault(); } + + /** Returns the default new-line sequence that the library uses. + @see OutputStream::setNewLineString() + */ + operator StringRef() const noexcept { return getDefault(); } +}; + +//============================================================================== +/** A predefined object representing a new-line, which can be written to a string or stream. + + To write a new-line to a stream, you can use the predefined 'newLine' variable like this: + @code + myOutputStream << "Hello World" << newLine << newLine; + @endcode +*/ +extern NewLine newLine; + +//============================================================================== +/** Writes a new-line sequence to a string. + You can use the predefined object 'newLine' to invoke this, e.g. + @code + myString << "Hello World" << newLine << newLine; + @endcode +*/ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&); + +#if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN) + inline String operator+ (String s1, const NewLine&) { return s1 += NewLine::getDefault(); } +#endif + +#endif // JUCE_NEWLINE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp new file mode 100644 index 0000000..1dce723 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp @@ -0,0 +1,2543 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4514 4996) +#endif + +NewLine newLine; + +#if defined (JUCE_STRINGS_ARE_UNICODE) && ! JUCE_STRINGS_ARE_UNICODE + #error "JUCE_STRINGS_ARE_UNICODE is deprecated! All strings are now unicode by default." +#endif + +#if JUCE_NATIVE_WCHAR_IS_UTF8 + typedef CharPointer_UTF8 CharPointer_wchar_t; +#elif JUCE_NATIVE_WCHAR_IS_UTF16 + typedef CharPointer_UTF16 CharPointer_wchar_t; +#else + typedef CharPointer_UTF32 CharPointer_wchar_t; +#endif + +static inline CharPointer_wchar_t castToCharPointer_wchar_t (const void* t) noexcept +{ + return CharPointer_wchar_t (static_cast (t)); +} + +//============================================================================== +// (Mirrors the structure of StringHolder, but without the atomic member, so can be statically constructed) +struct EmptyString +{ + int refCount; + size_t allocatedBytes; + String::CharPointerType::CharType text; +}; + +static const EmptyString emptyString = { 0x3fffffff, sizeof (String::CharPointerType::CharType), 0 }; + +//============================================================================== +class StringHolder +{ +public: + StringHolder() JUCE_DELETED_FUNCTION; + + typedef String::CharPointerType CharPointerType; + typedef String::CharPointerType::CharType CharType; + + //============================================================================== + static CharPointerType createUninitialisedBytes (size_t numBytes) + { + numBytes = (numBytes + 3) & ~(size_t) 3; + StringHolder* const s = reinterpret_cast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); + s->refCount.value = 0; + s->allocatedNumBytes = numBytes; + return CharPointerType (s->text); + } + + template + static CharPointerType createFromCharPointer (const CharPointer text) + { + if (text.getAddress() == nullptr || text.isEmpty()) + return CharPointerType (&(emptyString.text)); + + CharPointer t (text); + size_t bytesNeeded = sizeof (CharType); + + while (! t.isEmpty()) + bytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeAll (text); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer text, size_t maxChars) + { + if (text.getAddress() == nullptr || text.isEmpty() || maxChars == 0) + return CharPointerType (&(emptyString.text)); + + CharPointer end (text); + size_t numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (numChars < maxChars && ! end.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (end.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (text, (int) numChars + 1); + return dest; + } + + template + static CharPointerType createFromCharPointer (const CharPointer start, const CharPointer end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return CharPointerType (&(emptyString.text)); + + CharPointer e (start); + int numChars = 0; + size_t bytesNeeded = sizeof (CharType); + + while (e < end && ! e.isEmpty()) + { + bytesNeeded += CharPointerType::getBytesRequiredFor (e.getAndAdvance()); + ++numChars; + } + + const CharPointerType dest (createUninitialisedBytes (bytesNeeded)); + CharPointerType (dest).writeWithCharLimit (start, numChars + 1); + return dest; + } + + static CharPointerType createFromCharPointer (const CharPointerType start, const CharPointerType end) + { + if (start.getAddress() == nullptr || start.isEmpty()) + return CharPointerType (&(emptyString.text)); + + const size_t numBytes = (size_t) (reinterpret_cast (end.getAddress()) + - reinterpret_cast (start.getAddress())); + const CharPointerType dest (createUninitialisedBytes (numBytes + sizeof (CharType))); + memcpy (dest.getAddress(), start, numBytes); + dest.getAddress()[numBytes / sizeof (CharType)] = 0; + return dest; + } + + static CharPointerType createFromFixedLength (const char* const src, const size_t numChars) + { + const CharPointerType dest (createUninitialisedBytes (numChars * sizeof (CharType) + sizeof (CharType))); + CharPointerType (dest).writeWithCharLimit (CharPointer_UTF8 (src), (int) (numChars + 1)); + return dest; + } + + //============================================================================== + static void retain (const CharPointerType text) noexcept + { + StringHolder* const b = bufferFromText (text); + + if (b != (StringHolder*) &emptyString) + ++(b->refCount); + } + + static inline void release (StringHolder* const b) noexcept + { + if (b != (StringHolder*) &emptyString) + if (--(b->refCount) == -1) + delete[] reinterpret_cast (b); + } + + static void release (const CharPointerType text) noexcept + { + release (bufferFromText (text)); + } + + static inline int getReferenceCount (const CharPointerType text) noexcept + { + return bufferFromText (text)->refCount.get() + 1; + } + + //============================================================================== + static CharPointerType makeUniqueWithByteSize (const CharPointerType text, size_t numBytes) + { + StringHolder* const b = bufferFromText (text); + + if (b == (StringHolder*) &emptyString) + { + CharPointerType newText (createUninitialisedBytes (numBytes)); + newText.writeNull(); + return newText; + } + + if (b->allocatedNumBytes >= numBytes && b->refCount.get() <= 0) + return text; + + CharPointerType newText (createUninitialisedBytes (jmax (b->allocatedNumBytes, numBytes))); + memcpy (newText.getAddress(), text.getAddress(), b->allocatedNumBytes); + release (b); + + return newText; + } + + static size_t getAllocatedNumBytes (const CharPointerType text) noexcept + { + return bufferFromText (text)->allocatedNumBytes; + } + + //============================================================================== + Atomic refCount; + size_t allocatedNumBytes; + CharType text[1]; + +private: + static inline StringHolder* bufferFromText (const CharPointerType text) noexcept + { + // (Can't use offsetof() here because of warnings about this not being a POD) + return reinterpret_cast (reinterpret_cast (text.getAddress()) + - (reinterpret_cast (reinterpret_cast (1)->text) - 1)); + } + + void compileTimeChecks() + { + // Let me know if any of these assertions fail on your system! + #if JUCE_NATIVE_WCHAR_IS_UTF8 + static_jassert (sizeof (wchar_t) == 1); + #elif JUCE_NATIVE_WCHAR_IS_UTF16 + static_jassert (sizeof (wchar_t) == 2); + #elif JUCE_NATIVE_WCHAR_IS_UTF32 + static_jassert (sizeof (wchar_t) == 4); + #else + #error "native wchar_t size is unknown" + #endif + + static_jassert (sizeof (EmptyString) == sizeof (StringHolder)); + } +}; + +const String String::empty; + +//============================================================================== +String::String() noexcept : text (&(emptyString.text)) +{ +} + +String::~String() noexcept +{ + StringHolder::release (text); +} + +String::String (const String& other) noexcept : text (other.text) +{ + StringHolder::retain (text); +} + +void String::swapWith (String& other) noexcept +{ + std::swap (text, other.text); +} + +void String::clear() noexcept +{ + StringHolder::release (text); + text = &(emptyString.text); +} + +String& String::operator= (const String& other) noexcept +{ + StringHolder::retain (other.text); + StringHolder::release (text.atomicSwap (other.text)); + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +String::String (String&& other) noexcept : text (other.text) +{ + other.text = &(emptyString.text); +} + +String& String::operator= (String&& other) noexcept +{ + std::swap (text, other.text); + return *this; +} +#endif + +inline String::PreallocationBytes::PreallocationBytes (const size_t num) noexcept : numBytes (num) {} + +String::String (const PreallocationBytes& preallocationSize) + : text (StringHolder::createUninitialisedBytes (preallocationSize.numBytes + sizeof (CharPointerType::CharType))) +{ +} + +void String::preallocateBytes (const size_t numBytesNeeded) +{ + text = StringHolder::makeUniqueWithByteSize (text, numBytesNeeded + sizeof (CharPointerType::CharType)); +} + +int String::getReferenceCount() const noexcept +{ + return StringHolder::getReferenceCount (text); +} + +//============================================================================== +String::String (const char* const t) + : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t))) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + + Note that the Introjucer has a handy string literal generator utility that will convert + any unicode string to a valid C++ string literal, creating ascii escape sequences that will + work in any compiler. + */ + jassert (t == nullptr || CharPointer_ASCII::isValidString (t, std::numeric_limits::max())); +} + +String::String (const char* const t, const size_t maxChars) + : text (StringHolder::createFromCharPointer (CharPointer_ASCII (t), maxChars)) +{ + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the String class - so for example if your source data is actually UTF-8, + you'd call String (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + + Note that the Introjucer has a handy string literal generator utility that will convert + any unicode string to a valid C++ string literal, creating ascii escape sequences that will + work in any compiler. + */ + jassert (t == nullptr || CharPointer_ASCII::isValidString (t, (int) maxChars)); +} + +String::String (const wchar_t* const t) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t))) {} +String::String (const CharPointer_UTF8 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_UTF16 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_UTF32 t) : text (StringHolder::createFromCharPointer (t)) {} +String::String (const CharPointer_ASCII t) : text (StringHolder::createFromCharPointer (t)) {} + +String::String (const CharPointer_UTF8 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const CharPointer_UTF16 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const CharPointer_UTF32 t, const size_t maxChars) : text (StringHolder::createFromCharPointer (t, maxChars)) {} +String::String (const wchar_t* const t, size_t maxChars) : text (StringHolder::createFromCharPointer (castToCharPointer_wchar_t (t), maxChars)) {} + +String::String (const CharPointer_UTF8 start, const CharPointer_UTF8 end) : text (StringHolder::createFromCharPointer (start, end)) {} +String::String (const CharPointer_UTF16 start, const CharPointer_UTF16 end) : text (StringHolder::createFromCharPointer (start, end)) {} +String::String (const CharPointer_UTF32 start, const CharPointer_UTF32 end) : text (StringHolder::createFromCharPointer (start, end)) {} + +String::String (const std::string& s) : text (StringHolder::createFromFixedLength (s.data(), s.size())) {} + +String String::charToString (const juce_wchar character) +{ + String result (PreallocationBytes (CharPointerType::getBytesRequiredFor (character))); + CharPointerType t (result.text); + t.write (character); + t.writeNull(); + return result; +} + +//============================================================================== +namespace NumberToStringConverters +{ + enum + { + charsNeededForInt = 32, + charsNeededForDouble = 48 + }; + + template + static char* printDigits (char* t, Type v) noexcept + { + *--t = 0; + + do + { + *--t = '0' + (char) (v % 10); + v /= 10; + + } while (v > 0); + + return t; + } + + // pass in a pointer to the END of a buffer.. + static char* numberToString (char* t, const int64 n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, uint64 v) noexcept + { + return printDigits (t, v); + } + + static char* numberToString (char* t, const int n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + // NB: this needs to be careful not to call -std::numeric_limits::min(), + // which has undefined behaviour + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, unsigned int v) noexcept + { + return printDigits (t, v); + } + + struct StackArrayStream : public std::basic_streambuf > + { + explicit StackArrayStream (char* d) + { + static const std::locale classicLocale (std::locale::classic()); + imbue (classicLocale); + setp (d, d + charsNeededForDouble); + } + + size_t writeDouble (double n, int numDecPlaces) + { + { + std::ostream o (this); + + if (numDecPlaces > 0) + o.precision ((std::streamsize) numDecPlaces); + + o << n; + } + + return (size_t) (pptr() - pbase()); + } + }; + + static char* doubleToString (char* buffer, const int numChars, double n, int numDecPlaces, size_t& len) noexcept + { + if (numDecPlaces > 0 && numDecPlaces < 7 && n > -1.0e20 && n < 1.0e20) + { + char* const end = buffer + numChars; + char* t = end; + int64 v = (int64) (pow (10.0, numDecPlaces) * std::abs (n) + 0.5); + *--t = (char) 0; + + while (numDecPlaces >= 0 || v > 0) + { + if (numDecPlaces == 0) + *--t = '.'; + + *--t = (char) ('0' + (v % 10)); + + v /= 10; + --numDecPlaces; + } + + if (n < 0) + *--t = '-'; + + len = (size_t) (end - t - 1); + return t; + } + + StackArrayStream strm (buffer); + len = strm.writeDouble (n, numDecPlaces); + jassert (len <= charsNeededForDouble); + return buffer; + } + + template + static String::CharPointerType createFromInteger (const IntegerType number) + { + char buffer [charsNeededForInt]; + char* const end = buffer + numElementsInArray (buffer); + char* const start = numberToString (end, number); + return StringHolder::createFromFixedLength (start, (size_t) (end - start - 1)); + } + + static String::CharPointerType createFromDouble (const double number, const int numberOfDecimalPlaces) + { + char buffer [charsNeededForDouble]; + size_t len; + char* const start = doubleToString (buffer, numElementsInArray (buffer), (double) number, numberOfDecimalPlaces, len); + return StringHolder::createFromFixedLength (start, len); + } +} + +//============================================================================== +String::String (const int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const unsigned int number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const short number) : text (NumberToStringConverters::createFromInteger ((int) number)) {} +String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} +String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} + +String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} +String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} +String::String (const float number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble ((double) number, numberOfDecimalPlaces)) {} +String::String (const double number, const int numberOfDecimalPlaces) : text (NumberToStringConverters::createFromDouble (number, numberOfDecimalPlaces)) {} + +//============================================================================== +int String::length() const noexcept +{ + return (int) text.length(); +} + +static size_t findByteOffsetOfEnd (String::CharPointerType text) noexcept +{ + return (size_t) (((char*) text.findTerminatingNull().getAddress()) - (char*) text.getAddress()); +} + +size_t String::getByteOffsetOfEnd() const noexcept +{ + return findByteOffsetOfEnd (text); +} + +juce_wchar String::operator[] (int index) const noexcept +{ + jassert (index == 0 || (index > 0 && index <= (int) text.lengthUpTo ((size_t) index + 1))); + return text [index]; +} + +template +struct HashGenerator +{ + template + static Type calculate (CharPointer t) noexcept + { + Type result = Type(); + + while (! t.isEmpty()) + result = ((Type) multiplier) * result + (Type) t.getAndAdvance(); + + return result; + } + + enum { multiplier = sizeof (Type) > 4 ? 101 : 31 }; +}; + +int String::hashCode() const noexcept { return HashGenerator ::calculate (text); } +int64 String::hashCode64() const noexcept { return HashGenerator ::calculate (text); } +std::size_t String::hash() const noexcept { return HashGenerator::calculate (text); } + +//============================================================================== +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const String& s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const char* s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const char* s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const wchar_t* s2) noexcept { return s1.compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const wchar_t* s2) noexcept { return s1.compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, StringRef s2) noexcept { return s1.getCharPointer().compare (s2.text) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF8 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF16 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) == 0; } +JUCE_API bool JUCE_CALLTYPE operator!= (const String& s1, const CharPointer_UTF32 s2) noexcept { return s1.getCharPointer().compare (s2) != 0; } +JUCE_API bool JUCE_CALLTYPE operator> (const String& s1, const String& s2) noexcept { return s1.compare (s2) > 0; } +JUCE_API bool JUCE_CALLTYPE operator< (const String& s1, const String& s2) noexcept { return s1.compare (s2) < 0; } +JUCE_API bool JUCE_CALLTYPE operator>= (const String& s1, const String& s2) noexcept { return s1.compare (s2) >= 0; } +JUCE_API bool JUCE_CALLTYPE operator<= (const String& s1, const String& s2) noexcept { return s1.compare (s2) <= 0; } + +bool String::equalsIgnoreCase (const wchar_t* const t) const noexcept +{ + return t != nullptr ? text.compareIgnoreCase (castToCharPointer_wchar_t (t)) == 0 + : isEmpty(); +} + +bool String::equalsIgnoreCase (const char* const t) const noexcept +{ + return t != nullptr ? text.compareIgnoreCase (CharPointer_UTF8 (t)) == 0 + : isEmpty(); +} + +bool String::equalsIgnoreCase (StringRef t) const noexcept +{ + return text.compareIgnoreCase (t.text) == 0; +} + +bool String::equalsIgnoreCase (const String& other) const noexcept +{ + return text == other.text + || text.compareIgnoreCase (other.text) == 0; +} + +int String::compare (const String& other) const noexcept { return (text == other.text) ? 0 : text.compare (other.text); } +int String::compare (const char* const other) const noexcept { return text.compare (CharPointer_UTF8 (other)); } +int String::compare (const wchar_t* const other) const noexcept { return text.compare (castToCharPointer_wchar_t (other)); } +int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); } + +static int stringCompareRight (String::CharPointerType s1, String::CharPointerType s2) noexcept +{ + for (int bias = 0;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + const bool isDigit1 = CharacterFunctions::isDigit (c1); + + const juce_wchar c2 = s2.getAndAdvance(); + const bool isDigit2 = CharacterFunctions::isDigit (c2); + + if (! (isDigit1 || isDigit2)) return bias; + if (! isDigit1) return -1; + if (! isDigit2) return 1; + + if (c1 != c2 && bias == 0) + bias = c1 < c2 ? -1 : 1; + + jassert (c1 != 0 && c2 != 0); + } +} + +static int stringCompareLeft (String::CharPointerType s1, String::CharPointerType s2) noexcept +{ + for (;;) + { + const juce_wchar c1 = s1.getAndAdvance(); + const bool isDigit1 = CharacterFunctions::isDigit (c1); + + const juce_wchar c2 = s2.getAndAdvance(); + const bool isDigit2 = CharacterFunctions::isDigit (c2); + + if (! (isDigit1 || isDigit2)) return 0; + if (! isDigit1) return -1; + if (! isDigit2) return 1; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + } +} + +static int naturalStringCompare (String::CharPointerType s1, String::CharPointerType s2) noexcept +{ + bool firstLoop = true; + + for (;;) + { + const bool hasSpace1 = s1.isWhitespace(); + const bool hasSpace2 = s2.isWhitespace(); + + if ((! firstLoop) && (hasSpace1 ^ hasSpace2)) + return hasSpace2 ? 1 : -1; + + firstLoop = false; + + if (hasSpace1) s1 = s1.findEndOfWhitespace(); + if (hasSpace2) s2 = s2.findEndOfWhitespace(); + + if (s1.isDigit() && s2.isDigit()) + { + const int result = (*s1 == '0' || *s2 == '0') ? stringCompareLeft (s1, s2) + : stringCompareRight (s1, s2); + + if (result != 0) + return result; + } + + juce_wchar c1 = s1.getAndAdvance(); + juce_wchar c2 = s2.getAndAdvance(); + + if (c1 != c2) + { + c1 = CharacterFunctions::toUpperCase (c1); + c2 = CharacterFunctions::toUpperCase (c2); + } + + if (c1 == c2) + { + if (c1 == 0) + return 0; + } + else + { + const bool isAlphaNum1 = CharacterFunctions::isLetterOrDigit (c1); + const bool isAlphaNum2 = CharacterFunctions::isLetterOrDigit (c2); + + if (isAlphaNum2 && ! isAlphaNum1) return -1; + if (isAlphaNum1 && ! isAlphaNum2) return 1; + + return c1 < c2 ? -1 : 1; + } + + jassert (c1 != 0 && c2 != 0); + } +} + +int String::compareNatural (StringRef other) const noexcept +{ + return naturalStringCompare (getCharPointer(), other.text); +} + +//============================================================================== +void String::append (const String& textToAppend, size_t maxCharsToTake) +{ + appendCharPointer (textToAppend.text, maxCharsToTake); +} + +void String::appendCharPointer (const CharPointerType textToAppend) +{ + appendCharPointer (textToAppend, textToAppend.findTerminatingNull()); +} + +void String::appendCharPointer (const CharPointerType startOfTextToAppend, + const CharPointerType endOfTextToAppend) +{ + jassert (startOfTextToAppend.getAddress() != nullptr && endOfTextToAppend.getAddress() != nullptr); + + const int extraBytesNeeded = getAddressDifference (endOfTextToAppend.getAddress(), + startOfTextToAppend.getAddress()); + jassert (extraBytesNeeded >= 0); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + preallocateBytes (byteOffsetOfNull + (size_t) extraBytesNeeded); + + CharPointerType::CharType* const newStringStart = addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull); + memcpy (newStringStart, startOfTextToAppend.getAddress(), (size_t) extraBytesNeeded); + CharPointerType (addBytesToPointer (newStringStart, extraBytesNeeded)).writeNull(); + } +} + +String& String::operator+= (const wchar_t* const t) +{ + appendCharPointer (castToCharPointer_wchar_t (t)); + return *this; +} + +String& String::operator+= (const char* const t) +{ + appendCharPointer (CharPointer_UTF8 (t)); // (using UTF8 here triggers a faster code-path than ascii) + return *this; +} + +String& String::operator+= (const String& other) +{ + if (isEmpty()) + return operator= (other); + + appendCharPointer (other.text); + return *this; +} + +String& String::operator+= (const char ch) +{ + const char asString[] = { ch, 0 }; + return operator+= (asString); +} + +String& String::operator+= (const wchar_t ch) +{ + const wchar_t asString[] = { ch, 0 }; + return operator+= (asString); +} + +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +String& String::operator+= (const juce_wchar ch) +{ + const juce_wchar asString[] = { ch, 0 }; + appendCharPointer (CharPointer_UTF32 (asString)); + return *this; +} +#endif + +String& String::operator+= (const int number) +{ + char buffer [16]; + char* end = buffer + numElementsInArray (buffer); + char* start = NumberToStringConverters::numberToString (end, number); + + #if (JUCE_STRING_UTF_TYPE == 8) + appendCharPointer (CharPointerType (start), CharPointerType (end)); + #else + appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); + #endif + return *this; +} + +String& String::operator+= (int64 number) +{ + char buffer [32]; + char* end = buffer + numElementsInArray (buffer); + char* start = NumberToStringConverters::numberToString (end, number); + + #if (JUCE_STRING_UTF_TYPE == 8) + appendCharPointer (CharPointerType (start), CharPointerType (end)); + #else + appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); + #endif + return *this; +} + +//============================================================================== +JUCE_API String JUCE_CALLTYPE operator+ (const char* const s1, const String& s2) { String s (s1); return s += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* const s1, const String& s2) { String s (s1); return s += s2; } + +JUCE_API String JUCE_CALLTYPE operator+ (const char s1, const String& s2) { return String::charToString ((juce_wchar) (uint8) s1) + s2; } +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t s1, const String& s2) { return String::charToString (s1) + s2; } + +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const String& s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char* const s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t* s2) { return s1 += s2; } + +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const char s2) { return s1 += s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const wchar_t s2) { return s1 += s2; } + +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +JUCE_API String JUCE_CALLTYPE operator+ (const juce_wchar s1, const String& s2) { return String::charToString (s1) + s2; } +JUCE_API String JUCE_CALLTYPE operator+ (String s1, const juce_wchar s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const juce_wchar s2) { return s1 += s2; } +#endif + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t s2) { return s1 += s2; } + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const char* const s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const wchar_t* const s2) { return s1 += s2; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const String& s2) { return s1 += s2; } + +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int64 number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) +{ + return operator<< (stream, StringRef (text)); +} + +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, StringRef text) +{ + const size_t numBytes = CharPointer_UTF8::getBytesRequiredFor (text.text); + + #if (JUCE_STRING_UTF_TYPE == 8) + stream.write (text.text.getAddress(), numBytes); + #else + // (This avoids using toUTF8() to prevent the memory bloat that it would leave behind + // if lots of large, persistent strings were to be written to streams). + HeapBlock temp (numBytes + 1); + CharPointer_UTF8 (temp).writeAll (text.text); + stream.write (temp, numBytes); + #endif + + return stream; +} + +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const NewLine&) +{ + return string1 += NewLine::getDefault(); +} + +//============================================================================== +int String::indexOfChar (const juce_wchar character) const noexcept +{ + return text.indexOf (character); +} + +int String::indexOfChar (const int startIndex, const juce_wchar character) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (t.getAndAdvance() == character) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::lastIndexOfChar (const juce_wchar character) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (t.getAndAdvance() == character) + last = i; + + return last; +} + +int String::indexOfAnyOf (StringRef charactersToLookFor, const int startIndex, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + + for (int i = 0; ! t.isEmpty(); ++i) + { + if (i >= startIndex) + { + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + return i; + } + else + { + ++t; + } + } + + return -1; +} + +int String::indexOf (StringRef other) const noexcept +{ + return other.isEmpty() ? 0 : text.indexOf (other.text); +} + +int String::indexOfIgnoreCase (StringRef other) const noexcept +{ + return other.isEmpty() ? 0 : CharacterFunctions::indexOfIgnoreCase (text, other.text); +} + +int String::indexOf (const int startIndex, StringRef other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = t.indexOf (other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::indexOfIgnoreCase (const int startIndex, StringRef other) const noexcept +{ + if (other.isEmpty()) + return -1; + + CharPointerType t (text); + + for (int i = startIndex; --i >= 0;) + { + if (t.isEmpty()) + return -1; + + ++t; + } + + int found = CharacterFunctions::indexOfIgnoreCase (t, other.text); + if (found >= 0) + found += startIndex; + return found; +} + +int String::lastIndexOf (StringRef other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + for (CharPointerType n (text + i); i >= 0; --i) + { + if (n.compareUpTo (other.text, len) == 0) + return i; + + --n; + } + } + } + + return -1; +} + +int String::lastIndexOfIgnoreCase (StringRef other) const noexcept +{ + if (other.isNotEmpty()) + { + const int len = other.length(); + int i = length() - len; + + if (i >= 0) + { + for (CharPointerType n (text + i); i >= 0; --i) + { + if (n.compareIgnoreCaseUpTo (other.text, len) == 0) + return i; + + --n; + } + } + } + + return -1; +} + +int String::lastIndexOfAnyOf (StringRef charactersToLookFor, const bool ignoreCase) const noexcept +{ + CharPointerType t (text); + int last = -1; + + for (int i = 0; ! t.isEmpty(); ++i) + if (charactersToLookFor.text.indexOf (t.getAndAdvance(), ignoreCase) >= 0) + last = i; + + return last; +} + +bool String::contains (StringRef other) const noexcept +{ + return indexOf (other) >= 0; +} + +bool String::containsChar (const juce_wchar character) const noexcept +{ + return text.indexOf (character) >= 0; +} + +bool String::containsIgnoreCase (StringRef t) const noexcept +{ + return indexOfIgnoreCase (t) >= 0; +} + +int String::indexOfWholeWord (StringRef word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +int String::indexOfWholeWordIgnoreCase (StringRef word) const noexcept +{ + if (word.isNotEmpty()) + { + CharPointerType t (text); + const int wordLen = word.length(); + const int end = (int) t.length() - wordLen; + + for (int i = 0; i <= end; ++i) + { + if (t.compareIgnoreCaseUpTo (word.text, wordLen) == 0 + && (i == 0 || ! (t - 1).isLetterOrDigit()) + && ! (t + wordLen).isLetterOrDigit()) + return i; + + ++t; + } + } + + return -1; +} + +bool String::containsWholeWord (StringRef wordToLookFor) const noexcept +{ + return indexOfWholeWord (wordToLookFor) >= 0; +} + +bool String::containsWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept +{ + return indexOfWholeWordIgnoreCase (wordToLookFor) >= 0; +} + +//============================================================================== +template +struct WildCardMatcher +{ + static bool matches (CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (;;) + { + const juce_wchar wc = wildcard.getAndAdvance(); + + if (wc == '*') + return wildcard.isEmpty() || matchesAnywhere (wildcard, test, ignoreCase); + + if (! characterMatches (wc, test.getAndAdvance(), ignoreCase)) + return false; + + if (wc == 0) + return true; + } + } + + static bool characterMatches (const juce_wchar wc, const juce_wchar tc, const bool ignoreCase) noexcept + { + return (wc == tc) || (wc == '?' && tc != 0) + || (ignoreCase && CharacterFunctions::toLowerCase (wc) == CharacterFunctions::toLowerCase (tc)); + } + + static bool matchesAnywhere (const CharPointer wildcard, CharPointer test, const bool ignoreCase) noexcept + { + for (; ! test.isEmpty(); ++test) + if (matches (wildcard, test, ignoreCase)) + return true; + + return false; + } +}; + +bool String::matchesWildcard (StringRef wildcard, const bool ignoreCase) const noexcept +{ + return WildCardMatcher::matches (wildcard.text, text, ignoreCase); +} + +//============================================================================== +String String::repeatedString (StringRef stringToRepeat, int numberOfTimesToRepeat) +{ + if (numberOfTimesToRepeat <= 0) + return String(); + + String result (PreallocationBytes (findByteOffsetOfEnd (stringToRepeat) * (size_t) numberOfTimesToRepeat)); + CharPointerType n (result.text); + + while (--numberOfTimesToRepeat >= 0) + n.writeAll (stringToRepeat.text); + + return result; +} + +String String::paddedLeft (const juce_wchar padCharacter, int minimumLength) const +{ + jassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeAll (text); + return result; +} + +String String::paddedRight (const juce_wchar padCharacter, int minimumLength) const +{ + jassert (padCharacter != 0); + + int extraChars = minimumLength; + CharPointerType end (text); + + while (! end.isEmpty()) + { + --extraChars; + ++end; + } + + if (extraChars <= 0 || padCharacter == 0) + return *this; + + const size_t currentByteSize = (size_t) (((char*) end.getAddress()) - (char*) text.getAddress()); + String result (PreallocationBytes (currentByteSize + (size_t) extraChars * CharPointerType::getBytesRequiredFor (padCharacter))); + CharPointerType n (result.text); + + n.writeAll (text); + + while (--extraChars >= 0) + n.write (padCharacter); + + n.writeNull(); + return result; +} + +//============================================================================== +String String::replaceSection (int index, int numCharsToReplace, StringRef stringToInsert) const +{ + if (index < 0) + { + // a negative index to replace from? + jassertfalse; + index = 0; + } + + if (numCharsToReplace < 0) + { + // replacing a negative number of characters? + numCharsToReplace = 0; + jassertfalse; + } + + CharPointerType insertPoint (text); + + for (int i = 0; i < index; ++i) + { + if (insertPoint.isEmpty()) + { + // replacing beyond the end of the string? + jassertfalse; + return *this + stringToInsert; + } + + ++insertPoint; + } + + CharPointerType startOfRemainder (insertPoint); + + for (int i = 0; i < numCharsToReplace && ! startOfRemainder.isEmpty(); ++i) + ++startOfRemainder; + + if (insertPoint == text && startOfRemainder.isEmpty()) + return stringToInsert.text; + + const size_t initialBytes = (size_t) (((char*) insertPoint.getAddress()) - (char*) text.getAddress()); + const size_t newStringBytes = findByteOffsetOfEnd (stringToInsert); + const size_t remainderBytes = (size_t) (((char*) startOfRemainder.findTerminatingNull().getAddress()) - (char*) startOfRemainder.getAddress()); + + const size_t newTotalBytes = initialBytes + newStringBytes + remainderBytes; + if (newTotalBytes <= 0) + return String(); + + String result (PreallocationBytes ((size_t) newTotalBytes)); + + char* dest = (char*) result.text.getAddress(); + memcpy (dest, text.getAddress(), initialBytes); + dest += initialBytes; + memcpy (dest, stringToInsert.text.getAddress(), newStringBytes); + dest += newStringBytes; + memcpy (dest, startOfRemainder.getAddress(), remainderBytes); + dest += remainderBytes; + CharPointerType ((CharPointerType::CharType*) dest).writeNull(); + + return result; +} + +String String::replace (StringRef stringToReplace, StringRef stringToInsert, const bool ignoreCase) const +{ + const int stringToReplaceLen = stringToReplace.length(); + const int stringToInsertLen = stringToInsert.length(); + + int i = 0; + String result (*this); + + while ((i = (ignoreCase ? result.indexOfIgnoreCase (i, stringToReplace) + : result.indexOf (i, stringToReplace))) >= 0) + { + result = result.replaceSection (i, stringToReplaceLen, stringToInsert); + i += stringToInsertLen; + } + + return result; +} + +class StringCreationHelper +{ +public: + StringCreationHelper (const size_t initialBytes) + : source (nullptr), dest (nullptr), allocatedBytes (initialBytes), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + StringCreationHelper (const String::CharPointerType s) + : source (s), dest (nullptr), allocatedBytes (StringHolder::getAllocatedNumBytes (s)), bytesWritten (0) + { + result.preallocateBytes (allocatedBytes); + dest = result.getCharPointer(); + } + + void write (juce_wchar c) + { + bytesWritten += String::CharPointerType::getBytesRequiredFor (c); + + if (bytesWritten > allocatedBytes) + { + allocatedBytes += jmax ((size_t) 8, allocatedBytes / 16); + const size_t destOffset = (size_t) (((char*) dest.getAddress()) - (char*) result.getCharPointer().getAddress()); + result.preallocateBytes (allocatedBytes); + dest = addBytesToPointer (result.getCharPointer().getAddress(), (int) destOffset); + } + + dest.write (c); + } + + String result; + String::CharPointerType source; + +private: + String::CharPointerType dest; + size_t allocatedBytes, bytesWritten; +}; + +String String::replaceCharacter (const juce_wchar charToReplace, const juce_wchar charToInsert) const +{ + if (! containsChar (charToReplace)) + return *this; + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (c == charToReplace) + c = charToInsert; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::replaceCharacters (StringRef charactersToReplace, StringRef charactersToInsertInstead) const +{ + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + const int index = charactersToReplace.text.indexOf (c); + if (index >= 0) + c = charactersToInsertInstead [index]; + + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +//============================================================================== +bool String::startsWith (StringRef other) const noexcept +{ + return text.compareUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithIgnoreCase (StringRef other) const noexcept +{ + return text.compareIgnoreCaseUpTo (other.text, other.length()) == 0; +} + +bool String::startsWithChar (const juce_wchar character) const noexcept +{ + jassert (character != 0); // strings can't contain a null character! + + return *text == character; +} + +bool String::endsWithChar (const juce_wchar character) const noexcept +{ + jassert (character != 0); // strings can't contain a null character! + + if (text.isEmpty()) + return false; + + CharPointerType t (text.findTerminatingNull()); + return *--t == character; +} + +bool String::endsWith (StringRef other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (*end != *otherEnd) + return false; + } + + return otherEnd == other.text; +} + +bool String::endsWithIgnoreCase (StringRef other) const noexcept +{ + CharPointerType end (text.findTerminatingNull()); + CharPointerType otherEnd (other.text.findTerminatingNull()); + + while (end > text && otherEnd > other.text) + { + --end; + --otherEnd; + + if (end.toLowerCase() != otherEnd.toLowerCase()) + return false; + } + + return otherEnd == other.text; +} + +//============================================================================== +String String::toUpperCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const juce_wchar c = builder.source.toUpperCase(); + builder.write (c); + + if (c == 0) + break; + + ++(builder.source); + } + + return builder.result; +} + +String String::toLowerCase() const +{ + StringCreationHelper builder (text); + + for (;;) + { + const juce_wchar c = builder.source.toLowerCase(); + builder.write (c); + + if (c == 0) + break; + + ++(builder.source); + } + + return builder.result; +} + +//============================================================================== +juce_wchar String::getLastCharacter() const noexcept +{ + return isEmpty() ? juce_wchar() : text [length() - 1]; +} + +String String::substring (int start, const int end) const +{ + if (start < 0) + start = 0; + + if (end <= start) + return String(); + + int i = 0; + CharPointerType t1 (text); + + while (i < start) + { + if (t1.isEmpty()) + return String(); + + ++i; + ++t1; + } + + CharPointerType t2 (t1); + while (i < end) + { + if (t2.isEmpty()) + { + if (start == 0) + return *this; + + break; + } + + ++i; + ++t2; + } + + return String (t1, t2); +} + +String String::substring (int start) const +{ + if (start <= 0) + return *this; + + CharPointerType t (text); + + while (--start >= 0) + { + if (t.isEmpty()) + return String(); + + ++t; + } + + return String (t); +} + +String String::dropLastCharacters (const int numberToDrop) const +{ + return String (text, (size_t) jmax (0, length() - numberToDrop)); +} + +String String::getLastCharacters (const int numCharacters) const +{ + return String (text + jmax (0, length() - jmax (0, numCharacters))); +} + +String String::fromFirstOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return String(); + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::fromLastOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (includeSubString ? i : i + sub.length()); +} + +String String::upToFirstOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? indexOfIgnoreCase (sub) + : indexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +String String::upToLastOccurrenceOf (StringRef sub, + const bool includeSubString, + const bool ignoreCase) const +{ + const int i = ignoreCase ? lastIndexOfIgnoreCase (sub) + : lastIndexOf (sub); + if (i < 0) + return *this; + + return substring (0, includeSubString ? i + sub.length() : i); +} + +bool String::isQuotedString() const +{ + const String trimmed (trimStart()); + + return trimmed[0] == '"' + || trimmed[0] == '\''; +} + +String String::unquoted() const +{ + const int len = length(); + + if (len == 0) + return String(); + + const juce_wchar lastChar = text [len - 1]; + const int dropAtStart = (*text == '"' || *text == '\'') ? 1 : 0; + const int dropAtEnd = (lastChar == '"' || lastChar == '\'') ? 1 : 0; + + return substring (dropAtStart, len - dropAtEnd); +} + +String String::quoted (const juce_wchar quoteCharacter) const +{ + if (isEmpty()) + return charToString (quoteCharacter) + quoteCharacter; + + String t (*this); + + if (! t.startsWithChar (quoteCharacter)) + t = charToString (quoteCharacter) + t; + + if (! t.endsWithChar (quoteCharacter)) + t += quoteCharacter; + + return t; +} + +//============================================================================== +static String::CharPointerType findTrimmedEnd (const String::CharPointerType start, + String::CharPointerType end) +{ + while (end > start) + { + if (! (--end).isWhitespace()) + { + ++end; + break; + } + } + + return end; +} + +String String::trim() const +{ + if (isNotEmpty()) + { + CharPointerType start (text.findEndOfWhitespace()); + + const CharPointerType end (start.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (start, end)); + + if (trimmedEnd <= start) + return String(); + + if (text < start || trimmedEnd < end) + return String (start, trimmedEnd); + } + + return *this; +} + +String String::trimStart() const +{ + if (isNotEmpty()) + { + const CharPointerType t (text.findEndOfWhitespace()); + + if (t != text) + return String (t); + } + + return *this; +} + +String String::trimEnd() const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (findTrimmedEnd (text, end)); + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +String String::trimCharactersAtStart (StringRef charactersToTrim) const +{ + CharPointerType t (text); + + while (charactersToTrim.text.indexOf (*t) >= 0) + ++t; + + return t == text ? *this : String (t); +} + +String String::trimCharactersAtEnd (StringRef charactersToTrim) const +{ + if (isNotEmpty()) + { + const CharPointerType end (text.findTerminatingNull()); + CharPointerType trimmedEnd (end); + + while (trimmedEnd > text) + { + if (charactersToTrim.text.indexOf (*--trimmedEnd) < 0) + { + ++trimmedEnd; + break; + } + } + + if (trimmedEnd < end) + return String (text, trimmedEnd); + } + + return *this; +} + +//============================================================================== +String String::retainCharacters (StringRef charactersToRetain) const +{ + if (isEmpty()) + return String(); + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (charactersToRetain.text.indexOf (c) >= 0) + builder.write (c); + + if (c == 0) + break; + } + + builder.write (0); + return builder.result; +} + +String String::removeCharacters (StringRef charactersToRemove) const +{ + if (isEmpty()) + return String(); + + StringCreationHelper builder (text); + + for (;;) + { + juce_wchar c = builder.source.getAndAdvance(); + + if (charactersToRemove.text.indexOf (c) < 0) + builder.write (c); + + if (c == 0) + break; + } + + return builder.result; +} + +String String::initialSectionContainingOnly (StringRef permittedCharacters) const +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (permittedCharacters.text.indexOf (*t) < 0) + return String (text, t); + + return *this; +} + +String String::initialSectionNotContaining (StringRef charactersToStopAt) const +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (charactersToStopAt.text.indexOf (*t) >= 0) + return String (text, t); + + return *this; +} + +bool String::containsOnly (StringRef chars) const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty();) + if (chars.text.indexOf (t.getAndAdvance()) < 0) + return false; + + return true; +} + +bool String::containsAnyOf (StringRef chars) const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty();) + if (chars.text.indexOf (t.getAndAdvance()) >= 0) + return true; + + return false; +} + +bool String::containsNonWhitespaceChars() const noexcept +{ + for (CharPointerType t (text); ! t.isEmpty(); ++t) + if (! t.isWhitespace()) + return true; + + return false; +} + +// Note! The format parameter here MUST NOT be a reference, otherwise MS's va_start macro fails to work (but still compiles). +String String::formatted (const String pf, ... ) +{ + size_t bufferSize = 256; + + for (;;) + { + va_list args; + va_start (args, pf); + + #if JUCE_WINDOWS + HeapBlock temp (bufferSize); + const int num = (int) _vsnwprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); + #elif JUCE_ANDROID + HeapBlock temp (bufferSize); + const int num = (int) vsnprintf (temp.getData(), bufferSize - 1, pf.toUTF8(), args); + #else + HeapBlock temp (bufferSize); + const int num = (int) vswprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args); + #endif + + va_end (args); + + if (num > 0) + return String (temp); + + bufferSize += 256; + + if (num == 0 || bufferSize > 65536) // the upper limit is a sanity check to avoid situations where vprintf repeatedly + break; // returns -1 because of an error rather than because it needs more space. + } + + return String(); +} + +//============================================================================== +int String::getIntValue() const noexcept { return text.getIntValue32(); } +int64 String::getLargeIntValue() const noexcept { return text.getIntValue64(); } +float String::getFloatValue() const noexcept { return (float) getDoubleValue(); } +double String::getDoubleValue() const noexcept { return text.getDoubleValue(); } + +int String::getTrailingIntValue() const noexcept +{ + int n = 0; + int mult = 1; + CharPointerType t (text.findTerminatingNull()); + + while (--t >= text) + { + if (! t.isDigit()) + { + if (*t == '-') + n = -n; + + break; + } + + n += mult * (*t - '0'); + mult *= 10; + } + + return n; +} + +static const char hexDigits[] = "0123456789abcdef"; + +template +static String hexToString (Type v) +{ + String::CharPointerType::CharType buffer[32]; + String::CharPointerType::CharType* const end = buffer + numElementsInArray (buffer) - 1; + String::CharPointerType::CharType* t = end; + *t = 0; + + do + { + *--t = hexDigits [(int) (v & 15)]; + v >>= 4; + + } while (v != 0); + + return String (String::CharPointerType (t), + String::CharPointerType (end)); +} + +String String::toHexString (int number) { return hexToString ((unsigned int) number); } +String String::toHexString (int64 number) { return hexToString ((uint64) number); } +String String::toHexString (short number) { return toHexString ((int) (unsigned short) number); } + +String String::toHexString (const void* const d, const int size, const int groupSize) +{ + if (size <= 0) + return String(); + + int numChars = (size * 2) + 2; + if (groupSize > 0) + numChars += size / groupSize; + + String s (PreallocationBytes (sizeof (CharPointerType::CharType) * (size_t) numChars)); + + const unsigned char* data = static_cast (d); + CharPointerType dest (s.text); + + for (int i = 0; i < size; ++i) + { + const unsigned char nextByte = *data++; + dest.write ((juce_wchar) hexDigits [nextByte >> 4]); + dest.write ((juce_wchar) hexDigits [nextByte & 0xf]); + + if (groupSize > 0 && (i % groupSize) == (groupSize - 1) && i < (size - 1)) + dest.write ((juce_wchar) ' '); + } + + dest.writeNull(); + return s; +} + +int String::getHexValue32() const noexcept { return CharacterFunctions::HexParser ::parse (text); } +int64 String::getHexValue64() const noexcept { return CharacterFunctions::HexParser::parse (text); } + +//============================================================================== +String String::createStringFromData (const void* const unknownData, const int size) +{ + const uint8* const data = static_cast (unknownData); + + if (size <= 0 || data == nullptr) + return String(); + + if (size == 1) + return charToString ((juce_wchar) data[0]); + + if (CharPointer_UTF16::isByteOrderMarkBigEndian (data) + || CharPointer_UTF16::isByteOrderMarkLittleEndian (data)) + { + const int numChars = size / 2 - 1; + + StringCreationHelper builder ((size_t) numChars); + + const uint16* const src = (const uint16*) (data + 2); + + if (CharPointer_UTF16::isByteOrderMarkBigEndian (data)) + { + for (int i = 0; i < numChars; ++i) + builder.write ((juce_wchar) ByteOrder::swapIfLittleEndian (src[i])); + } + else + { + for (int i = 0; i < numChars; ++i) + builder.write ((juce_wchar) ByteOrder::swapIfBigEndian (src[i])); + } + + builder.write (0); + return builder.result; + } + + const uint8* start = data; + + if (size >= 3 && CharPointer_UTF8::isByteOrderMark (data)) + start += 3; + + return String (CharPointer_UTF8 ((const char*) start), + CharPointer_UTF8 ((const char*) (data + size))); +} + +//============================================================================== +static const juce_wchar emptyChar = 0; + +template +struct StringEncodingConverter +{ + static CharPointerType_Dest convert (const String& s) + { + String& source = const_cast (s); + + typedef typename CharPointerType_Dest::CharType DestChar; + + if (source.isEmpty()) + return CharPointerType_Dest (reinterpret_cast (&emptyChar)); + + CharPointerType_Src text (source.getCharPointer()); + const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text) + sizeof (typename CharPointerType_Dest::CharType); + const size_t endOffset = (text.sizeInBytes() + 3) & ~3u; // the new string must be word-aligned or many Windows + // functions will fail to read it correctly! + source.preallocateBytes (endOffset + extraBytesNeeded); + text = source.getCharPointer(); + + void* const newSpace = addBytesToPointer (text.getAddress(), (int) endOffset); + const CharPointerType_Dest extraSpace (static_cast (newSpace)); + + #if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..) + const size_t bytesToClear = (size_t) jmin ((int) extraBytesNeeded, 4); + zeromem (addBytesToPointer (newSpace, extraBytesNeeded - bytesToClear), bytesToClear); + #endif + + CharPointerType_Dest (extraSpace).writeAll (text); + return extraSpace; + } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 ((CharPointer_UTF8::CharType*) source.getCharPointer().getAddress()); } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 ((CharPointer_UTF16::CharType*) source.getCharPointer().getAddress()); } +}; + +template <> +struct StringEncodingConverter +{ + static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 ((CharPointer_UTF32::CharType*) source.getCharPointer().getAddress()); } +}; + +CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter::convert (*this); } +CharPointer_UTF16 String::toUTF16() const { return StringEncodingConverter::convert (*this); } +CharPointer_UTF32 String::toUTF32() const { return StringEncodingConverter::convert (*this); } + +const char* String::toRawUTF8() const +{ + return toUTF8().getAddress(); +} + +const wchar_t* String::toWideCharPointer() const +{ + return StringEncodingConverter::convert (*this).getAddress(); +} + +std::string String::toStdString() const +{ + return std::string (toRawUTF8()); +} + +//============================================================================== +template +struct StringCopier +{ + static size_t copyToBuffer (const CharPointerType_Src source, typename CharPointerType_Dest::CharType* const buffer, const size_t maxBufferSizeBytes) + { + jassert (((ssize_t) maxBufferSizeBytes) >= 0); // keep this value positive! + + if (buffer == nullptr) + return CharPointerType_Dest::getBytesRequiredFor (source) + sizeof (typename CharPointerType_Dest::CharType); + + return CharPointerType_Dest (buffer).writeWithDestByteLimit (source, maxBufferSizeBytes); + } +}; + +size_t String::copyToUTF8 (CharPointer_UTF8::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +size_t String::copyToUTF16 (CharPointer_UTF16::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +size_t String::copyToUTF32 (CharPointer_UTF32::CharType* const buffer, size_t maxBufferSizeBytes) const noexcept +{ + return StringCopier::copyToBuffer (text, buffer, maxBufferSizeBytes); +} + +//============================================================================== +size_t String::getNumBytesAsUTF8() const noexcept +{ + return CharPointer_UTF8::getBytesRequiredFor (text); +} + +String String::fromUTF8 (const char* const buffer, int bufferSizeBytes) +{ + if (buffer != nullptr) + { + if (bufferSizeBytes < 0) + return String (CharPointer_UTF8 (buffer)); + + if (bufferSizeBytes > 0) + { + jassert (CharPointer_UTF8::isValidString (buffer, bufferSizeBytes)); + return String (CharPointer_UTF8 (buffer), CharPointer_UTF8 (buffer + bufferSizeBytes)); + } + } + + return String(); +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +StringRef::StringRef() noexcept : text ((const String::CharPointerType::CharType*) "\0\0\0") +{ +} + +StringRef::StringRef (const char* stringLiteral) noexcept + #if JUCE_STRING_UTF_TYPE != 8 + : text (nullptr), stringCopy (stringLiteral) + #else + : text (stringLiteral) + #endif +{ + #if JUCE_STRING_UTF_TYPE != 8 + text = stringCopy.getCharPointer(); + #endif + + jassert (stringLiteral != nullptr); // This must be a valid string literal, not a null pointer!! + + #if JUCE_NATIVE_WCHAR_IS_UTF8 + /* If you get an assertion here, then you're trying to create a string from 8-bit data + that contains values greater than 127. These can NOT be correctly converted to unicode + because there's no way for the String class to know what encoding was used to + create them. The source data could be UTF-8, ASCII or one of many local code-pages. + + To get around this problem, you must be more explicit when you pass an ambiguous 8-bit + string to the StringRef class - so for example if your source data is actually UTF-8, + you'd call StringRef (CharPointer_UTF8 ("my utf8 string..")), and it would be able to + correctly convert the multi-byte characters to unicode. It's *highly* recommended that + you use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent these strings in a way that isn't dependent on + the compiler, source code editor and platform. + */ + jassert (CharPointer_ASCII::isValidString (stringLiteral, std::numeric_limits::max())); + #endif +} + +StringRef::StringRef (String::CharPointerType stringLiteral) noexcept : text (stringLiteral) +{ + jassert (stringLiteral.getAddress() != nullptr); // This must be a valid string literal, not a null pointer!! +} + +StringRef::StringRef (const String& string) noexcept : text (string.getCharPointer()) {} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class StringTests : public UnitTest +{ +public: + StringTests() : UnitTest ("String class") {} + + template + struct TestUTFConversion + { + static void test (UnitTest& test, Random& r) + { + String s (createRandomWideCharString (r)); + + typename CharPointerType::CharType buffer [300]; + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF32()); + test.expectEquals (String (CharPointerType (buffer)), s); + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF16()); + test.expectEquals (String (CharPointerType (buffer)), s); + + memset (buffer, 0xff, sizeof (buffer)); + CharPointerType (buffer).writeAll (s.toUTF8()); + test.expectEquals (String (CharPointerType (buffer)), s); + + test.expect (CharPointerType::isValidString (buffer, (int) strlen ((const char*) buffer))); + } + }; + + static String createRandomWideCharString (Random& r) + { + juce_wchar buffer[50] = { 0 }; + + for (int i = 0; i < numElementsInArray (buffer) - 1; ++i) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) (1 + r.nextInt (0xff)); + } + + return CharPointer_UTF32 (buffer); + } + + void runTest() + { + Random r = getRandom(); + + { + beginTest ("Basics"); + + expect (String().length() == 0); + expect (String() == String::empty); + String s1, s2 ("abcd"); + expect (s1.isEmpty() && ! s1.isNotEmpty()); + expect (s2.isNotEmpty() && ! s2.isEmpty()); + expect (s2.length() == 4); + s1 = "abcd"; + expect (s2 == s1 && s1 == s2); + expect (s1 == "abcd" && s1 == L"abcd"); + expect (String ("abcd") == String (L"abcd")); + expect (String ("abcdefg", 4) == L"abcd"); + expect (String ("abcdefg", 4) == String (L"abcdefg", 4)); + expect (String::charToString ('x') == "x"); + expect (String::charToString (0) == String::empty); + expect (s2 + "e" == "abcde" && s2 + 'e' == "abcde"); + expect (s2 + L'e' == "abcde" && s2 + L"e" == "abcde"); + expect (s1.equalsIgnoreCase ("abcD") && s1 < "abce" && s1 > "abbb"); + expect (s1.startsWith ("ab") && s1.startsWith ("abcd") && ! s1.startsWith ("abcde")); + expect (s1.startsWithIgnoreCase ("aB") && s1.endsWithIgnoreCase ("CD")); + expect (s1.endsWith ("bcd") && ! s1.endsWith ("aabcd")); + expectEquals (s1.indexOf (String::empty), 0); + expectEquals (s1.indexOfIgnoreCase (String::empty), 0); + expect (s1.startsWith (String::empty) && s1.endsWith (String::empty) && s1.contains (String::empty)); + expect (s1.contains ("cd") && s1.contains ("ab") && s1.contains ("abcd")); + expect (s1.containsChar ('a')); + expect (! s1.containsChar ('x')); + expect (! s1.containsChar (0)); + expect (String ("abc foo bar").containsWholeWord ("abc") && String ("abc foo bar").containsWholeWord ("abc")); + } + + { + beginTest ("Operations"); + + String s ("012345678"); + expect (s.hashCode() != 0); + expect (s.hashCode64() != 0); + expect (s.hashCode() != (s + s).hashCode()); + expect (s.hashCode64() != (s + s).hashCode64()); + expect (s.compare (String ("012345678")) == 0); + expect (s.compare (String ("012345679")) < 0); + expect (s.compare (String ("012345676")) > 0); + expect (String("a").compareNatural ("A") == 0); + expect (String("A").compareNatural ("B") < 0); + expect (String("a").compareNatural ("B") < 0); + expect (String("10").compareNatural ("2") > 0); + expect (String("Abc 10").compareNatural ("aBC 2") > 0); + expect (String("Abc 1").compareNatural ("aBC 2") < 0); + expect (s.substring (2, 3) == String::charToString (s[2])); + expect (s.substring (0, 1) == String::charToString (s[0])); + expect (s.getLastCharacter() == s [s.length() - 1]); + expect (String::charToString (s.getLastCharacter()) == s.getLastCharacters (1)); + expect (s.substring (0, 3) == L"012"); + expect (s.substring (0, 100) == s); + expect (s.substring (-1, 100) == s); + expect (s.substring (3) == "345678"); + expect (s.indexOf (String (L"45")) == 4); + expect (String ("444445").indexOf ("45") == 4); + expect (String ("444445").lastIndexOfChar ('4') == 4); + expect (String ("45454545x").lastIndexOf (String (L"45")) == 6); + expect (String ("45454545x").lastIndexOfAnyOf ("456") == 7); + expect (String ("45454545x").lastIndexOfAnyOf (String (L"456x")) == 8); + expect (String ("abABaBaBa").lastIndexOfIgnoreCase ("aB") == 6); + expect (s.indexOfChar (L'4') == 4); + expect (s + s == "012345678012345678"); + expect (s.startsWith (s)); + expect (s.startsWith (s.substring (0, 4))); + expect (s.startsWith (s.dropLastCharacters (4))); + expect (s.endsWith (s.substring (5))); + expect (s.endsWith (s)); + expect (s.contains (s.substring (3, 6))); + expect (s.contains (s.substring (3))); + expect (s.startsWithChar (s[0])); + expect (s.endsWithChar (s.getLastCharacter())); + expect (s [s.length()] == 0); + expect (String ("abcdEFGH").toLowerCase() == String ("abcdefgh")); + expect (String ("abcdEFGH").toUpperCase() == String ("ABCDEFGH")); + + String s2 ("123"); + s2 << ((int) 4) << ((short) 5) << "678" << L"9" << '0'; + s2 += "xyz"; + expect (s2 == "1234567890xyz"); + s2 += (int) 123; + expect (s2 == "1234567890xyz123"); + s2 += (int64) 123; + expect (s2 == "1234567890xyz123123"); + + beginTest ("Numeric conversions"); + expect (String::empty.getIntValue() == 0); + expect (String::empty.getDoubleValue() == 0.0); + expect (String::empty.getFloatValue() == 0.0f); + expect (s.getIntValue() == 12345678); + expect (s.getLargeIntValue() == (int64) 12345678); + expect (s.getDoubleValue() == 12345678.0); + expect (s.getFloatValue() == 12345678.0f); + expect (String (-1234).getIntValue() == -1234); + expect (String ((int64) -1234).getLargeIntValue() == -1234); + expect (String (-1234.56).getDoubleValue() == -1234.56); + expect (String (-1234.56f).getFloatValue() == -1234.56f); + expect (String (std::numeric_limits::max()).getIntValue() == std::numeric_limits::max()); + expect (String (std::numeric_limits::min()).getIntValue() == std::numeric_limits::min()); + expect (String (std::numeric_limits::max()).getLargeIntValue() == std::numeric_limits::max()); + expect (String (std::numeric_limits::min()).getLargeIntValue() == std::numeric_limits::min()); + expect (("xyz" + s).getTrailingIntValue() == s.getIntValue()); + expect (s.getHexValue32() == 0x12345678); + expect (s.getHexValue64() == (int64) 0x12345678); + expect (String::toHexString (0x1234abcd).equalsIgnoreCase ("1234abcd")); + expect (String::toHexString ((int64) 0x1234abcd).equalsIgnoreCase ("1234abcd")); + expect (String::toHexString ((short) 0x12ab).equalsIgnoreCase ("12ab")); + + unsigned char data[] = { 1, 2, 3, 4, 0xa, 0xb, 0xc, 0xd }; + expect (String::toHexString (data, 8, 0).equalsIgnoreCase ("010203040a0b0c0d")); + expect (String::toHexString (data, 8, 1).equalsIgnoreCase ("01 02 03 04 0a 0b 0c 0d")); + expect (String::toHexString (data, 8, 2).equalsIgnoreCase ("0102 0304 0a0b 0c0d")); + + beginTest ("Subsections"); + String s3; + s3 = "abcdeFGHIJ"; + expect (s3.equalsIgnoreCase ("ABCdeFGhiJ")); + expect (s3.compareIgnoreCase (L"ABCdeFGhiJ") == 0); + expect (s3.containsIgnoreCase (s3.substring (3))); + expect (s3.indexOfAnyOf ("xyzf", 2, true) == 5); + expect (s3.indexOfAnyOf (String (L"xyzf"), 2, false) == -1); + expect (s3.indexOfAnyOf ("xyzF", 2, false) == 5); + expect (s3.containsAnyOf (String (L"zzzFs"))); + expect (s3.startsWith ("abcd")); + expect (s3.startsWithIgnoreCase (String (L"abCD"))); + expect (s3.startsWith (String::empty)); + expect (s3.startsWithChar ('a')); + expect (s3.endsWith (String ("HIJ"))); + expect (s3.endsWithIgnoreCase (String (L"Hij"))); + expect (s3.endsWith (String::empty)); + expect (s3.endsWithChar (L'J')); + expect (s3.indexOf ("HIJ") == 7); + expect (s3.indexOf (String (L"HIJK")) == -1); + expect (s3.indexOfIgnoreCase ("hij") == 7); + expect (s3.indexOfIgnoreCase (String (L"hijk")) == -1); + expect (s3.toStdString() == s3.toRawUTF8()); + + String s4 (s3); + s4.append (String ("xyz123"), 3); + expect (s4 == s3 + "xyz"); + + expect (String (1234) < String (1235)); + expect (String (1235) > String (1234)); + expect (String (1234) >= String (1234)); + expect (String (1234) <= String (1234)); + expect (String (1235) >= String (1234)); + expect (String (1234) <= String (1235)); + + String s5 ("word word2 word3"); + expect (s5.containsWholeWord (String ("word2"))); + expect (s5.indexOfWholeWord ("word2") == 5); + expect (s5.containsWholeWord (String (L"word"))); + expect (s5.containsWholeWord ("word3")); + expect (s5.containsWholeWord (s5)); + expect (s5.containsWholeWordIgnoreCase (String (L"Word2"))); + expect (s5.indexOfWholeWordIgnoreCase ("Word2") == 5); + expect (s5.containsWholeWordIgnoreCase (String (L"Word"))); + expect (s5.containsWholeWordIgnoreCase ("Word3")); + expect (! s5.containsWholeWordIgnoreCase (String (L"Wordx"))); + expect (! s5.containsWholeWordIgnoreCase ("xWord2")); + expect (s5.containsNonWhitespaceChars()); + expect (s5.containsOnly ("ordw23 ")); + expect (! String (" \n\r\t").containsNonWhitespaceChars()); + + expect (s5.matchesWildcard (String (L"wor*"), false)); + expect (s5.matchesWildcard ("wOr*", true)); + expect (s5.matchesWildcard (String (L"*word3"), true)); + expect (s5.matchesWildcard ("*word?", true)); + expect (s5.matchesWildcard (String (L"Word*3"), true)); + expect (! s5.matchesWildcard (String (L"*34"), true)); + expect (String ("xx**y").matchesWildcard ("*y", true)); + expect (String ("xx**y").matchesWildcard ("x*y", true)); + expect (String ("xx**y").matchesWildcard ("xx*y", true)); + expect (String ("xx**y").matchesWildcard ("xx*", true)); + expect (String ("xx?y").matchesWildcard ("x??y", true)); + expect (String ("xx?y").matchesWildcard ("xx?y", true)); + expect (! String ("xx?y").matchesWildcard ("xx?y?", true)); + expect (String ("xx?y").matchesWildcard ("xx??", true)); + + expectEquals (s5.fromFirstOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromFirstOccurrenceOf ("xword2", true, false), s5.substring (100)); + expectEquals (s5.fromFirstOccurrenceOf (String (L"word2"), true, false), s5.substring (5)); + expectEquals (s5.fromFirstOccurrenceOf ("Word2", true, true), s5.substring (5)); + expectEquals (s5.fromFirstOccurrenceOf ("word2", false, false), s5.getLastCharacters (6)); + expectEquals (s5.fromFirstOccurrenceOf ("Word2", false, true), s5.getLastCharacters (6)); + + expectEquals (s5.fromLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.fromLastOccurrenceOf ("wordx", true, false), s5); + expectEquals (s5.fromLastOccurrenceOf ("word", true, false), s5.getLastCharacters (5)); + expectEquals (s5.fromLastOccurrenceOf ("worD", true, true), s5.getLastCharacters (5)); + expectEquals (s5.fromLastOccurrenceOf ("word", false, false), s5.getLastCharacters (1)); + expectEquals (s5.fromLastOccurrenceOf ("worD", false, true), s5.getLastCharacters (1)); + + expect (s5.upToFirstOccurrenceOf (String::empty, true, false).isEmpty()); + expectEquals (s5.upToFirstOccurrenceOf ("word4", true, false), s5); + expectEquals (s5.upToFirstOccurrenceOf ("word2", true, false), s5.substring (0, 10)); + expectEquals (s5.upToFirstOccurrenceOf ("Word2", true, true), s5.substring (0, 10)); + expectEquals (s5.upToFirstOccurrenceOf ("word2", false, false), s5.substring (0, 5)); + expectEquals (s5.upToFirstOccurrenceOf ("Word2", false, true), s5.substring (0, 5)); + + expectEquals (s5.upToLastOccurrenceOf (String::empty, true, false), s5); + expectEquals (s5.upToLastOccurrenceOf ("zword", true, false), s5); + expectEquals (s5.upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); + expectEquals (s5.dropLastCharacters(1).upToLastOccurrenceOf ("word", true, false), s5.dropLastCharacters (1)); + expectEquals (s5.upToLastOccurrenceOf ("Word", true, true), s5.dropLastCharacters (1)); + expectEquals (s5.upToLastOccurrenceOf ("word", false, false), s5.dropLastCharacters (5)); + expectEquals (s5.upToLastOccurrenceOf ("Word", false, true), s5.dropLastCharacters (5)); + + expectEquals (s5.replace ("word", "xyz", false), String ("xyz xyz2 xyz3")); + expect (s5.replace ("Word", "xyz", true) == "xyz xyz2 xyz3"); + expect (s5.dropLastCharacters (1).replace ("Word", String ("xyz"), true) == L"xyz xyz2 xyz"); + expect (s5.replace ("Word", "", true) == " 2 3"); + expectEquals (s5.replace ("Word2", "xyz", true), String ("word xyz word3")); + expect (s5.replaceCharacter (L'w', 'x') != s5); + expectEquals (s5.replaceCharacter ('w', L'x').replaceCharacter ('x', 'w'), s5); + expect (s5.replaceCharacters ("wo", "xy") != s5); + expectEquals (s5.replaceCharacters ("wo", "xy").replaceCharacters ("xy", "wo"), s5); + expectEquals (s5.retainCharacters ("1wordxya"), String ("wordwordword")); + expect (s5.retainCharacters (String::empty).isEmpty()); + expect (s5.removeCharacters ("1wordxya") == " 2 3"); + expectEquals (s5.removeCharacters (String::empty), s5); + expect (s5.initialSectionContainingOnly ("word") == L"word"); + expect (String ("word").initialSectionContainingOnly ("word") == L"word"); + expectEquals (s5.initialSectionNotContaining (String ("xyz ")), String ("word")); + expectEquals (s5.initialSectionNotContaining (String (";[:'/")), s5); + expect (! s5.isQuotedString()); + expect (s5.quoted().isQuotedString()); + expect (! s5.quoted().unquoted().isQuotedString()); + expect (! String ("x'").isQuotedString()); + expect (String ("'x").isQuotedString()); + + String s6 (" \t xyz \t\r\n"); + expectEquals (s6.trim(), String ("xyz")); + expect (s6.trim().trim() == "xyz"); + expectEquals (s5.trim(), s5); + expectEquals (s6.trimStart().trimEnd(), s6.trim()); + expectEquals (s6.trimStart().trimEnd(), s6.trimEnd().trimStart()); + expectEquals (s6.trimStart().trimStart().trimEnd().trimEnd(), s6.trimEnd().trimStart()); + expect (s6.trimStart() != s6.trimEnd()); + expectEquals (("\t\r\n " + s6 + "\t\n \r").trim(), s6.trim()); + expect (String::repeatedString ("xyz", 3) == L"xyzxyzxyz"); + } + + { + beginTest ("UTF conversions"); + + TestUTFConversion ::test (*this, r); + TestUTFConversion ::test (*this, r); + TestUTFConversion ::test (*this, r); + } + + { + beginTest ("StringArray"); + + StringArray s; + s.addTokens ("4,3,2,1,0", ";,", "x"); + expectEquals (s.size(), 5); + + expectEquals (s.joinIntoString ("-"), String ("4-3-2-1-0")); + s.remove (2); + expectEquals (s.joinIntoString ("--"), String ("4--3--1--0")); + expectEquals (s.joinIntoString (String::empty), String ("4310")); + s.clear(); + expectEquals (s.joinIntoString ("x"), String::empty); + + StringArray toks; + toks.addTokens ("x,,", ";,", ""); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("x--")); + toks.clear(); + + toks.addTokens (",x,", ";,", ""); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("-x-")); + toks.clear(); + + toks.addTokens ("x,'y,z',", ";,", "'"); + expectEquals (toks.size(), 3); + expectEquals (toks.joinIntoString ("-"), String ("x-'y,z'-")); + } + + { + beginTest ("var"); + + var v1 = 0; + var v2 = 0.1; + var v3 = "0.1"; + var v4 = (int64) 0; + var v5 = 0.0; + expect (! v2.equals (v1)); + expect (! v1.equals (v2)); + expect (v2.equals (v3)); + expect (v3.equals (v2)); + expect (! v3.equals (v1)); + expect (! v1.equals (v3)); + expect (v1.equals (v4)); + expect (v4.equals (v1)); + expect (v5.equals (v4)); + expect (v4.equals (v5)); + expect (! v2.equals (v4)); + expect (! v4.equals (v2)); + } + } +}; + +static StringTests stringUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.h b/JuceLibraryCode/modules/juce_core/text/juce_String.h new file mode 100644 index 0000000..6a51107 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.h @@ -0,0 +1,1370 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRING_H_INCLUDED +#define JUCE_STRING_H_INCLUDED + + +//============================================================================== +/** + The JUCE String class! + + Using a reference-counted internal representation, these strings are fast + and efficient, and there are methods to do just about any operation you'll ever + dream of. + + @see StringArray, StringPairArray +*/ +class JUCE_API String +{ +public: + //============================================================================== + /** Creates an empty string. + @see empty + */ + String() noexcept; + + /** Creates a copy of another string. */ + String (const String& other) noexcept; + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + String (String&& other) noexcept; + #endif + + /** Creates a string from a zero-terminated ascii text string. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + */ + String (const char* text); + + /** Creates a string from a string of 8-bit ascii characters. + + The string passed-in must not contain any characters with a value above 127, because + these can't be converted to unicode without knowing the original encoding that was + used to create the string. If you attempt to pass-in values above 127, you'll get an + assertion. + + To create strings with extended characters from UTF-8, you should explicitly call + String (CharPointer_UTF8 ("my utf8 string..")). It's *highly* recommended that you + use UTF-8 with escape characters in your source code to represent extended characters, + because there's no other way to represent unicode strings in a way that isn't dependent + on the compiler, source code editor and platform. + + This will use up to the first maxChars characters of the string (or less if the string + is actually shorter). + */ + String (const char* text, size_t maxChars); + + /** Creates a string from a whcar_t character string. + Depending on the platform, this may be treated as either UTF-32 or UTF-16. + */ + String (const wchar_t* text); + + /** Creates a string from a whcar_t character string. + Depending on the platform, this may be treated as either UTF-32 or UTF-16. + */ + String (const wchar_t* text, size_t maxChars); + + //============================================================================== + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 text, size_t maxChars); + + /** Creates a string from a UTF-8 character string */ + String (const CharPointer_UTF8 start, const CharPointer_UTF8 end); + + //============================================================================== + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 text); + + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 text, size_t maxChars); + + /** Creates a string from a UTF-16 character string */ + String (const CharPointer_UTF16 start, const CharPointer_UTF16 end); + + //============================================================================== + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 text); + + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 text, size_t maxChars); + + /** Creates a string from a UTF-32 character string */ + String (const CharPointer_UTF32 start, const CharPointer_UTF32 end); + + //============================================================================== + /** Creates a string from an ASCII character string */ + String (const CharPointer_ASCII text); + + /** Creates a string from a UTF-8 encoded std::string. */ + String (const std::string&); + + //============================================================================== + /** Creates a string from a single character. */ + static String charToString (juce_wchar character); + + /** Destructor. */ + ~String() noexcept; + + //============================================================================== + /** This is an empty string that can be used whenever one is needed. + + It's better to use this than String() because it explains what's going on + and is more efficient. + */ + static const String empty; + + /** This is the character encoding type used internally to store the string. + + By setting the value of JUCE_STRING_UTF_TYPE to 8, 16, or 32, you can change the + internal storage format of the String class. UTF-8 uses the least space (if your strings + contain few extended characters), but call operator[] involves iterating the string to find + the required index. UTF-32 provides instant random access to its characters, but uses 4 bytes + per character to store them. UTF-16 uses more space than UTF-8 and is also slow to index, + but is the native wchar_t format used in Windows. + + It doesn't matter too much which format you pick, because the toUTF8(), toUTF16() and + toUTF32() methods let you access the string's content in any of the other formats. + */ + #if (JUCE_STRING_UTF_TYPE == 32) + typedef CharPointer_UTF32 CharPointerType; + #elif (JUCE_STRING_UTF_TYPE == 16) + typedef CharPointer_UTF16 CharPointerType; + #elif (DOXYGEN || JUCE_STRING_UTF_TYPE == 8) + typedef CharPointer_UTF8 CharPointerType; + #else + #error "You must set the value of JUCE_STRING_UTF_TYPE to be either 8, 16, or 32!" + #endif + + //============================================================================== + /** Generates a probably-unique 32-bit hashcode from this string. */ + int hashCode() const noexcept; + + /** Generates a probably-unique 64-bit hashcode from this string. */ + int64 hashCode64() const noexcept; + + /** Generates a probably-unique hashcode from this string. */ + std::size_t hash() const noexcept; + + /** Returns the number of characters in the string. */ + int length() const noexcept; + + //============================================================================== + // Assignment and concatenation operators.. + + /** Replaces this string's contents with another string. */ + String& operator= (const String& other) noexcept; + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + String& operator= (String&& other) noexcept; + #endif + + /** Appends another string at the end of this one. */ + String& operator+= (const String& stringToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (const char* textToAppend); + /** Appends another string at the end of this one. */ + String& operator+= (const wchar_t* textToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (int numberToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (int64 numberToAppend); + /** Appends a character at the end of this string. */ + String& operator+= (char characterToAppend); + /** Appends a character at the end of this string. */ + String& operator+= (wchar_t characterToAppend); + #if ! JUCE_NATIVE_WCHAR_IS_UTF32 + /** Appends a character at the end of this string. */ + String& operator+= (juce_wchar characterToAppend); + #endif + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + void append (const String& textToAppend, size_t maxCharsToTake); + + /** Appends a string to the end of this one. + + @param startOfTextToAppend the start of the string to add. This must not be a nullptr + @param endOfTextToAppend the end of the string to add. This must not be a nullptr + */ + void appendCharPointer (const CharPointerType startOfTextToAppend, + const CharPointerType endOfTextToAppend); + + /** Appends a string to the end of this one. + + @param startOfTextToAppend the start of the string to add. This must not be a nullptr + @param endOfTextToAppend the end of the string to add. This must not be a nullptr + */ + template + void appendCharPointer (const CharPointer startOfTextToAppend, + const CharPointer endOfTextToAppend) + { + jassert (startOfTextToAppend.getAddress() != nullptr && endOfTextToAppend.getAddress() != nullptr); + + size_t extraBytesNeeded = 0, numChars = 1; + + for (CharPointer t (startOfTextToAppend); t != endOfTextToAppend && ! t.isEmpty(); ++numChars) + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)) + .writeWithCharLimit (startOfTextToAppend, (int) numChars); + } + } + + /** Appends a string to the end of this one. */ + void appendCharPointer (const CharPointerType textToAppend); + + /** Appends a string to the end of this one. + + @param textToAppend the string to add + @param maxCharsToTake the maximum number of characters to take from the string passed in + */ + template + void appendCharPointer (const CharPointer textToAppend, size_t maxCharsToTake) + { + if (textToAppend.getAddress() != nullptr) + { + size_t extraBytesNeeded = 0, numChars = 1; + + for (CharPointer t (textToAppend); numChars <= maxCharsToTake && ! t.isEmpty(); ++numChars) + extraBytesNeeded += CharPointerType::getBytesRequiredFor (t.getAndAdvance()); + + if (extraBytesNeeded > 0) + { + const size_t byteOffsetOfNull = getByteOffsetOfEnd(); + + preallocateBytes (byteOffsetOfNull + extraBytesNeeded); + CharPointerType (addBytesToPointer (text.getAddress(), (int) byteOffsetOfNull)) + .writeWithCharLimit (textToAppend, (int) numChars); + } + } + } + + /** Appends a string to the end of this one. */ + template + void appendCharPointer (const CharPointer textToAppend) + { + appendCharPointer (textToAppend, std::numeric_limits::max()); + } + + //============================================================================== + // Comparison methods.. + + /** Returns true if the string contains no characters. + Note that there's also an isNotEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isEmpty() const noexcept { return text[0] == 0; } + + /** Returns true if the string contains at least one character. + Note that there's also an isEmpty() method to help write readable code. + @see containsNonWhitespaceChars() + */ + inline bool isNotEmpty() const noexcept { return text[0] != 0; } + + /** Resets this string to be empty. */ + void clear() noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const String& other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (StringRef other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const wchar_t* other) const noexcept; + + /** Case-insensitive comparison with another string. */ + bool equalsIgnoreCase (const char* other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const String& other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const char* other) const noexcept; + + /** Case-sensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compare (const wchar_t* other) const noexcept; + + /** Case-insensitive comparison with another string. + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareIgnoreCase (const String& other) const noexcept; + + /** Compares two strings, taking into account textual characteristics like numbers and spaces. + + This comparison is case-insensitive and can detect words and embedded numbers in the + strings, making it good for sorting human-readable lists of things like filenames. + + @returns 0 if the two strings are identical; negative if this string comes before + the other one alphabetically, or positive if it comes after it. + */ + int compareNatural (StringRef other) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool startsWith (StringRef text) const noexcept; + + /** Tests whether the string begins with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool startsWithChar (juce_wchar character) const noexcept; + + /** Tests whether the string begins with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool startsWithIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool endsWith (StringRef text) const noexcept; + + /** Tests whether the string ends with a particular character. + If the character is 0, this will always return false. + Uses a case-sensitive comparison. + */ + bool endsWithChar (juce_wchar character) const noexcept; + + /** Tests whether the string ends with another string. + If the parameter is an empty string, this will always return true. + Uses a case-insensitive comparison. + */ + bool endsWithIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string contains another substring. + If the parameter is an empty string, this will always return true. + Uses a case-sensitive comparison. + */ + bool contains (StringRef text) const noexcept; + + /** Tests whether the string contains a particular character. + Uses a case-sensitive comparison. + */ + bool containsChar (juce_wchar character) const noexcept; + + /** Tests whether the string contains another substring. + Uses a case-insensitive comparison. + */ + bool containsIgnoreCase (StringRef text) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + bool containsWholeWord (StringRef wordToLookFor) const noexcept; + + /** Tests whether the string contains another substring as a distinct word. + + @returns true if the string contains this word, surrounded by + non-alphanumeric characters + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + bool containsWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWordIgnoreCase, containsWholeWord + */ + int indexOfWholeWord (StringRef wordToLookFor) const noexcept; + + /** Finds an instance of another substring if it exists as a distinct word. + + @returns if the string contains this word, surrounded by non-alphanumeric characters, + then this will return the index of the start of the substring. If it isn't + found, then it will return -1 + @see indexOfWholeWord, containsWholeWordIgnoreCase + */ + int indexOfWholeWordIgnoreCase (StringRef wordToLookFor) const noexcept; + + /** Looks for any of a set of characters in the string. + Uses a case-sensitive comparison. + + @returns true if the string contains any of the characters from + the string that is passed in. + */ + bool containsAnyOf (StringRef charactersItMightContain) const noexcept; + + /** Looks for a set of characters in the string. + Uses a case-sensitive comparison. + + @returns Returns false if any of the characters in this string do not occur in + the parameter string. If this string is empty, the return value will + always be true. + */ + bool containsOnly (StringRef charactersItMightContain) const noexcept; + + /** Returns true if this string contains any non-whitespace characters. + + This will return false if the string contains only whitespace characters, or + if it's empty. + + It is equivalent to calling "myString.trim().isNotEmpty()". + */ + bool containsNonWhitespaceChars() const noexcept; + + /** Returns true if the string matches this simple wildcard expression. + + So for example String ("abcdef").matchesWildcard ("*DEF", true) would return true. + + This isn't a full-blown regex though! The only wildcard characters supported + are "*" and "?". It's mainly intended for filename pattern matching. + */ + bool matchesWildcard (StringRef wildcard, bool ignoreCase) const noexcept; + + //============================================================================== + // Substring location methods.. + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (juce_wchar characterToLookFor) const noexcept; + + /** Searches for a character inside this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param characterToLookFor the character to look for + @returns the index of the first occurrence of the character in this + string, or -1 if it's not found. + */ + int indexOfChar (int startIndex, juce_wchar characterToLookFor) const noexcept; + + /** Returns the index of the first character that matches one of the characters + passed-in to this method. + + This scans the string, beginning from the startIndex supplied, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see indexOfChar, lastIndexOfAnyOf + */ + int indexOfAnyOf (StringRef charactersToLookFor, + int startIndex = 0, + bool ignoreCase = false) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOf (StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-sensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOf (int startIndex, StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return 0. + */ + int indexOfIgnoreCase (StringRef textToLookFor) const noexcept; + + /** Searches for a substring within this string. + Uses a case-insensitive comparison. + @param startIndex the index from which the search should proceed + @param textToLookFor the string to search for + @returns the index of the first occurrence of this substring, or -1 if it's not found. + If textToLookFor is an empty string, this will always return -1. + */ + int indexOfIgnoreCase (int startIndex, StringRef textToLookFor) const noexcept; + + /** Searches for a character inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the last occurrence of the character in this string, or -1 if it's not found. + */ + int lastIndexOfChar (juce_wchar character) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-sensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, + or -1 if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOf (StringRef textToLookFor) const noexcept; + + /** Searches for a substring inside this string (working backwards from the end of the string). + Uses a case-insensitive comparison. + @returns the index of the start of the last occurrence of the substring within this string, or -1 + if it's not found. If textToLookFor is an empty string, this will always return -1. + */ + int lastIndexOfIgnoreCase (StringRef textToLookFor) const noexcept; + + /** Returns the index of the last character in this string that matches one of the + characters passed-in to this method. + + This scans the string backwards, starting from its end, and if it finds + a character that appears in the string charactersToLookFor, it returns its index. + + If none of these characters are found, it returns -1. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see lastIndexOf, indexOfAnyOf + */ + int lastIndexOfAnyOf (StringRef charactersToLookFor, + bool ignoreCase = false) const noexcept; + + + //============================================================================== + // Substring extraction and manipulation methods.. + + /** Returns the character at this index in the string. + In a release build, no checks are made to see if the index is within a valid range, so be + careful! In a debug build, the index is checked and an assertion fires if it's out-of-range. + + Also beware that depending on the encoding format that the string is using internally, this + method may execute in either O(1) or O(n) time, so be careful when using it in your algorithms. + If you're scanning through a string to inspect its characters, you should never use this operator + for random access, it's far more efficient to call getCharPointer() to return a pointer, and + then to use that to iterate the string. + @see getCharPointer + */ + juce_wchar operator[] (int index) const noexcept; + + /** Returns the final character of the string. + If the string is empty this will return 0. + */ + juce_wchar getLastCharacter() const noexcept; + + //============================================================================== + /** Returns a subsection of the string. + + If the range specified is beyond the limits of the string, as much as + possible is returned. + + @param startIndex the index of the start of the substring needed + @param endIndex all characters from startIndex up to (but not including) + this index are returned + @see fromFirstOccurrenceOf, dropLastCharacters, getLastCharacters, upToFirstOccurrenceOf + */ + String substring (int startIndex, int endIndex) const; + + /** Returns a section of the string, starting from a given position. + + @param startIndex the first character to include. If this is beyond the end + of the string, an empty string is returned. If it is zero or + less, the whole string is returned. + @returns the substring from startIndex up to the end of the string + @see dropLastCharacters, getLastCharacters, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String substring (int startIndex) const; + + /** Returns a version of this string with a number of characters removed + from the end. + + @param numberToDrop the number of characters to drop from the end of the + string. If this is greater than the length of the string, + an empty string will be returned. If zero or less, the + original string will be returned. + @see substring, fromFirstOccurrenceOf, upToFirstOccurrenceOf, fromLastOccurrenceOf, getLastCharacter + */ + String dropLastCharacters (int numberToDrop) const; + + /** Returns a number of characters from the end of the string. + + This returns the last numCharacters characters from the end of the string. If the + string is shorter than numCharacters, the whole string is returned. + + @see substring, dropLastCharacters, getLastCharacter + */ + String getLastCharacters (int numCharacters) const; + + //============================================================================== + /** Returns a section of the string starting from a given substring. + + This will search for the first occurrence of the given substring, and + return the section of the string starting from the point where this is + found (optionally not including the substring itself). + + e.g. for the string "123456", fromFirstOccurrenceOf ("34", true) would return "3456", and + fromFirstOccurrenceOf ("34", false) would return "56". + + If the substring isn't found, the method will return an empty string. + + If ignoreCase is true, the comparison will be case-insensitive. + + @see upToFirstOccurrenceOf, fromLastOccurrenceOf + */ + String fromFirstOccurrenceOf (StringRef substringToStartFrom, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns a section of the string starting from the last occurrence of a given substring. + + Similar to fromFirstOccurrenceOf(), but using the last occurrence of the substring, and + unlike fromFirstOccurrenceOf(), if the substring isn't found, this method will + return the whole of the original string. + + @see fromFirstOccurrenceOf, upToLastOccurrenceOf + */ + String fromLastOccurrenceOf (StringRef substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the first occurrence of a substring. + + This will search for the first occurrence of a given substring, and then + return a copy of the string, up to the position of this substring, + optionally including or excluding the substring itself in the result. + + e.g. for the string "123456", upTo ("34", false) would return "12", and + upTo ("34", true) would return "1234". + + If the substring isn't found, this will return the whole of the original string. + + @see upToLastOccurrenceOf, fromFirstOccurrenceOf + */ + String upToFirstOccurrenceOf (StringRef substringToEndWith, + bool includeSubStringInResult, + bool ignoreCase) const; + + /** Returns the start of this string, up to the last occurrence of a substring. + + Similar to upToFirstOccurrenceOf(), but this finds the last occurrence rather than the first. + If the substring isn't found, this will return the whole of the original string. + + @see upToFirstOccurrenceOf, fromFirstOccurrenceOf + */ + String upToLastOccurrenceOf (StringRef substringToFind, + bool includeSubStringInResult, + bool ignoreCase) const; + + //============================================================================== + /** Returns a copy of this string with any whitespace characters removed from the start and end. */ + String trim() const; + + /** Returns a copy of this string with any whitespace characters removed from the start. */ + String trimStart() const; + + /** Returns a copy of this string with any whitespace characters removed from the end. */ + String trimEnd() const; + + /** Returns a copy of this string, having removed a specified set of characters from its start. + Characters are removed from the start of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimStart, trimCharactersAtEnd + */ + String trimCharactersAtStart (StringRef charactersToTrim) const; + + /** Returns a copy of this string, having removed a specified set of characters from its end. + Characters are removed from the end of the string until it finds one that is not in the + specified set, and then it stops. + @param charactersToTrim the set of characters to remove. + @see trim, trimEnd, trimCharactersAtStart + */ + String trimCharactersAtEnd (StringRef charactersToTrim) const; + + //============================================================================== + /** Returns an upper-case version of this string. */ + String toUpperCase() const; + + /** Returns an lower-case version of this string. */ + String toLowerCase() const; + + //============================================================================== + /** Replaces a sub-section of the string with another string. + + This will return a copy of this string, with a set of characters + from startIndex to startIndex + numCharsToReplace removed, and with + a new string inserted in their place. + + Note that this is a const method, and won't alter the string itself. + + @param startIndex the first character to remove. If this is beyond the bounds of the string, + it will be constrained to a valid range. + @param numCharactersToReplace the number of characters to remove. If zero or less, no + characters will be taken out. + @param stringToInsert the new string to insert at startIndex after the characters have been + removed. + */ + String replaceSection (int startIndex, + int numCharactersToReplace, + StringRef stringToInsert) const; + + /** Replaces all occurrences of a substring with another string. + + Returns a copy of this string, with any occurrences of stringToReplace + swapped for stringToInsertInstead. + + Note that this is a const method, and won't alter the string itself. + */ + String replace (StringRef stringToReplace, + StringRef stringToInsertInstead, + bool ignoreCase = false) const; + + /** Returns a string with all occurrences of a character replaced with a different one. */ + String replaceCharacter (juce_wchar characterToReplace, + juce_wchar characterToInsertInstead) const; + + /** Replaces a set of characters with another set. + + Returns a string in which each character from charactersToReplace has been replaced + by the character at the equivalent position in newCharacters (so the two strings + passed in must be the same length). + + e.g. replaceCharacters ("abc", "def") replaces 'a' with 'd', 'b' with 'e', etc. + + Note that this is a const method, and won't affect the string itself. + */ + String replaceCharacters (StringRef charactersToReplace, + StringRef charactersToInsertInstead) const; + + /** Returns a version of this string that only retains a fixed set of characters. + + This will return a copy of this string, omitting any characters which are not + found in the string passed-in. + + e.g. for "1122334455", retainCharacters ("432") would return "223344" + + Note that this is a const method, and won't alter the string itself. + */ + String retainCharacters (StringRef charactersToRetain) const; + + /** Returns a version of this string with a set of characters removed. + + This will return a copy of this string, omitting any characters which are + found in the string passed-in. + + e.g. for "1122334455", removeCharacters ("432") would return "1155" + + Note that this is a const method, and won't alter the string itself. + */ + String removeCharacters (StringRef charactersToRemove) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that doesn't appear in the string passed in. + */ + String initialSectionContainingOnly (StringRef permittedCharacters) const; + + /** Returns a section from the start of the string that only contains a certain set of characters. + + This returns the leftmost section of the string, up to (and not including) the + first character that occurs in the string passed in. (If none of the specified + characters are found in the string, the return value will just be the original string). + */ + String initialSectionNotContaining (StringRef charactersToStopAt) const; + + //============================================================================== + /** Checks whether the string might be in quotation marks. + + @returns true if the string begins with a quote character (either a double or single quote). + It is also true if there is whitespace before the quote, but it doesn't check the end of the string. + @see unquoted, quoted + */ + bool isQuotedString() const; + + /** Removes quotation marks from around the string, (if there are any). + + Returns a copy of this string with any quotes removed from its ends. Quotes that aren't + at the ends of the string are not affected. If there aren't any quotes, the original string + is returned. + + Note that this is a const method, and won't alter the string itself. + + @see isQuotedString, quoted + */ + String unquoted() const; + + /** Adds quotation marks around a string. + + This will return a copy of the string with a quote at the start and end, (but won't + add the quote if there's already one there, so it's safe to call this on strings that + may already have quotes around them). + + Note that this is a const method, and won't alter the string itself. + + @param quoteCharacter the character to add at the start and end + @see isQuotedString, unquoted + */ + String quoted (juce_wchar quoteCharacter = '"') const; + + + //============================================================================== + /** Creates a string which is a version of a string repeated and joined together. + + @param stringToRepeat the string to repeat + @param numberOfTimesToRepeat how many times to repeat it + */ + static String repeatedString (StringRef stringToRepeat, + int numberOfTimesToRepeat); + + /** Returns a copy of this string with the specified character repeatedly added to its + beginning until the total length is at least the minimum length specified. + */ + String paddedLeft (juce_wchar padCharacter, int minimumLength) const; + + /** Returns a copy of this string with the specified character repeatedly added to its + end until the total length is at least the minimum length specified. + */ + String paddedRight (juce_wchar padCharacter, int minimumLength) const; + + /** Creates a string from data in an unknown format. + + This looks at some binary data and tries to guess whether it's Unicode + or 8-bit characters, then returns a string that represents it correctly. + + Should be able to handle Unicode endianness correctly, by looking at + the first two bytes. + */ + static String createStringFromData (const void* data, int size); + + /** Creates a String from a printf-style parameter list. + + I don't like this method. I don't use it myself, and I recommend avoiding it and + using the operator<< methods or pretty much anything else instead. It's only provided + here because of the popular unrest that was stirred-up when I tried to remove it... + + If you're really determined to use it, at least make sure that you never, ever, + pass any String objects to it as parameters. And bear in mind that internally, depending + on the platform, it may be using wchar_t or char character types, so that even string + literals can't be safely used as parameters if you're writing portable code. + */ + static String formatted (const String formatString, ... ); + + //============================================================================== + // Numeric conversions.. + + /** Creates a string containing this signed 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int decimalInteger); + + /** Creates a string containing this unsigned 32-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned int decimalInteger); + + /** Creates a string containing this signed 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (short decimalInteger); + + /** Creates a string containing this unsigned 16-bit integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned short decimalInteger); + + /** Creates a string containing this signed 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (int64 largeIntegerValue); + + /** Creates a string containing this unsigned 64-bit integer as a decimal number. + @see getLargeIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (uint64 largeIntegerValue); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @see getDoubleValue, getIntValue + */ + explicit String (float floatValue); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @see getFloatValue, getIntValue + */ + explicit String (double doubleValue); + + /** Creates a string representing this floating-point number. + @param floatValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getDoubleValue, getIntValue + */ + String (float floatValue, int numberOfDecimalPlaces); + + /** Creates a string representing this floating-point number. + @param doubleValue the value to convert to a string + @param numberOfDecimalPlaces if this is > 0, it will format the number using that many + decimal places, and will not use exponent notation. If 0 or + less, it will use exponent notation if necessary. + @see getFloatValue, getIntValue + */ + String (double doubleValue, int numberOfDecimalPlaces); + + /** Reads the value of the string as a decimal number (up to 32 bits in size). + + @returns the value of the string as a 32 bit signed base-10 integer. + @see getTrailingIntValue, getHexValue32, getHexValue64 + */ + int getIntValue() const noexcept; + + /** Reads the value of the string as a decimal number (up to 64 bits in size). + @returns the value of the string as a 64 bit signed base-10 integer. + */ + int64 getLargeIntValue() const noexcept; + + /** Parses a decimal number from the end of the string. + + This will look for a value at the end of the string. + e.g. for "321 xyz654" it will return 654; for "2 3 4" it'll return 4. + + Negative numbers are not handled, so "xyz-5" returns 5. + + @see getIntValue + */ + int getTrailingIntValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 32-bit floating point value. + @see getDoubleValue + */ + float getFloatValue() const noexcept; + + /** Parses this string as a floating point number. + + @returns the value of the string as a 64-bit floating point value. + @see getFloatValue + */ + double getDoubleValue() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff12345678" would produce 0x12345678. + + @returns a 32-bit number which is the value of the string in hex. + */ + int getHexValue32() const noexcept; + + /** Parses the string as a hexadecimal number. + + Non-hexadecimal characters in the string are ignored. + + If the string contains too many characters, then the lowest significant + digits are returned, e.g. "ffff1234567812345678" would produce 0x1234567812345678. + + @returns a 64-bit number which is the value of the string in hex. + */ + int64 getHexValue64() const noexcept; + + /** Creates a string representing this 32-bit value in hexadecimal. */ + static String toHexString (int number); + + /** Creates a string representing this 64-bit value in hexadecimal. */ + static String toHexString (int64 number); + + /** Creates a string representing this 16-bit value in hexadecimal. */ + static String toHexString (short number); + + /** Creates a string containing a hex dump of a block of binary data. + + @param data the binary data to use as input + @param size how many bytes of data to use + @param groupSize how many bytes are grouped together before inserting a + space into the output. e.g. group size 0 has no spaces, + group size 1 looks like: "be a1 c2 ff", group size 2 looks + like "bea1 c2ff". + */ + static String toHexString (const void* data, int size, int groupSize = 1); + + //============================================================================== + /** Returns the character pointer currently being used to store this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + */ + inline CharPointerType getCharPointer() const noexcept { return text; } + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see toRawUTF8, getCharPointer, toUTF16, toUTF32 + */ + CharPointer_UTF8 toUTF8() const; + + /** Returns a pointer to a UTF-8 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @see getCharPointer, toUTF8, toUTF16, toUTF32 + */ + const char* toRawUTF8() const; + + /** Returns a pointer to a UTF-16 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + To find out how many bytes you need to store this string as UTF-16, you can call + CharPointer_UTF16::getBytesRequiredFor (myString.getCharPointer()) + + @see getCharPointer, toUTF8, toUTF32 + */ + CharPointer_UTF16 toUTF16() const; + + /** Returns a pointer to a UTF-32 version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + @see getCharPointer, toUTF8, toUTF16 + */ + CharPointer_UTF32 toUTF32() const; + + /** Returns a pointer to a wchar_t version of this string. + + Because it returns a reference to the string's internal data, the pointer + that is returned must not be stored anywhere, as it can be deleted whenever the + string changes. + + Bear in mind that the wchar_t type is different on different platforms, so on + Windows, this will be equivalent to calling toUTF16(), on unix it'll be the same + as calling toUTF32(), etc. + + @see getCharPointer, toUTF8, toUTF16, toUTF32 + */ + const wchar_t* toWideCharPointer() const; + + /** */ + std::string toStdString() const; + + //============================================================================== + /** Creates a String from a UTF-8 encoded buffer. + If the size is < 0, it'll keep reading until it hits a zero. + */ + static String fromUTF8 (const char* utf8buffer, int bufferSizeBytes = -1); + + /** Returns the number of bytes required to represent this string as UTF8. + The number returned does NOT include the trailing zero. + @see toUTF8, copyToUTF8 + */ + size_t getNumBytesAsUTF8() const noexcept; + + //============================================================================== + /** Copies the string to a buffer as UTF-8 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-8, you can call + CharPointer_UTF8::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF8::writeWithDestByteLimit + */ + size_t copyToUTF8 (CharPointer_UTF8::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + /** Copies the string to a buffer as UTF-16 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-16, you can call + CharPointer_UTF16::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF16::writeWithDestByteLimit + */ + size_t copyToUTF16 (CharPointer_UTF16::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + /** Copies the string to a buffer as UTF-32 characters. + + Returns the number of bytes copied to the buffer, including the terminating null + character. + + To find out how many bytes you need to store this string as UTF-32, you can call + CharPointer_UTF32::getBytesRequiredFor (myString.getCharPointer()) + + @param destBuffer the place to copy it to; if this is a null pointer, the method just + returns the number of bytes required (including the terminating null character). + @param maxBufferSizeBytes the size of the destination buffer, in bytes. If the string won't fit, it'll + put in as many as it can while still allowing for a terminating null char at the + end, and will return the number of bytes that were actually used. + @see CharPointer_UTF32::writeWithDestByteLimit + */ + size_t copyToUTF32 (CharPointer_UTF32::CharType* destBuffer, size_t maxBufferSizeBytes) const noexcept; + + //============================================================================== + /** Increases the string's internally allocated storage. + + Although the string's contents won't be affected by this call, it will + increase the amount of memory allocated internally for the string to grow into. + + If you're about to make a large number of calls to methods such + as += or <<, it's more efficient to preallocate enough extra space + beforehand, so that these methods won't have to keep resizing the string + to append the extra characters. + + @param numBytesNeeded the number of bytes to allocate storage for. If this + value is less than the currently allocated size, it will + have no effect. + */ + void preallocateBytes (size_t numBytesNeeded); + + /** Swaps the contents of this string with another one. + This is a very fast operation, as no allocation or copying needs to be done. + */ + void swapWith (String& other) noexcept; + + //============================================================================== + #if JUCE_MAC || JUCE_IOS || DOXYGEN + /** OSX ONLY - Creates a String from an OSX CFString. */ + static String fromCFString (CFStringRef cfString); + + /** OSX ONLY - Converts this string to a CFString. + Remember that you must use CFRelease() to free the returned string when you're + finished with it. + */ + CFStringRef toCFString() const; + + /** OSX ONLY - Returns a copy of this string in which any decomposed unicode characters have + been converted to their precomposed equivalents. */ + String convertToPrecomposedUnicode() const; + #endif + + /** Returns the number of String objects which are currently sharing the same internal + data as this one. + */ + int getReferenceCount() const noexcept; + +private: + //============================================================================== + CharPointerType text; + + //============================================================================== + struct PreallocationBytes + { + explicit PreallocationBytes (size_t) noexcept; + size_t numBytes; + }; + + explicit String (const PreallocationBytes&); // This constructor preallocates a certain amount of memory + size_t getByteOffsetOfEnd() const noexcept; + JUCE_DEPRECATED (String (const String&, size_t)); + + // This private cast operator should prevent strings being accidentally cast + // to bools (this is possible because the compiler can add an implicit cast + // via a const char*) + operator bool() const noexcept { return false; } +}; + +//============================================================================== +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (const char* string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (char string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (wchar_t string1, const String& string2); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (juce_wchar string1, const String& string2); +#endif + +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const String& string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const char* string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, const wchar_t* string2); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, char characterToAppend); +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, wchar_t characterToAppend); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Concatenates two strings. */ +JUCE_API String JUCE_CALLTYPE operator+ (String string1, juce_wchar characterToAppend); +#endif + +//============================================================================== +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, char characterToAppend); +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, wchar_t characterToAppend); +#if ! JUCE_NATIVE_WCHAR_IS_UTF32 +/** Appends a character at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, juce_wchar characterToAppend); +#endif + +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const char* string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const wchar_t* string2); +/** Appends a string to the end of the first one. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string2); + +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, long number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int64 number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, uint64 number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, float number); +/** Appends a decimal number at the end of a string. */ +JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, double number); + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const CharPointer_UTF32 string2) noexcept; + +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const char* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const wchar_t* string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF8 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF16 string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, const CharPointer_UTF32 string2) noexcept; + +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator> (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator< (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator>= (const String& string1, const String& string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator<= (const String& string1, const String& string2) noexcept; + +//============================================================================== +/** This operator allows you to write a juce String directly to std output streams. + This is handy for writing strings to std::cout, std::cerr, etc. +*/ +template +std::basic_ostream & JUCE_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toRawUTF8(); +} + +/** This operator allows you to write a juce String directly to std output streams. + This is handy for writing strings to std::wcout, std::wcerr, etc. +*/ +template +std::basic_ostream & JUCE_CALLTYPE operator<< (std::basic_ostream & stream, const String& stringToWrite) +{ + return stream << stringToWrite.toWideCharPointer(); +} + +/** Writes a string to an OutputStream as UTF8. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& stringToWrite); + +/** Writes a string to an OutputStream as UTF8. */ +JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, StringRef stringToWrite); + + +#endif // JUCE_STRING_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp new file mode 100644 index 0000000..6c49138 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp @@ -0,0 +1,485 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +StringArray::StringArray() noexcept +{ +} + +StringArray::StringArray (const StringArray& other) + : strings (other.strings) +{ +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +StringArray::StringArray (StringArray&& other) noexcept + : strings (static_cast &&> (other.strings)) +{ +} +#endif + +StringArray::StringArray (const String& firstValue) +{ + strings.add (firstValue); +} + +StringArray::StringArray (const String* initialStrings, int numberOfStrings) +{ + strings.addArray (initialStrings, numberOfStrings); +} + +StringArray::StringArray (const char* const* initialStrings) +{ + strings.addNullTerminatedArray (initialStrings); +} + +StringArray::StringArray (const char* const* initialStrings, int numberOfStrings) +{ + strings.addArray (initialStrings, numberOfStrings); +} + +StringArray::StringArray (const wchar_t* const* initialStrings) +{ + strings.addNullTerminatedArray (initialStrings); +} + +StringArray::StringArray (const wchar_t* const* initialStrings, int numberOfStrings) +{ + strings.addArray (initialStrings, numberOfStrings); +} + +StringArray& StringArray::operator= (const StringArray& other) +{ + strings = other.strings; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +StringArray& StringArray::operator= (StringArray&& other) noexcept +{ + strings = static_cast &&> (other.strings); + return *this; +} +#endif + +StringArray::~StringArray() +{ +} + +bool StringArray::operator== (const StringArray& other) const noexcept +{ + return strings == other.strings; +} + +bool StringArray::operator!= (const StringArray& other) const noexcept +{ + return ! operator== (other); +} + +void StringArray::swapWith (StringArray& other) noexcept +{ + strings.swapWith (other.strings); +} + +void StringArray::clear() +{ + strings.clear(); +} + +void StringArray::clearQuick() +{ + strings.clearQuick(); +} + +const String& StringArray::operator[] (const int index) const noexcept +{ + if (isPositiveAndBelow (index, strings.size())) + return strings.getReference (index); + + return String::empty; +} + +String& StringArray::getReference (const int index) noexcept +{ + return strings.getReference (index); +} + +void StringArray::add (const String& newString) +{ + strings.add (newString); +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +void StringArray::add (String&& stringToAdd) +{ + strings.add (static_cast (stringToAdd)); +} +#endif + +void StringArray::insert (const int index, const String& newString) +{ + strings.insert (index, newString); +} + +void StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase) +{ + if (! contains (newString, ignoreCase)) + add (newString); +} + +void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd) +{ + if (startIndex < 0) + { + jassertfalse; + startIndex = 0; + } + + if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size()) + numElementsToAdd = otherArray.size() - startIndex; + + while (--numElementsToAdd >= 0) + strings.add (otherArray.strings.getReference (startIndex++)); +} + +void StringArray::set (const int index, const String& newString) +{ + strings.set (index, newString); +} + +bool StringArray::contains (StringRef stringToLookFor, const bool ignoreCase) const +{ + return indexOf (stringToLookFor, ignoreCase) >= 0; +} + +int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int i) const +{ + if (i < 0) + i = 0; + + const int numElements = size(); + + if (ignoreCase) + { + for (; i < numElements; ++i) + if (strings.getReference(i).equalsIgnoreCase (stringToLookFor)) + return i; + } + else + { + for (; i < numElements; ++i) + if (stringToLookFor == strings.getReference (i)) + return i; + } + + return -1; +} + +void StringArray::move (const int currentIndex, const int newIndex) noexcept +{ + strings.move (currentIndex, newIndex); +} + +//============================================================================== +void StringArray::remove (const int index) +{ + strings.remove (index); +} + +void StringArray::removeString (StringRef stringToRemove, const bool ignoreCase) +{ + if (ignoreCase) + { + for (int i = size(); --i >= 0;) + if (strings.getReference(i).equalsIgnoreCase (stringToRemove)) + strings.remove (i); + } + else + { + for (int i = size(); --i >= 0;) + if (stringToRemove == strings.getReference (i)) + strings.remove (i); + } +} + +void StringArray::removeRange (int startIndex, int numberToRemove) +{ + strings.removeRange (startIndex, numberToRemove); +} + +//============================================================================== +void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings) +{ + if (removeWhitespaceStrings) + { + for (int i = size(); --i >= 0;) + if (! strings.getReference(i).containsNonWhitespaceChars()) + strings.remove (i); + } + else + { + for (int i = size(); --i >= 0;) + if (strings.getReference(i).isEmpty()) + strings.remove (i); + } +} + +void StringArray::trim() +{ + for (int i = size(); --i >= 0;) + { + String& s = strings.getReference(i); + s = s.trim(); + } +} + +//============================================================================== +struct InternalStringArrayComparator_CaseSensitive +{ + static int compareElements (String& s1, String& s2) noexcept { return s1.compare (s2); } +}; + +struct InternalStringArrayComparator_CaseInsensitive +{ + static int compareElements (String& s1, String& s2) noexcept { return s1.compareIgnoreCase (s2); } +}; + +struct InternalStringArrayComparator_Natural +{ + static int compareElements (String& s1, String& s2) noexcept { return s1.compareNatural (s2); } +}; + +void StringArray::sort (const bool ignoreCase) +{ + if (ignoreCase) + { + InternalStringArrayComparator_CaseInsensitive comp; + strings.sort (comp); + } + else + { + InternalStringArrayComparator_CaseSensitive comp; + strings.sort (comp); + } +} + +void StringArray::sortNatural() +{ + InternalStringArrayComparator_Natural comp; + strings.sort (comp); +} + +//============================================================================== +String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const +{ + const int last = (numberToJoin < 0) ? size() + : jmin (size(), start + numberToJoin); + + if (start < 0) + start = 0; + + if (start >= last) + return String(); + + if (start == last - 1) + return strings.getReference (start); + + const size_t separatorBytes = separator.text.sizeInBytes() - sizeof (String::CharPointerType::CharType); + size_t bytesNeeded = separatorBytes * (size_t) (last - start - 1); + + for (int i = start; i < last; ++i) + bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType); + + String result; + result.preallocateBytes (bytesNeeded); + + String::CharPointerType dest (result.getCharPointer()); + + while (start < last) + { + const String& s = strings.getReference (start); + + if (! s.isEmpty()) + dest.writeAll (s.getCharPointer()); + + if (++start < last && separatorBytes > 0) + dest.writeAll (separator.text); + } + + dest.writeNull(); + + return result; +} + +int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings) +{ + return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : ""); +} + +int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters) +{ + int num = 0; + + if (text.isNotEmpty()) + { + for (String::CharPointerType t (text.text);;) + { + String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t, + breakCharacters.text, + quoteCharacters.text)); + strings.add (String (t, tokenEnd)); + ++num; + + if (tokenEnd.isEmpty()) + break; + + t = ++tokenEnd; + } + } + + return num; +} + +int StringArray::addLines (StringRef sourceText) +{ + int numLines = 0; + String::CharPointerType text (sourceText.text); + bool finished = text.isEmpty(); + + while (! finished) + { + for (String::CharPointerType startOfLine (text);;) + { + const String::CharPointerType endOfLine (text); + + switch (text.getAndAdvance()) + { + case 0: finished = true; break; + case '\n': break; + case '\r': if (*text == '\n') ++text; break; + default: continue; + } + + strings.add (String (startOfLine, endOfLine)); + ++numLines; + break; + } + } + + return numLines; +} + +StringArray StringArray::fromTokens (StringRef stringToTokenise, bool preserveQuotedStrings) +{ + StringArray s; + s.addTokens (stringToTokenise, preserveQuotedStrings); + return s; +} + +StringArray StringArray::fromTokens (StringRef stringToTokenise, + StringRef breakCharacters, + StringRef quoteCharacters) +{ + StringArray s; + s.addTokens (stringToTokenise, breakCharacters, quoteCharacters); + return s; +} + +StringArray StringArray::fromLines (StringRef stringToBreakUp) +{ + StringArray s; + s.addLines (stringToBreakUp); + return s; +} + +//============================================================================== +void StringArray::removeDuplicates (const bool ignoreCase) +{ + for (int i = 0; i < size() - 1; ++i) + { + const String s (strings.getReference(i)); + + for (int nextIndex = i + 1;;) + { + nextIndex = indexOf (s, ignoreCase, nextIndex); + + if (nextIndex < 0) + break; + + strings.remove (nextIndex); + } + } +} + +void StringArray::appendNumbersToDuplicates (const bool ignoreCase, + const bool appendNumberToFirstInstance, + CharPointer_UTF8 preNumberString, + CharPointer_UTF8 postNumberString) +{ + CharPointer_UTF8 defaultPre (" ("), defaultPost (")"); + + if (preNumberString.getAddress() == nullptr) + preNumberString = defaultPre; + + if (postNumberString.getAddress() == nullptr) + postNumberString = defaultPost; + + for (int i = 0; i < size() - 1; ++i) + { + String& s = strings.getReference(i); + + int nextIndex = indexOf (s, ignoreCase, i + 1); + + if (nextIndex >= 0) + { + const String original (s); + + int number = 0; + + if (appendNumberToFirstInstance) + s = original + String (preNumberString) + String (++number) + String (postNumberString); + else + ++number; + + while (nextIndex >= 0) + { + set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString)); + nextIndex = indexOf (original, ignoreCase, nextIndex + 1); + } + } + } +} + +void StringArray::ensureStorageAllocated (int minNumElements) +{ + strings.ensureStorageAllocated (minNumElements); +} + +void StringArray::minimiseStorageOverheads() +{ + strings.minimiseStorageOverheads(); +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h new file mode 100644 index 0000000..52335e1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h @@ -0,0 +1,421 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGARRAY_H_INCLUDED +#define JUCE_STRINGARRAY_H_INCLUDED + + +//============================================================================== +/** + A special array for holding a list of strings. + + @see String, StringPairArray +*/ +class JUCE_API StringArray +{ +public: + //============================================================================== + /** Creates an empty string array */ + StringArray() noexcept; + + /** Creates a copy of another string array */ + StringArray (const StringArray&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + StringArray (StringArray&&) noexcept; + #endif + + /** Creates an array containing a single string. */ + explicit StringArray (const String& firstValue); + + /** Creates an array from a raw array of strings. + @param strings an array of strings to add + @param numberOfStrings how many items there are in the array + */ + StringArray (const String* strings, int numberOfStrings); + + /** Creates a copy of an array of string literals. + @param strings an array of strings to add. Null pointers in the array will be + treated as empty strings + @param numberOfStrings how many items there are in the array + */ + StringArray (const char* const* strings, int numberOfStrings); + + /** Creates a copy of a null-terminated array of string literals. + + Each item from the array passed-in is added, until it encounters a null pointer, + at which point it stops. + */ + explicit StringArray (const char* const* strings); + + /** Creates a copy of a null-terminated array of string literals. + Each item from the array passed-in is added, until it encounters a null pointer, + at which point it stops. + */ + explicit StringArray (const wchar_t* const* strings); + + /** Creates a copy of an array of string literals. + @param strings an array of strings to add. Null pointers in the array will be + treated as empty strings + @param numberOfStrings how many items there are in the array + */ + StringArray (const wchar_t* const* strings, int numberOfStrings); + + /** Destructor. */ + ~StringArray(); + + /** Copies the contents of another string array into this one */ + StringArray& operator= (const StringArray&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + StringArray& operator= (StringArray&&) noexcept; + #endif + + /** Swaps the contents of this and another StringArray. */ + void swapWith (StringArray&) noexcept; + + //============================================================================== + /** Compares two arrays. + Comparisons are case-sensitive. + @returns true only if the other array contains exactly the same strings in the same order + */ + bool operator== (const StringArray&) const noexcept; + + /** Compares two arrays. + Comparisons are case-sensitive. + @returns false if the other array contains exactly the same strings in the same order + */ + bool operator!= (const StringArray&) const noexcept; + + //============================================================================== + /** Returns the number of strings in the array */ + inline int size() const noexcept { return strings.size(); }; + + /** Returns one of the strings from the array. + + If the index is out-of-range, an empty string is returned. + + Obviously the reference returned shouldn't be stored for later use, as the + string it refers to may disappear when the array changes. + */ + const String& operator[] (int index) const noexcept; + + /** Returns a reference to one of the strings in the array. + This lets you modify a string in-place in the array, but you must be sure that + the index is in-range. + */ + String& getReference (int index) noexcept; + + /** Returns a pointer to the first String in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline String* begin() const noexcept { return strings.begin(); } + + /** Returns a pointer to the String which follows the last element in the array. + This method is provided for compatibility with standard C++ iteration mechanisms. + */ + inline String* end() const noexcept { return strings.end(); } + + /** Searches for a string in the array. + + The comparison will be case-insensitive if the ignoreCase parameter is true. + + @returns true if the string is found inside the array + */ + bool contains (StringRef stringToLookFor, + bool ignoreCase = false) const; + + /** Searches for a string in the array. + + The comparison will be case-insensitive if the ignoreCase parameter is true. + + @param stringToLookFor the string to try to find + @param ignoreCase whether the comparison should be case-insensitive + @param startIndex the first index to start searching from + @returns the index of the first occurrence of the string in this array, + or -1 if it isn't found. + */ + int indexOf (StringRef stringToLookFor, + bool ignoreCase = false, + int startIndex = 0) const; + + //============================================================================== + /** Appends a string at the end of the array. */ + void add (const String& stringToAdd); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Appends a string at the end of the array. */ + void add (String&& stringToAdd); + #endif + + /** Inserts a string into the array. + + This will insert a string into the array at the given index, moving + up the other elements to make room for it. + If the index is less than zero or greater than the size of the array, + the new string will be added to the end of the array. + */ + void insert (int index, const String& stringToAdd); + + /** Adds a string to the array as long as it's not already in there. + The search can optionally be case-insensitive. + */ + void addIfNotAlreadyThere (const String& stringToAdd, bool ignoreCase = false); + + /** Replaces one of the strings in the array with another one. + + If the index is higher than the array's size, the new string will be + added to the end of the array; if it's less than zero nothing happens. + */ + void set (int index, const String& newString); + + /** Appends some strings from another array to the end of this one. + + @param other the array to add + @param startIndex the first element of the other array to add + @param numElementsToAdd the maximum number of elements to add (if this is + less than zero, they are all added) + */ + void addArray (const StringArray& other, + int startIndex = 0, + int numElementsToAdd = -1); + + /** Breaks up a string into tokens and adds them to this array. + + This will tokenise the given string using whitespace characters as the + token delimiters, and will add these tokens to the end of the array. + @returns the number of tokens added + @see fromTokens + */ + int addTokens (StringRef stringToTokenise, bool preserveQuotedStrings); + + /** Breaks up a string into tokens and adds them to this array. + + This will tokenise the given string (using the string passed in to define the + token delimiters), and will add these tokens to the end of the array. + + @param stringToTokenise the string to tokenise + @param breakCharacters a string of characters, any of which will be considered + to be a token delimiter. + @param quoteCharacters if this string isn't empty, it defines a set of characters + which are treated as quotes. Any text occurring + between quotes is not broken up into tokens. + @returns the number of tokens added + @see fromTokens + */ + int addTokens (StringRef stringToTokenise, + StringRef breakCharacters, + StringRef quoteCharacters); + + /** Breaks up a string into lines and adds them to this array. + + This breaks a string down into lines separated by \\n or \\r\\n, and adds each line + to the array. Line-break characters are omitted from the strings that are added to + the array. + */ + int addLines (StringRef stringToBreakUp); + + /** Returns an array containing the tokens in a given string. + + This will tokenise the given string using whitespace characters as the + token delimiters, and return these tokens as an array. + @see addTokens + */ + static StringArray fromTokens (StringRef stringToTokenise, + bool preserveQuotedStrings); + + /** Returns an array containing the tokens in a given string. + + This will tokenise the given string using whitespace characters as the + token delimiters, and return these tokens as an array. + + @param stringToTokenise the string to tokenise + @param breakCharacters a string of characters, any of which will be considered + to be a token delimiter. + @param quoteCharacters if this string isn't empty, it defines a set of characters + which are treated as quotes. Any text occurring + between quotes is not broken up into tokens. + @see addTokens + */ + static StringArray fromTokens (StringRef stringToTokenise, + StringRef breakCharacters, + StringRef quoteCharacters); + + /** Returns an array containing the lines in a given string. + + This breaks a string down into lines separated by \\n or \\r\\n, and returns an + array containing these lines. Line-break characters are omitted from the strings that + are added to the array. + */ + static StringArray fromLines (StringRef stringToBreakUp); + + //============================================================================== + /** Removes all elements from the array. */ + void clear(); + + /** Removes all elements from the array without freeing the array's allocated storage. + @see clear + */ + void clearQuick(); + + /** Removes a string from the array. + If the index is out-of-range, no action will be taken. + */ + void remove (int index); + + /** Finds a string in the array and removes it. + This will remove the first occurrence of the given string from the array. The + comparison may be case-insensitive depending on the ignoreCase parameter. + */ + void removeString (StringRef stringToRemove, + bool ignoreCase = false); + + /** Removes a range of elements from the array. + + This will remove a set of elements, starting from the given index, + and move subsequent elements down to close the gap. + + If the range extends beyond the bounds of the array, it will + be safely clipped to the size of the array. + + @param startIndex the index of the first element to remove + @param numberToRemove how many elements should be removed + */ + void removeRange (int startIndex, int numberToRemove); + + /** Removes any duplicated elements from the array. + + If any string appears in the array more than once, only the first occurrence of + it will be retained. + + @param ignoreCase whether to use a case-insensitive comparison + */ + void removeDuplicates (bool ignoreCase); + + /** Removes empty strings from the array. + @param removeWhitespaceStrings if true, strings that only contain whitespace + characters will also be removed + */ + void removeEmptyStrings (bool removeWhitespaceStrings = true); + + /** Moves one of the strings to a different position. + + This will move the string to a specified index, shuffling along + any intervening elements as required. + + So for example, if you have the array { 0, 1, 2, 3, 4, 5 } then calling + move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the value to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this value to end up. If this + is less than zero, the value will be moved to the end + of the array + */ + void move (int currentIndex, int newIndex) noexcept; + + /** Deletes any whitespace characters from the starts and ends of all the strings. */ + void trim(); + + /** Adds numbers to the strings in the array, to make each string unique. + + This will add numbers to the ends of groups of similar strings. + e.g. if there are two "moose" strings, they will become "moose (1)" and "moose (2)" + + @param ignoreCaseWhenComparing whether the comparison used is case-insensitive + @param appendNumberToFirstInstance whether the first of a group of similar strings + also has a number appended to it. + @param preNumberString when adding a number, this string is added before the number. + If you pass 0, a default string will be used, which adds + brackets around the number. + @param postNumberString this string is appended after any numbers that are added. + If you pass 0, a default string will be used, which adds + brackets around the number. + */ + void appendNumbersToDuplicates (bool ignoreCaseWhenComparing, + bool appendNumberToFirstInstance, + CharPointer_UTF8 preNumberString = CharPointer_UTF8 (nullptr), + CharPointer_UTF8 postNumberString = CharPointer_UTF8 (nullptr)); + + //============================================================================== + /** Joins the strings in the array together into one string. + + This will join a range of elements from the array into a string, separating + them with a given string. + + e.g. joinIntoString (",") will turn an array of "a" "b" and "c" into "a,b,c". + + @param separatorString the string to insert between all the strings + @param startIndex the first element to join + @param numberOfElements how many elements to join together. If this is less + than zero, all available elements will be used. + */ + String joinIntoString (StringRef separatorString, + int startIndex = 0, + int numberOfElements = -1) const; + + //============================================================================== + /** Sorts the array into alphabetical order. + @param ignoreCase if true, the comparisons used will be case-sensitive. + */ + void sort (bool ignoreCase); + + /** Sorts the array using extra language-aware rules to do a better job of comparing + words containing spaces and numbers. + @see String::compareNatural() + */ + void sortNatural(); + + //============================================================================== + /** Increases the array's internal storage to hold a minimum number of elements. + + Calling this before adding a large known number of elements means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + */ + void ensureStorageAllocated (int minNumElements); + + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads(); + + /** This is the array holding the actual strings. This is public to allow direct access + to array methods that may not already be provided by the StringArray class. + */ + Array strings; + +private: + JUCE_LEAK_DETECTOR (StringArray) +}; + + +#endif // JUCE_STRINGARRAY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp new file mode 100644 index 0000000..3275d2d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp @@ -0,0 +1,147 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +StringPairArray::StringPairArray (const bool ignoreCase_) + : ignoreCase (ignoreCase_) +{ +} + +StringPairArray::StringPairArray (const StringPairArray& other) + : keys (other.keys), + values (other.values), + ignoreCase (other.ignoreCase) +{ +} + +StringPairArray::~StringPairArray() +{ +} + +StringPairArray& StringPairArray::operator= (const StringPairArray& other) +{ + keys = other.keys; + values = other.values; + return *this; +} + +bool StringPairArray::operator== (const StringPairArray& other) const +{ + for (int i = keys.size(); --i >= 0;) + if (other [keys[i]] != values[i]) + return false; + + return true; +} + +bool StringPairArray::operator!= (const StringPairArray& other) const +{ + return ! operator== (other); +} + +const String& StringPairArray::operator[] (StringRef key) const +{ + return values [keys.indexOf (key, ignoreCase)]; +} + +String StringPairArray::getValue (StringRef key, const String& defaultReturnValue) const +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + return values[i]; + + return defaultReturnValue; +} + +bool StringPairArray::containsKey (StringRef key) const noexcept +{ + return keys.contains (key); +} + +void StringPairArray::set (const String& key, const String& value) +{ + const int i = keys.indexOf (key, ignoreCase); + + if (i >= 0) + { + values.set (i, value); + } + else + { + keys.add (key); + values.add (value); + } +} + +void StringPairArray::addArray (const StringPairArray& other) +{ + for (int i = 0; i < other.size(); ++i) + set (other.keys[i], other.values[i]); +} + +void StringPairArray::clear() +{ + keys.clear(); + values.clear(); +} + +void StringPairArray::remove (StringRef key) +{ + remove (keys.indexOf (key, ignoreCase)); +} + +void StringPairArray::remove (const int index) +{ + keys.remove (index); + values.remove (index); +} + +void StringPairArray::setIgnoresCase (const bool shouldIgnoreCase) +{ + ignoreCase = shouldIgnoreCase; +} + +String StringPairArray::getDescription() const +{ + String s; + + for (int i = 0; i < keys.size(); ++i) + { + s << keys[i] << " = " << values[i]; + if (i < keys.size()) + s << ", "; + } + + return s; +} + +void StringPairArray::minimiseStorageOverheads() +{ + keys.minimiseStorageOverheads(); + values.minimiseStorageOverheads(); +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h new file mode 100644 index 0000000..e1c774d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h @@ -0,0 +1,157 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGPAIRARRAY_H_INCLUDED +#define JUCE_STRINGPAIRARRAY_H_INCLUDED + + +//============================================================================== +/** + A container for holding a set of strings which are keyed by another string. + + @see StringArray +*/ +class JUCE_API StringPairArray +{ +public: + //============================================================================== + /** Creates an empty array */ + StringPairArray (bool ignoreCaseWhenComparingKeys = true); + + /** Creates a copy of another array */ + StringPairArray (const StringPairArray& other); + + /** Destructor. */ + ~StringPairArray(); + + /** Copies the contents of another string array into this one */ + StringPairArray& operator= (const StringPairArray& other); + + //============================================================================== + /** Compares two arrays. + Comparisons are case-sensitive. + @returns true only if the other array contains exactly the same strings with the same keys + */ + bool operator== (const StringPairArray& other) const; + + /** Compares two arrays. + Comparisons are case-sensitive. + @returns false if the other array contains exactly the same strings with the same keys + */ + bool operator!= (const StringPairArray& other) const; + + //============================================================================== + /** Finds the value corresponding to a key string. + + If no such key is found, this will just return an empty string. To check whether + a given key actually exists (because it might actually be paired with an empty string), use + the getAllKeys() method to obtain a list. + + Obviously the reference returned shouldn't be stored for later use, as the + string it refers to may disappear when the array changes. + + @see getValue + */ + const String& operator[] (StringRef key) const; + + /** Finds the value corresponding to a key string. + If no such key is found, this will just return the value provided as a default. + @see operator[] + */ + String getValue (StringRef, const String& defaultReturnValue) const; + + /** Returns true if the given key exists. */ + bool containsKey (StringRef key) const noexcept; + + /** Returns a list of all keys in the array. */ + const StringArray& getAllKeys() const noexcept { return keys; } + + /** Returns a list of all values in the array. */ + const StringArray& getAllValues() const noexcept { return values; } + + /** Returns the number of strings in the array */ + inline int size() const noexcept { return keys.size(); }; + + + //============================================================================== + /** Adds or amends a key/value pair. + If a value already exists with this key, its value will be overwritten, + otherwise the key/value pair will be added to the array. + */ + void set (const String& key, const String& value); + + /** Adds the items from another array to this one. + This is equivalent to using set() to add each of the pairs from the other array. + */ + void addArray (const StringPairArray& other); + + //============================================================================== + /** Removes all elements from the array. */ + void clear(); + + /** Removes a string from the array based on its key. + If the key isn't found, nothing will happen. + */ + void remove (StringRef key); + + /** Removes a string from the array based on its index. + If the index is out-of-range, no action will be taken. + */ + void remove (int index); + + //============================================================================== + /** Indicates whether to use a case-insensitive search when looking up a key string. + */ + void setIgnoresCase (bool shouldIgnoreCase); + + //============================================================================== + /** Returns a descriptive string containing the items. + This is handy for dumping the contents of an array. + */ + String getDescription() const; + + //============================================================================== + /** Reduces the amount of storage being used by the array. + + Arrays typically allocate slightly more storage than they need, and after + removing elements, they may have quite a lot of unused space allocated. + This method will reduce the amount of allocated storage to a minimum. + */ + void minimiseStorageOverheads(); + + +private: + //============================================================================== + StringArray keys, values; + bool ignoreCase; + + JUCE_LEAK_DETECTOR (StringPairArray) +}; + + +#endif // JUCE_STRINGPAIRARRAY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp new file mode 100644 index 0000000..039a30b --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.cpp @@ -0,0 +1,166 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +static const int minNumberOfStringsForGarbageCollection = 300; +static const uint32 garbageCollectionInterval = 30000; + + +StringPool::StringPool() noexcept : lastGarbageCollectionTime (0) {} +StringPool::~StringPool() {} + +struct StartEndString +{ + StartEndString (String::CharPointerType s, String::CharPointerType e) noexcept : start (s), end (e) {} + operator String() const { return String (start, end); } + + String::CharPointerType start, end; +}; + +static int compareStrings (const String& s1, const String& s2) noexcept { return s1.compare (s2); } +static int compareStrings (CharPointer_UTF8 s1, const String& s2) noexcept { return s1.compare (s2.getCharPointer()); } + +static int compareStrings (const StartEndString& string1, const String& string2) noexcept +{ + String::CharPointerType s1 (string1.start), s2 (string2.getCharPointer()); + + for (;;) + { + const int c1 = s1 < string1.end ? (int) s1.getAndAdvance() : 0; + const int c2 = (int) s2.getAndAdvance(); + const int diff = c1 - c2; + + if (diff != 0) return diff < 0 ? -1 : 1; + if (c1 == 0) break; + } + + return 0; +} + +template +static String addPooledString (Array& strings, const NewStringType& newString) +{ + int start = 0; + int end = strings.size(); + + while (start < end) + { + const String& startString = strings.getReference (start); + const int startComp = compareStrings (newString, startString); + + if (startComp == 0) + return startString; + + const int halfway = (start + end) / 2; + + if (halfway == start) + { + if (startComp > 0) + ++start; + + break; + } + + const String& halfwayString = strings.getReference (halfway); + const int halfwayComp = compareStrings (newString, halfwayString); + + if (halfwayComp == 0) + return halfwayString; + + if (halfwayComp > 0) + start = halfway; + else + end = halfway; + } + + strings.insert (start, newString); + return strings.getReference (start); +} + +String StringPool::getPooledString (const char* const newString) +{ + if (newString == nullptr || *newString == 0) + return String(); + + const ScopedLock sl (lock); + garbageCollectIfNeeded(); + return addPooledString (strings, CharPointer_UTF8 (newString)); +} + +String StringPool::getPooledString (String::CharPointerType start, String::CharPointerType end) +{ + if (start.isEmpty() || start == end) + return String(); + + const ScopedLock sl (lock); + garbageCollectIfNeeded(); + return addPooledString (strings, StartEndString (start, end)); +} + +String StringPool::getPooledString (StringRef newString) +{ + if (newString.isEmpty()) + return String(); + + const ScopedLock sl (lock); + garbageCollectIfNeeded(); + return addPooledString (strings, newString.text); +} + +String StringPool::getPooledString (const String& newString) +{ + if (newString.isEmpty()) + return String(); + + const ScopedLock sl (lock); + garbageCollectIfNeeded(); + return addPooledString (strings, newString); +} + +void StringPool::garbageCollectIfNeeded() +{ + if (strings.size() > minNumberOfStringsForGarbageCollection + && Time::getApproximateMillisecondCounter() > lastGarbageCollectionTime + garbageCollectionInterval) + garbageCollect(); +} + +void StringPool::garbageCollect() +{ + const ScopedLock sl (lock); + + for (int i = strings.size(); --i >= 0;) + if (strings.getReference(i).getReferenceCount() == 1) + strings.remove (i); + + lastGarbageCollectionTime = Time::getApproximateMillisecondCounter(); +} + +StringPool& StringPool::getGlobalPool() noexcept +{ + static StringPool pool; + return pool; +} diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPool.h b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.h new file mode 100644 index 0000000..4de2186 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPool.h @@ -0,0 +1,94 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGPOOL_H_INCLUDED +#define JUCE_STRINGPOOL_H_INCLUDED + + +//============================================================================== +/** + A StringPool holds a set of shared strings, which reduces storage overheads and improves + comparison speed when dealing with many duplicate strings. + + When you add a string to a pool using getPooledString, it'll return a character + array containing the same string. This array is owned by the pool, and the same array + is returned every time a matching string is asked for. This means that it's trivial to + compare two pooled strings for equality, as you can simply compare their pointers. It + also cuts down on storage if you're using many copies of the same string. +*/ +class JUCE_API StringPool +{ +public: + //============================================================================== + /** Creates an empty pool. */ + StringPool() noexcept; + + /** Destructor */ + ~StringPool(); + + //============================================================================== + /** Returns a pointer to a shared copy of the string that is passed in. + The pool will always return the same String object when asked for a string that matches it. + */ + String getPooledString (const String& original); + + /** Returns a pointer to a copy of the string that is passed in. + The pool will always return the same String object when asked for a string that matches it. + */ + String getPooledString (const char* original); + + /** Returns a pointer to a shared copy of the string that is passed in. + The pool will always return the same String object when asked for a string that matches it. + */ + String getPooledString (StringRef original); + + /** Returns a pointer to a copy of the string that is passed in. + The pool will always return the same String object when asked for a string that matches it. + */ + String getPooledString (String::CharPointerType start, String::CharPointerType end); + + //============================================================================== + /** Scans the pool, and removes any strings that are unreferenced. + You don't generally need to call this - it'll be called automatically when the pool grows + large enough to warrant it. + */ + void garbageCollect(); + + /** Returns a shared global pool which is used for things like Identifiers, XML parsing. */ + static StringPool& getGlobalPool() noexcept; + +private: + Array strings; + CriticalSection lock; + uint32 lastGarbageCollectionTime; + + void garbageCollectIfNeeded(); +}; + + +#endif // JUCE_STRINGPOOL_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringRef.h b/JuceLibraryCode/modules/juce_core/text/juce_StringRef.h new file mode 100644 index 0000000..434bf4a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringRef.h @@ -0,0 +1,138 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_STRINGREF_H_INCLUDED +#define JUCE_STRINGREF_H_INCLUDED + +//============================================================================== +/** + A simple class for holding temporary references to a string literal or String. + + Unlike a real String object, the StringRef does not allocate any memory or + take ownership of the strings you give to it - it simply holds a reference to + a string that has been allocated elsewhere. + The main purpose of the class is to be used instead of a const String& as the type + of function arguments where the caller may pass either a string literal or a String + object. This means that when the called uses a string literal, there's no need + for an temporary String object to be allocated, and this cuts down overheads + substantially. + + Because the class is simply a wrapper around a pointer, you should always pass + it by value, not by reference. + + @code + void myStringFunction1 (const String&); + void myStringFunction2 (StringRef); + + myStringFunction1 ("abc"); // Implicitly allocates a temporary String object. + myStringFunction2 ("abc"); // Much faster, as no local allocations are needed. + @endcode + + For examples of it in use, see the XmlElement or StringArray classes. + + Bear in mind that there are still many cases where it's better to use an argument + which is a const String&. For example if the function stores the string or needs + to internally create a String from the argument, then it's better for the original + argument to already be a String. + + @see String +*/ +class JUCE_API StringRef +{ +public: + /** Creates a StringRef from a raw string literal. + The StringRef object does NOT take ownership or copy this data, so you must + ensure that the data does not change during the lifetime of the StringRef. + Note that this pointer not be null! + */ + StringRef (const char* stringLiteral) noexcept; + + /** Creates a StringRef from a raw char pointer. + The StringRef object does NOT take ownership or copy this data, so you must + ensure that the data does not change during the lifetime of the StringRef. + */ + StringRef (String::CharPointerType stringLiteral) noexcept; + + /** Creates a StringRef from a String. + The StringRef object does NOT take ownership or copy the data from the String, + so you must ensure that the String is not modified or deleted during the lifetime + of the StringRef. + */ + StringRef (const String& string) noexcept; + + /** Creates a StringRef pointer to an empty string. */ + StringRef() noexcept; + + //============================================================================== + /** Returns a raw pointer to the underlying string data. */ + operator const String::CharPointerType::CharType*() const noexcept { return text.getAddress(); } + /** Returns a pointer to the underlying string data as a char pointer object. */ + operator String::CharPointerType() const noexcept { return text; } + + /** Returns true if the string is empty. */ + bool isEmpty() const noexcept { return text.isEmpty(); } + /** Returns true if the string is not empty. */ + bool isNotEmpty() const noexcept { return ! text.isEmpty(); } + /** Returns the number of characters in the string. */ + int length() const noexcept { return (int) text.length(); } + + /** Retrieves a character by index. */ + juce_wchar operator[] (int index) const noexcept { return text[index]; } + + /** Compares this StringRef with a String. */ + bool operator== (const String& s) const noexcept { return text.compare (s.getCharPointer()) == 0; } + /** Compares this StringRef with a String. */ + bool operator!= (const String& s) const noexcept { return text.compare (s.getCharPointer()) != 0; } + + /** Case-sensitive comparison of two StringRefs. */ + bool operator== (StringRef s) const noexcept { return text.compare (s.text) == 0; } + /** Case-sensitive comparison of two StringRefs. */ + bool operator!= (StringRef s) const noexcept { return text.compare (s.text) != 0; } + + //============================================================================== + /** The text that is referenced. */ + String::CharPointerType text; + + #if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN) + // Sorry, non-UTF8 people, you're unable to take advantage of StringRef, because + // you've chosen a character encoding that doesn't match C++ string literals. + String stringCopy; + #endif +}; + +//============================================================================== +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, StringRef string2) noexcept; +/** Case-sensitive comparison of two strings. */ +JUCE_API bool JUCE_CALLTYPE operator!= (const String& string1, StringRef string2) noexcept; + +#if JUCE_STRING_UTF_TYPE != 8 && ! defined (DOXYGEN) + inline String operator+ (String s1, StringRef s2) { return s1 += String (s2.text); } +#endif + +#endif // JUCE_STRINGREF_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.cpp b/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.cpp new file mode 100644 index 0000000..6b4c807 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.cpp @@ -0,0 +1,247 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +struct TextDiffHelpers +{ + enum { minLengthToMatch = 3 }; + + struct StringRegion + { + StringRegion (const String& s) noexcept + : text (s.getCharPointer()), start (0), length (s.length()) {} + + StringRegion (const String::CharPointerType t, int s, int len) noexcept + : text (t), start (s), length (len) {} + + String::CharPointerType text; + int start, length; + }; + + static void addInsertion (TextDiff& td, const String::CharPointerType text, int index, int length) + { + TextDiff::Change c; + c.insertedText = String (text, (size_t) length); + c.start = index; + c.length = length; + td.changes.add (c); + } + + static void addDeletion (TextDiff& td, int index, int length) + { + TextDiff::Change c; + c.start = index; + c.length = length; + td.changes.add (c); + } + + static void diffSkippingCommonStart (TextDiff& td, const StringRegion& a, const StringRegion& b) + { + String::CharPointerType sa (a.text); + String::CharPointerType sb (b.text); + const int maxLen = jmax (a.length, b.length); + + for (int i = 0; i < maxLen; ++i, ++sa, ++sb) + { + if (*sa != *sb) + { + diffRecursively (td, StringRegion (sa, a.start + i, a.length - i), + StringRegion (sb, b.start + i, b.length - i)); + break; + } + } + } + + static void diffRecursively (TextDiff& td, const StringRegion& a, const StringRegion& b) + { + int indexA, indexB; + const int len = findLongestCommonSubstring (a.text, a.length, + b.text, b.length, + indexA, indexB); + + if (len >= minLengthToMatch) + { + if (indexA > 0 && indexB > 0) + diffSkippingCommonStart (td, StringRegion (a.text, a.start, indexA), + StringRegion (b.text, b.start, indexB)); + else if (indexA > 0) + addDeletion (td, b.start, indexA); + else if (indexB > 0) + addInsertion (td, b.text, b.start, indexB); + + diffRecursively (td, StringRegion (a.text + indexA + len, a.start + indexA + len, a.length - indexA - len), + StringRegion (b.text + indexB + len, b.start + indexB + len, b.length - indexB - len)); + } + else + { + if (a.length > 0) addDeletion (td, b.start, a.length); + if (b.length > 0) addInsertion (td, b.text, b.start, b.length); + } + } + + static int findLongestCommonSubstring (String::CharPointerType a, const int lenA, + const String::CharPointerType b, const int lenB, + int& indexInA, int& indexInB) + { + if (lenA == 0 || lenB == 0) + return 0; + + HeapBlock lines; + lines.calloc (2 + 2 * (size_t) lenB); + + int* l0 = lines; + int* l1 = l0 + lenB + 1; + + int loopsWithoutImprovement = 0; + int bestLength = 0; + indexInA = indexInB = 0; + + for (int i = 0; i < lenA; ++i) + { + const juce_wchar ca = a.getAndAdvance(); + String::CharPointerType b2 (b); + + for (int j = 0; j < lenB; ++j) + { + if (ca != b2.getAndAdvance()) + { + l1[j + 1] = 0; + } + else + { + const int len = l0[j] + 1; + l1[j + 1] = len; + + if (len > bestLength) + { + loopsWithoutImprovement = 0; + bestLength = len; + indexInA = i; + indexInB = j; + } + } + } + + if (++loopsWithoutImprovement > 100) + break; + + std::swap (l0, l1); + } + + indexInA -= bestLength - 1; + indexInB -= bestLength - 1; + return bestLength; + } +}; + +TextDiff::TextDiff (const String& original, const String& target) +{ + TextDiffHelpers::diffSkippingCommonStart (*this, original, target); +} + +String TextDiff::appliedTo (String text) const +{ + for (int i = 0; i < changes.size(); ++i) + text = changes.getReference(i).appliedTo (text); + + return text; +} + +bool TextDiff::Change::isDeletion() const noexcept +{ + return insertedText.isEmpty(); +} + +String TextDiff::Change::appliedTo (const String& text) const noexcept +{ + return text.substring (0, start) + (isDeletion() ? text.substring (start + length) + : (insertedText + text.substring (start))); +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class DiffTests : public UnitTest +{ +public: + DiffTests() : UnitTest ("TextDiff class") {} + + static String createString (Random& r) + { + juce_wchar buffer[50] = { 0 }; + + for (int i = r.nextInt (49); --i >= 0;) + { + if (r.nextInt (10) == 0) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) ('a' + r.nextInt (3)); + } + + return CharPointer_UTF32 (buffer); + } + + void testDiff (const String& a, const String& b) + { + TextDiff diff (a, b); + const String result (diff.appliedTo (a)); + expectEquals (result, b); + } + + void runTest() + { + beginTest ("TextDiff"); + + Random r = getRandom(); + + testDiff (String::empty, String::empty); + testDiff ("x", String::empty); + testDiff (String::empty, "x"); + testDiff ("x", "x"); + testDiff ("x", "y"); + testDiff ("xxx", "x"); + testDiff ("x", "xxx"); + + for (int i = 5000; --i >= 0;) + { + String s (createString (r)); + testDiff (s, createString (r)); + testDiff (s + createString (r), s + createString (r)); + } + } +}; + +static DiffTests diffTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.h b/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.h new file mode 100644 index 0000000..d420c3d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/text/juce_TextDiff.h @@ -0,0 +1,80 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TEXTDIFF_H_INCLUDED +#define JUCE_TEXTDIFF_H_INCLUDED + + +/** + Calculates and applies a sequence of changes to convert one text string into + another. + + Once created, the TextDiff object contains an array of change objects, where + each change can be either an insertion or a deletion. When applied in order + to the original string, these changes will convert it to the target string. +*/ +class JUCE_API TextDiff +{ +public: + /** Creates a set of diffs for converting the original string into the target. */ + TextDiff (const String& original, + const String& target); + + /** Applies this sequence of changes to the original string, producing the + target string that was specified when generating them. + + Obviously it only makes sense to call this function with the string that + was originally passed to the constructor. Any other input will produce an + undefined result. + */ + String appliedTo (String text) const; + + /** Describes a change, which can be either an insertion or deletion. */ + struct Change + { + String insertedText; /**< If this change is a deletion, this string will be empty; otherwise, + it'll be the text that should be inserted at the index specified by start. */ + int start; /**< Specifies the character index in a string at which text should be inserted or deleted. */ + int length; /**< If this change is a deletion, this specifies the number of characters to delete. For an + insertion, this is the length of the new text being inserted. */ + + /** Returns true if this change is a deletion, or false for an insertion. */ + bool isDeletion() const noexcept; + + /** Returns the result of applying this change to a string. */ + String appliedTo (const String& original) const noexcept; + }; + + /** The list of changes required to perform the transformation. + Applying each of these, in order, to the original string will produce the target. + */ + Array changes; +}; + + +#endif // JUCE_TEXTDIFF_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.cpp new file mode 100644 index 0000000..4566b13 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.cpp @@ -0,0 +1,113 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +ChildProcess::ChildProcess() {} +ChildProcess::~ChildProcess() {} + +bool ChildProcess::isRunning() const +{ + return activeProcess != nullptr && activeProcess->isRunning(); +} + +int ChildProcess::readProcessOutput (void* dest, int numBytes) +{ + return activeProcess != nullptr ? activeProcess->read (dest, numBytes) : 0; +} + +bool ChildProcess::kill() +{ + return activeProcess == nullptr || activeProcess->killProcess(); +} + +uint32 ChildProcess::getExitCode() const +{ + return activeProcess != nullptr ? activeProcess->getExitCode() : 0; +} + +bool ChildProcess::waitForProcessToFinish (const int timeoutMs) const +{ + const uint32 timeoutTime = Time::getMillisecondCounter() + (uint32) timeoutMs; + + do + { + if (! isRunning()) + return true; + } + while (timeoutMs < 0 || Time::getMillisecondCounter() < timeoutTime); + + return false; +} + +String ChildProcess::readAllProcessOutput() +{ + MemoryOutputStream result; + + for (;;) + { + char buffer [512]; + const int num = readProcessOutput (buffer, sizeof (buffer)); + + if (num <= 0) + break; + + result.write (buffer, (size_t) num); + } + + return result.toString(); +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class ChildProcessTests : public UnitTest +{ +public: + ChildProcessTests() : UnitTest ("ChildProcess") {} + + void runTest() + { + beginTest ("Child Processes"); + + #if JUCE_WINDOWS || JUCE_MAC || JUCE_LINUX + ChildProcess p; + + #if JUCE_WINDOWS + expect (p.start ("tasklist")); + #else + expect (p.start ("ls /")); + #endif + + //String output (p.readAllProcessOutput()); + //expect (output.isNotEmpty()); + #endif + } +}; + +static ChildProcessTests childProcessUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.h b/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.h new file mode 100644 index 0000000..0adcb57 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ChildProcess.h @@ -0,0 +1,119 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CHILDPROCESS_H_INCLUDED +#define JUCE_CHILDPROCESS_H_INCLUDED + + +//============================================================================== +/** + Launches and monitors a child process. + + This class lets you launch an executable, and read its output. You can also + use it to check whether the child process has finished. +*/ +class JUCE_API ChildProcess +{ +public: + //============================================================================== + /** Creates a process object. + To actually launch the process, use start(). + */ + ChildProcess(); + + /** Destructor. + Note that deleting this object won't terminate the child process. + */ + ~ChildProcess(); + + /** These flags are used by the start() methods. */ + enum StreamFlags + { + wantStdOut = 1, + wantStdErr = 2 + }; + + /** Attempts to launch a child process command. + + The command should be the name of the executable file, followed by any arguments + that are required. + If the process has already been launched, this will launch it again. If a problem + occurs, the method will return false. + The streamFlags is a combinations of values to indicate which of the child's output + streams should be read and returned by readProcessOutput(). + */ + bool start (const String& command, int streamFlags = wantStdOut | wantStdErr); + + /** Attempts to launch a child process command. + + The first argument should be the name of the executable file, followed by any other + arguments that are needed. + If the process has already been launched, this will launch it again. If a problem + occurs, the method will return false. + The streamFlags is a combinations of values to indicate which of the child's output + streams should be read and returned by readProcessOutput(). + */ + bool start (const StringArray& arguments, int streamFlags = wantStdOut | wantStdErr); + + /** Returns true if the child process is alive. */ + bool isRunning() const; + + /** Attempts to read some output from the child process. + This will attempt to read up to the given number of bytes of data from the + process. It returns the number of bytes that were actually read. + */ + int readProcessOutput (void* destBuffer, int numBytesToRead); + + /** Blocks until the process has finished, and then returns its complete output + as a string. + */ + String readAllProcessOutput(); + + /** Blocks until the process is no longer running. */ + bool waitForProcessToFinish (int timeoutMs) const; + + /** If the process has finished, this returns its exit code. */ + uint32 getExitCode() const; + + /** Attempts to kill the child process. + Returns true if it succeeded. Trying to read from the process after calling this may + result in undefined behaviour. + */ + bool kill(); + +private: + //============================================================================== + class ActiveProcess; + friend struct ContainerDeletePolicy; + ScopedPointer activeProcess; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess) +}; + + +#endif // JUCE_CHILDPROCESS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_CriticalSection.h b/JuceLibraryCode/modules/juce_core/threads/juce_CriticalSection.h new file mode 100644 index 0000000..60e61dd --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_CriticalSection.h @@ -0,0 +1,266 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_CRITICALSECTION_H_INCLUDED +#define JUCE_CRITICALSECTION_H_INCLUDED + + +//============================================================================== +/** + A re-entrant mutex. + + A CriticalSection acts as a re-entrant mutex object. The best way to lock and unlock + one of these is by using RAII in the form of a local ScopedLock object - have a look + through the codebase for many examples of how to do this. + + In almost all cases you'll want to declare your CriticalSection as a member variable. + Occasionally you may want to declare one as a static variable, but in that case the usual + C++ static object order-of-construction warnings should be heeded. + + @see ScopedLock, ScopedTryLock, ScopedUnlock, SpinLock, ReadWriteLock, Thread, InterProcessLock +*/ +class JUCE_API CriticalSection +{ +public: + //============================================================================== + /** Creates a CriticalSection object. */ + CriticalSection() noexcept; + + /** Destructor. + If the critical section is deleted whilst locked, any subsequent behaviour + is unpredictable. + */ + ~CriticalSection() noexcept; + + //============================================================================== + /** Acquires the lock. + + If the lock is already held by the caller thread, the method returns immediately. + If the lock is currently held by another thread, this will wait until it becomes free. + + It's strongly recommended that you never call this method directly - instead use the + ScopedLock class to manage the locking using an RAII pattern instead. + + @see exit, tryEnter, ScopedLock + */ + void enter() const noexcept; + + /** Attempts to lock this critical section without blocking. + + This method behaves identically to CriticalSection::enter, except that the caller thread + does not wait if the lock is currently held by another thread but returns false immediately. + + @returns false if the lock is currently held by another thread, true otherwise. + @see enter + */ + bool tryEnter() const noexcept; + + /** Releases the lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enter() method has been called multiple times by the thread, each + call must be matched by a call to exit() before other threads will be allowed + to take over the lock. + + @see enter, ScopedLock + */ + void exit() const noexcept; + + + //============================================================================== + /** Provides the type of scoped lock to use with a CriticalSection. */ + typedef GenericScopedLock ScopedLockType; + + /** Provides the type of scoped unlocker to use with a CriticalSection. */ + typedef GenericScopedUnlock ScopedUnlockType; + + /** Provides the type of scoped try-locker to use with a CriticalSection. */ + typedef GenericScopedTryLock ScopedTryLockType; + + +private: + //============================================================================== + #if JUCE_WINDOWS + // To avoid including windows.h in the public JUCE headers, we'll just allocate + // a block of memory here that's big enough to be used internally as a windows + // CRITICAL_SECTION structure. + #if JUCE_64BIT + uint8 lock[44]; + #else + uint8 lock[24]; + #endif + #else + mutable pthread_mutex_t lock; + #endif + + JUCE_DECLARE_NON_COPYABLE (CriticalSection) +}; + + +//============================================================================== +/** + A class that can be used in place of a real CriticalSection object, but which + doesn't perform any locking. + + This is currently used by some templated classes, and most compilers should + manage to optimise it out of existence. + + @see CriticalSection, Array, OwnedArray, ReferenceCountedArray +*/ +class JUCE_API DummyCriticalSection +{ +public: + inline DummyCriticalSection() noexcept {} + inline ~DummyCriticalSection() noexcept {} + + inline void enter() const noexcept {} + inline bool tryEnter() const noexcept { return true; } + inline void exit() const noexcept {} + + //============================================================================== + /** A dummy scoped-lock type to use with a dummy critical section. */ + struct ScopedLockType + { + ScopedLockType (const DummyCriticalSection&) noexcept {} + }; + + /** A dummy scoped-unlocker type to use with a dummy critical section. */ + typedef ScopedLockType ScopedUnlockType; + +private: + JUCE_DECLARE_NON_COPYABLE (DummyCriticalSection) +}; + +//============================================================================== +/** + Automatically locks and unlocks a CriticalSection object. + + You can use a ScopedLock as a local variable to provide RAII-based locking of a CriticalSection. + + e.g. @code + + struct MyObject + { + CriticalSection objectLock; + + // assuming that this example function will be called by multiple threads + void foo() + { + const ScopedLock myScopedLock (objectLock); + + // objectLock is now locked.. + + ...do some thread-safe work here... + + // ..and objectLock gets unlocked here, as myScopedLock goes out of + // scope at the end of the block + } + }; + @endcode + + @see CriticalSection, ScopedUnlock +*/ +typedef CriticalSection::ScopedLockType ScopedLock; + +//============================================================================== +/** + Automatically unlocks and re-locks a CriticalSection object. + + This is the reverse of a ScopedLock object - instead of locking the critical + section for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock critical sections that aren't actually locked! + + e.g. @code + + struct MyObject + { + CriticalSection objectLock; + + void foo() + { + { + const ScopedLock myScopedLock (objectLock); + + // objectLock is now locked.. + + { + ScopedUnlock myUnlocker (objectLock); + + // ..and now unlocked.. + } + + // ..and now locked again.. + } + + // ..and finally unlocked. + } + }; + @endcode + + @see CriticalSection, ScopedLock +*/ +typedef CriticalSection::ScopedUnlockType ScopedUnlock; + +//============================================================================== +/** + Automatically tries to lock and unlock a CriticalSection object. + + Use one of these as a local variable to control access to a CriticalSection. + + e.g. @code + + struct MyObject + { + CriticalSection objectLock; + + void foo() + { + const ScopedTryLock myScopedTryLock (objectLock); + + // Unlike using a ScopedLock, this may fail to actually get the lock, so you + // must call the isLocked() method before making any assumptions.. + if (myScopedTryLock.isLocked()) + { + ...safely do some work... + } + else + { + // If we get here, then our attempt at locking failed because another thread had already locked it.. + } + } + }; + @endcode + + @see CriticalSection::tryEnter, ScopedLock, ScopedUnlock, ScopedReadLock +*/ +typedef CriticalSection::ScopedTryLockType ScopedTryLock; + + +#endif // JUCE_CRITICALSECTION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_DynamicLibrary.h b/JuceLibraryCode/modules/juce_core/threads/juce_DynamicLibrary.h new file mode 100644 index 0000000..df6625b --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_DynamicLibrary.h @@ -0,0 +1,85 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_DYNAMICLIBRARY_H_INCLUDED +#define JUCE_DYNAMICLIBRARY_H_INCLUDED + +/** + Handles the opening and closing of DLLs. + + This class can be used to open a DLL and get some function pointers from it. + Since the DLL is freed when this object is deleted, it's handy for managing + library lifetimes using RAII. +*/ +class JUCE_API DynamicLibrary +{ +public: + /** Creates an unopened DynamicLibrary object. + Call open() to actually open one. + */ + DynamicLibrary() noexcept : handle (nullptr) {} + + /** + */ + DynamicLibrary (const String& name) : handle (nullptr) { open (name); } + + /** Destructor. + If a library is currently open, it will be closed when this object is destroyed. + */ + ~DynamicLibrary() { close(); } + + /** Opens a DLL. + The name and the method by which it gets found is of course platform-specific, and + may or may not include a path, depending on the OS. + If a library is already open when this method is called, it will first close the library + before attempting to load the new one. + @returns true if the library was successfully found and opened. + */ + bool open (const String& name); + + /** Releases the currently-open DLL, or has no effect if none was open. */ + void close(); + + /** Tries to find a named function in the currently-open DLL, and returns a pointer to it. + If no library is open, or if the function isn't found, this will return a null pointer. + */ + void* getFunction (const String& functionName) noexcept; + + /** Returns the platform-specific native library handle. + You'll need to cast this to whatever is appropriate for the OS that's in use. + */ + void* getNativeHandle() const noexcept { return handle; } + +private: + void* handle; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DynamicLibrary) +}; + + +#endif // JUCE_DYNAMICLIBRARY_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp new file mode 100644 index 0000000..3475b74 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp @@ -0,0 +1,36 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +HighResolutionTimer::HighResolutionTimer() { pimpl = new Pimpl (*this); } +HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } + +void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); } +void HighResolutionTimer::stopTimer() { pimpl->stop(); } + +bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; } +int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; } diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h new file mode 100644 index 0000000..21022ad --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h @@ -0,0 +1,109 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED +#define JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED + +/** + A high-resolution periodic timer. + + This provides accurately-timed regular callbacks. Unlike the normal Timer + class, this one uses a dedicated thread, not the message thread, so is + far more stable and precise. + + You should only use this class in situations where you really need accuracy, + because unlike the normal Timer class, which is very lightweight and cheap + to start/stop, the HighResolutionTimer will use far more resources, and + starting/stopping it may involve launching and killing threads. + + @see Timer +*/ +class JUCE_API HighResolutionTimer +{ +protected: + /** Creates a HighResolutionTimer. + When created, the timer is stopped, so use startTimer() to get it going. + */ + HighResolutionTimer(); + +public: + /** Destructor. */ + virtual ~HighResolutionTimer(); + + //============================================================================== + /** The user-defined callback routine that actually gets called periodically. + + This will be called on a dedicated timer thread, so make sure your + implementation is thread-safe! + + It's perfectly ok to call startTimer() or stopTimer() from within this + callback to change the subsequent intervals. + */ + virtual void hiResTimerCallback() = 0; + + //============================================================================== + /** Starts the timer and sets the length of interval required. + + If the timer is already started, this will reset its counter, so the + time between calling this method and the next timer callback will not be + less than the interval length passed in. + + @param intervalInMilliseconds the interval to use (any values less than 1 will be + rounded up to 1) + */ + void startTimer (int intervalInMilliseconds); + + /** Stops the timer. + + This method may block while it waits for pending callbacks to complete. Once it + returns, no more callbacks will be made. If it is called from the timer's own thread, + it will cancel the timer after the current callback returns. + */ + void stopTimer(); + + /** Checks if the timer has been started. + @returns true if the timer is running. + */ + bool isTimerRunning() const noexcept; + + /** Returns the timer's interval. + @returns the timer's interval in milliseconds if it's running, or 0 if it's not. + */ + int getTimerInterval() const noexcept; + +private: + struct Pimpl; + friend struct Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) +}; + + +#endif // JUCE_HIGHRESOLUTIONTIMER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_InterProcessLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_InterProcessLock.h new file mode 100644 index 0000000..dc903b0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_InterProcessLock.h @@ -0,0 +1,128 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_INTERPROCESSLOCK_H_INCLUDED +#define JUCE_INTERPROCESSLOCK_H_INCLUDED + + +//============================================================================== +/** + Acts as a critical section which processes can use to block each other. + + @see CriticalSection +*/ +class JUCE_API InterProcessLock +{ +public: + //============================================================================== + /** Creates a lock object. + @param name a name that processes will use to identify this lock object + */ + explicit InterProcessLock (const String& name); + + /** Destructor. + This will also release the lock if it's currently held by this process. + */ + ~InterProcessLock(); + + //============================================================================== + /** Attempts to lock the critical section. + + @param timeOutMillisecs how many milliseconds to wait if the lock is already + held by another process - a value of 0 will return + immediately, negative values will wait forever + @returns true if the lock could be gained within the timeout period, or + false if the timeout expired. + */ + bool enter (int timeOutMillisecs = -1); + + /** Releases the lock if it's currently held by this process. */ + void exit(); + + //============================================================================== + /** + Automatically locks and unlocks an InterProcessLock object. + + This works like a ScopedLock, but using an InterprocessLock rather than + a CriticalSection. + + @see ScopedLock + */ + class ScopedLockType + { + public: + //============================================================================== + /** Creates a scoped lock. + + As soon as it is created, this will lock the InterProcessLock, and + when the ScopedLockType object is deleted, the InterProcessLock will + be unlocked. + + Note that since an InterprocessLock can fail due to errors, you should check + isLocked() to make sure that the lock was successful before using it. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + explicit ScopedLockType (InterProcessLock& l) : ipLock (l) { lockWasSuccessful = l.enter(); } + + /** Destructor. + + The InterProcessLock will be unlocked when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedLockType() { ipLock.exit(); } + + /** Returns true if the InterProcessLock was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + + private: + //============================================================================== + InterProcessLock& ipLock; + bool lockWasSuccessful; + + JUCE_DECLARE_NON_COPYABLE (ScopedLockType) + }; + +private: + //============================================================================== + class Pimpl; + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + + CriticalSection lock; + String name; + + JUCE_DECLARE_NON_COPYABLE (InterProcessLock) +}; + + +#endif // JUCE_INTERPROCESSLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_Process.h b/JuceLibraryCode/modules/juce_core/threads/juce_Process.h new file mode 100644 index 0000000..f3efd66 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_Process.h @@ -0,0 +1,153 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PROCESS_H_INCLUDED +#define JUCE_PROCESS_H_INCLUDED + + +//============================================================================== +/** Represents the current executable's process. + + This contains methods for controlling the current application at the + process-level. + + @see Thread, JUCEApplicationBase +*/ +class JUCE_API Process +{ +public: + //============================================================================== + enum ProcessPriority + { + LowPriority = 0, + NormalPriority = 1, + HighPriority = 2, + RealtimePriority = 3 + }; + + /** Changes the current process's priority. + + @param priority the process priority, where + 0=low, 1=normal, 2=high, 3=realtime + */ + static void JUCE_CALLTYPE setPriority (const ProcessPriority priority); + + /** Kills the current process immediately. + + This is an emergency process terminator that kills the application + immediately - it's intended only for use only when something goes + horribly wrong. + + @see JUCEApplicationBase::quit + */ + static void JUCE_CALLTYPE terminate(); + + //============================================================================== + /** Returns true if this application process is the one that the user is + currently using. + */ + static bool JUCE_CALLTYPE isForegroundProcess(); + + /** Attempts to make the current process the active one. + (This is not possible on some platforms). + */ + static void JUCE_CALLTYPE makeForegroundProcess(); + + /** Hides the application (on an OS that supports this, e.g. OSX) */ + static void JUCE_CALLTYPE hide(); + + //============================================================================== + /** Raises the current process's privilege level. + + Does nothing if this isn't supported by the current OS, or if process + privilege level is fixed. + */ + static void JUCE_CALLTYPE raisePrivilege(); + + /** Lowers the current process's privilege level. + + Does nothing if this isn't supported by the current OS, or if process + privilege level is fixed. + */ + static void JUCE_CALLTYPE lowerPrivilege(); + + //============================================================================== + /** Returns true if this process is being hosted by a debugger. */ + static bool JUCE_CALLTYPE isRunningUnderDebugger(); + + + //============================================================================== + /** Tries to launch the OS's default reader application for a given file or URL. */ + static bool JUCE_CALLTYPE openDocument (const String& documentURL, const String& parameters); + + /** Tries to launch the OS's default email application to let the user create a message. */ + static bool JUCE_CALLTYPE openEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach); + + #if JUCE_WINDOWS || DOXYGEN + //============================================================================== + /** WINDOWS ONLY - This returns the HINSTANCE of the current module. + + The return type is a void* to avoid being dependent on windows.h - just cast + it to a HINSTANCE to use it. + + In a normal JUCE application, this will be automatically set to the module + handle of the executable. + + If you've built a DLL and plan to use any JUCE messaging or windowing classes, + you'll need to make sure you call the setCurrentModuleInstanceHandle() + to provide the correct module handle in your DllMain() function, because + the system relies on the correct instance handle when opening windows. + */ + static void* JUCE_CALLTYPE getCurrentModuleInstanceHandle() noexcept; + + /** WINDOWS ONLY - Sets a new module handle to be used by the library. + + The parameter type is a void* to avoid being dependent on windows.h, but it actually + expects a HINSTANCE value. + + @see getCurrentModuleInstanceHandle() + */ + static void JUCE_CALLTYPE setCurrentModuleInstanceHandle (void* newHandle) noexcept; + #endif + + #if JUCE_MAC || DOXYGEN + //============================================================================== + /** OSX ONLY - Shows or hides the OSX dock icon for this app. */ + static void setDockIconVisible (bool isVisible); + #endif + +private: + Process(); + JUCE_DECLARE_NON_COPYABLE (Process) +}; + + +#endif // JUCE_PROCESS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.cpp new file mode 100644 index 0000000..a0821b4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.cpp @@ -0,0 +1,150 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +ReadWriteLock::ReadWriteLock() noexcept + : numWaitingWriters (0), + numWriters (0), + writerThreadId (0) +{ + readerThreads.ensureStorageAllocated (16); +} + +ReadWriteLock::~ReadWriteLock() noexcept +{ + jassert (readerThreads.size() == 0); + jassert (numWriters == 0); +} + +//============================================================================== +void ReadWriteLock::enterRead() const noexcept +{ + while (! tryEnterRead()) + waitEvent.wait (100); +} + +bool ReadWriteLock::tryEnterRead() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + const SpinLock::ScopedLockType sl (accessLock); + + for (int i = 0; i < readerThreads.size(); ++i) + { + ThreadRecursionCount& trc = readerThreads.getReference(i); + + if (trc.threadID == threadId) + { + trc.count++; + return true; + } + } + + if (numWriters + numWaitingWriters == 0 + || (threadId == writerThreadId && numWriters > 0)) + { + ThreadRecursionCount trc = { threadId, 1 }; + readerThreads.add (trc); + return true; + } + + return false; +} + +void ReadWriteLock::exitRead() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + const SpinLock::ScopedLockType sl (accessLock); + + for (int i = 0; i < readerThreads.size(); ++i) + { + ThreadRecursionCount& trc = readerThreads.getReference(i); + + if (trc.threadID == threadId) + { + if (--(trc.count) == 0) + { + readerThreads.remove (i); + waitEvent.signal(); + } + + return; + } + } + + jassertfalse; // unlocking a lock that wasn't locked.. +} + +//============================================================================== +void ReadWriteLock::enterWrite() const noexcept +{ + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + const SpinLock::ScopedLockType sl (accessLock); + + while (! tryEnterWriteInternal (threadId)) + { + ++numWaitingWriters; + accessLock.exit(); + waitEvent.wait (100); + accessLock.enter(); + --numWaitingWriters; + } +} + +bool ReadWriteLock::tryEnterWrite() const noexcept +{ + const SpinLock::ScopedLockType sl (accessLock); + return tryEnterWriteInternal (Thread::getCurrentThreadId()); +} + +bool ReadWriteLock::tryEnterWriteInternal (Thread::ThreadID threadId) const noexcept +{ + if (readerThreads.size() + numWriters == 0 + || threadId == writerThreadId + || (readerThreads.size() == 1 && readerThreads.getReference(0).threadID == threadId)) + { + writerThreadId = threadId; + ++numWriters; + return true; + } + + return false; +} + +void ReadWriteLock::exitWrite() const noexcept +{ + const SpinLock::ScopedLockType sl (accessLock); + + // check this thread actually had the lock.. + jassert (numWriters > 0 && writerThreadId == Thread::getCurrentThreadId()); + + if (--numWriters == 0) + { + writerThreadId = 0; + waitEvent.signal(); + } +} diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.h new file mode 100644 index 0000000..c41d258 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ReadWriteLock.h @@ -0,0 +1,152 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_READWRITELOCK_H_INCLUDED +#define JUCE_READWRITELOCK_H_INCLUDED + + +//============================================================================== +/** + A critical section that allows multiple simultaneous readers. + + Features of this type of lock are: + + - Multiple readers can hold the lock at the same time, but only one writer + can hold it at once. + - Writers trying to gain the lock will be blocked until all readers and writers + have released it + - Readers trying to gain the lock while a writer is waiting to acquire it will be + blocked until the writer has obtained and released it + - If a thread already has a read lock and tries to obtain a write lock, it will succeed if + there are no other readers + - If a thread already has the write lock and tries to obtain a read lock, this will succeed. + - Recursive locking is supported. + + @see ScopedReadLock, ScopedWriteLock, CriticalSection +*/ +class JUCE_API ReadWriteLock +{ +public: + //============================================================================== + /** + Creates a ReadWriteLock object. + */ + ReadWriteLock() noexcept; + + /** Destructor. + If the object is deleted whilst locked, any subsequent behaviour is undefined. + */ + ~ReadWriteLock() noexcept; + + //============================================================================== + /** Locks this object for reading. + + Multiple threads can simultaneously lock the object for reading, but if another + thread has it locked for writing, then this will block until it releases the lock. + + @see exitRead, ScopedReadLock + */ + void enterRead() const noexcept; + + /** Tries to lock this object for reading. + + Multiple threads can simultaneously lock the object for reading, but if another + thread has it locked for writing, then this will fail and return false. + + @returns true if the lock is successfully gained. + @see exitRead, ScopedReadLock + */ + bool tryEnterRead() const noexcept; + + /** Releases the read-lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enterRead() method has been called multiple times by the thread, each + call must be matched by a call to exitRead() before other threads will be allowed + to take over the lock. + + @see enterRead, ScopedReadLock + */ + void exitRead() const noexcept; + + //============================================================================== + /** Locks this object for writing. + + This will block until any other threads that have it locked for reading or + writing have released their lock. + + @see exitWrite, ScopedWriteLock + */ + void enterWrite() const noexcept; + + /** Tries to lock this object for writing. + + This is like enterWrite(), but doesn't block - it returns true if it manages + to obtain the lock. + + @returns true if the lock is successfully gained. + @see enterWrite + */ + bool tryEnterWrite() const noexcept; + + /** Releases the write-lock. + + If the caller thread hasn't got the lock, this can have unpredictable results. + + If the enterWrite() method has been called multiple times by the thread, each + call must be matched by a call to exit() before other threads will be allowed + to take over the lock. + + @see enterWrite, ScopedWriteLock + */ + void exitWrite() const noexcept; + + +private: + //============================================================================== + SpinLock accessLock; + WaitableEvent waitEvent; + mutable int numWaitingWriters, numWriters; + mutable Thread::ThreadID writerThreadId; + + struct ThreadRecursionCount + { + Thread::ThreadID threadID; + int count; + }; + + mutable Array readerThreads; + + bool tryEnterWriteInternal (Thread::ThreadID) const noexcept; + + JUCE_DECLARE_NON_COPYABLE (ReadWriteLock) +}; + + +#endif // JUCE_READWRITELOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ScopedLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedLock.h new file mode 100644 index 0000000..442551a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedLock.h @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDLOCK_H_INCLUDED +#define JUCE_SCOPEDLOCK_H_INCLUDED + + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ...do some stuff... + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedUnlock, CriticalSection, SpinLock, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedLock +{ +public: + //============================================================================== + /** Creates a GenericScopedLock. + + As soon as it is created, this will acquire the lock, and when the GenericScopedLock + object is deleted, the lock will be released. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedLock (const LockType& lock) noexcept : lock_ (lock) { lock.enter(); } + + /** Destructor. + The lock will be released when the destructor is called. + Make sure this object is created and deleted by the same thread, otherwise there are + no guarantees what will happen! + */ + inline ~GenericScopedLock() noexcept { lock_.exit(); } + +private: + //============================================================================== + const LockType& lock_; + + JUCE_DECLARE_NON_COPYABLE (GenericScopedLock) +}; + + +//============================================================================== +/** + Automatically unlocks and re-locks a mutex object. + + This is the reverse of a GenericScopedLock object - instead of locking the mutex + for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock mutexes that aren't actually locked! + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ... do some stuff with it locked .. + + while (xyz) + { + ... do some stuff with it locked .. + + const GenericScopedUnlock unlocker (myCriticalSection); + + // myCriticalSection is now unlocked for the remainder of this block, + // and re-locked at the end. + + ...do some stuff with it unlocked ... + } + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedLock, CriticalSection, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedUnlock +{ +public: + //============================================================================== + /** Creates a GenericScopedUnlock. + + As soon as it is created, this will unlock the CriticalSection, and + when the ScopedLock object is deleted, the CriticalSection will + be re-locked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedUnlock (const LockType& lock) noexcept : lock_ (lock) { lock.exit(); } + + /** Destructor. + + The CriticalSection will be unlocked when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedUnlock() noexcept { lock_.enter(); } + + +private: + //============================================================================== + const LockType& lock_; + + JUCE_DECLARE_NON_COPYABLE (GenericScopedUnlock) +}; + + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedTryLock myScopedTryLock (myCriticalSection); + + // Unlike using a ScopedLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action.. + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because another thread had already locked it.. + } + + // myCriticalSection gets unlocked here (if it was locked) + } + @endcode + + @see CriticalSection::tryEnter, GenericScopedLock, GenericScopedUnlock +*/ +template +class GenericScopedTryLock +{ +public: + //============================================================================== + /** Creates a GenericScopedTryLock. + + As soon as it is created, this will attempt to acquire the lock, and when the + GenericScopedTryLock is deleted, the lock will be released (if the lock was + successfully acquired). + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedTryLock (const LockType& lock) noexcept + : lock_ (lock), lockWasSuccessful (lock.tryEnter()) {} + + /** Destructor. + + The mutex will be unlocked (if it had been successfully locked) when the + destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedTryLock() noexcept { if (lockWasSuccessful) lock_.exit(); } + + /** Returns true if the mutex was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + +private: + //============================================================================== + const LockType& lock_; + const bool lockWasSuccessful; + + JUCE_DECLARE_NON_COPYABLE (GenericScopedTryLock) +}; + + +#endif // JUCE_SCOPEDLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ScopedReadLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedReadLock.h new file mode 100644 index 0000000..107e838 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedReadLock.h @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDREADLOCK_H_INCLUDED +#define JUCE_SCOPEDREADLOCK_H_INCLUDED + + +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedReadLock myScopedLock (myLock); + // myLock is now locked + + ...do some stuff... + + // myLock gets unlocked here. + } + @endcode + + @see ReadWriteLock, ScopedWriteLock +*/ +class JUCE_API ScopedReadLock +{ +public: + //============================================================================== + /** Creates a ScopedReadLock. + + As soon as it is created, this will call ReadWriteLock::enterRead(), and + when the ScopedReadLock object is deleted, the ReadWriteLock will + be unlocked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit ScopedReadLock (const ReadWriteLock& lock) noexcept : lock_ (lock) { lock.enterRead(); } + + /** Destructor. + + The ReadWriteLock's exitRead() method will be called when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedReadLock() noexcept { lock_.exitRead(); } + + +private: + //============================================================================== + const ReadWriteLock& lock_; + + JUCE_DECLARE_NON_COPYABLE (ScopedReadLock) +}; + + +#endif // JUCE_SCOPEDREADLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ScopedWriteLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedWriteLock.h new file mode 100644 index 0000000..44e3198 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ScopedWriteLock.h @@ -0,0 +1,90 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDWRITELOCK_H_INCLUDED +#define JUCE_SCOPEDWRITELOCK_H_INCLUDED + + +//============================================================================== +/** + Automatically locks and unlocks a ReadWriteLock object. + + Use one of these as a local variable to control access to a ReadWriteLock. + + e.g. @code + + ReadWriteLock myLock; + + for (;;) + { + const ScopedWriteLock myScopedLock (myLock); + // myLock is now locked + + ...do some stuff... + + // myLock gets unlocked here. + } + @endcode + + @see ReadWriteLock, ScopedReadLock +*/ +class JUCE_API ScopedWriteLock +{ +public: + //============================================================================== + /** Creates a ScopedWriteLock. + + As soon as it is created, this will call ReadWriteLock::enterWrite(), and + when the ScopedWriteLock object is deleted, the ReadWriteLock will + be unlocked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit ScopedWriteLock (const ReadWriteLock& lock) noexcept : lock_ (lock) { lock.enterWrite(); } + + /** Destructor. + + The ReadWriteLock's exitWrite() method will be called when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~ScopedWriteLock() noexcept { lock_.exitWrite(); } + + +private: + //============================================================================== + const ReadWriteLock& lock_; + + JUCE_DECLARE_NON_COPYABLE (ScopedWriteLock) +}; + + +#endif // JUCE_SCOPEDWRITELOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_SpinLock.h b/JuceLibraryCode/modules/juce_core/threads/juce_SpinLock.h new file mode 100644 index 0000000..664cc3c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_SpinLock.h @@ -0,0 +1,91 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_SPINLOCK_H_INCLUDED +#define JUCE_SPINLOCK_H_INCLUDED + + +//============================================================================== +/** + A simple spin-lock class that can be used as a simple, low-overhead mutex for + uncontended situations. + + Note that unlike a CriticalSection, this type of lock is not re-entrant, and may + be less efficient when used it a highly contended situation, but it's very small and + requires almost no initialisation. + It's most appropriate for simple situations where you're only going to hold the + lock for a very brief time. + + @see CriticalSection +*/ +class JUCE_API SpinLock +{ +public: + inline SpinLock() noexcept {} + inline ~SpinLock() noexcept {} + + /** Acquires the lock. + This will block until the lock has been successfully acquired by this thread. + Note that a SpinLock is NOT re-entrant, and is not smart enough to know whether the + caller thread already has the lock - so if a thread tries to acquire a lock that it + already holds, this method will never return! + + It's strongly recommended that you never call this method directly - instead use the + ScopedLockType class to manage the locking using an RAII pattern instead. + */ + void enter() const noexcept; + + /** Attempts to acquire the lock, returning true if this was successful. */ + inline bool tryEnter() const noexcept + { + return lock.compareAndSetBool (1, 0); + } + + /** Releases the lock. */ + inline void exit() const noexcept + { + jassert (lock.value == 1); // Agh! Releasing a lock that isn't currently held! + lock = 0; + } + + //============================================================================== + /** Provides the type of scoped lock to use for locking a SpinLock. */ + typedef GenericScopedLock ScopedLockType; + + /** Provides the type of scoped unlocker to use with a SpinLock. */ + typedef GenericScopedUnlock ScopedUnlockType; + +private: + //============================================================================== + mutable Atomic lock; + + JUCE_DECLARE_NON_COPYABLE (SpinLock) +}; + + +#endif // JUCE_SPINLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp new file mode 100644 index 0000000..3d1ff0a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp @@ -0,0 +1,371 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +Thread::Thread (const String& threadName_) + : threadName (threadName_), + threadHandle (nullptr), + threadId (0), + threadPriority (5), + affinityMask (0), + shouldExit (false) +{ +} + +Thread::~Thread() +{ + /* If your thread class's destructor has been called without first stopping the thread, that + means that this partially destructed object is still performing some work - and that's + probably a Bad Thing! + + To avoid this type of nastiness, always make sure you call stopThread() before or during + your subclass's destructor. + */ + jassert (! isThreadRunning()); + + stopThread (-1); +} + +//============================================================================== +// Use a ref-counted object to hold this shared data, so that it can outlive its static +// shared pointer when threads are still running during static shutdown. +struct CurrentThreadHolder : public ReferenceCountedObject +{ + CurrentThreadHolder() noexcept {} + + typedef ReferenceCountedObjectPtr Ptr; + ThreadLocalValue value; + + JUCE_DECLARE_NON_COPYABLE (CurrentThreadHolder) +}; + +static char currentThreadHolderLock [sizeof (SpinLock)]; // (statically initialised to zeros). + +static SpinLock* castToSpinLockWithoutAliasingWarning (void* s) +{ + return static_cast (s); +} + +static CurrentThreadHolder::Ptr getCurrentThreadHolder() +{ + static CurrentThreadHolder::Ptr currentThreadHolder; + SpinLock::ScopedLockType lock (*castToSpinLockWithoutAliasingWarning (currentThreadHolderLock)); + + if (currentThreadHolder == nullptr) + currentThreadHolder = new CurrentThreadHolder(); + + return currentThreadHolder; +} + +void Thread::threadEntryPoint() +{ + const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder()); + currentThreadHolder->value = this; + + JUCE_TRY + { + if (threadName.isNotEmpty()) + setCurrentThreadName (threadName); + + if (startSuspensionEvent.wait (10000)) + { + jassert (getCurrentThreadId() == threadId); + + if (affinityMask != 0) + setCurrentThreadAffinityMask (affinityMask); + + run(); + } + } + JUCE_CATCH_ALL_ASSERT + + currentThreadHolder->value.releaseCurrentThreadStorage(); + closeThreadHandle(); +} + +// used to wrap the incoming call from the platform-specific code +void JUCE_API juce_threadEntryPoint (void* userData) +{ + static_cast (userData)->threadEntryPoint(); +} + +//============================================================================== +void Thread::startThread() +{ + const ScopedLock sl (startStopLock); + + shouldExit = false; + + if (threadHandle == nullptr) + { + launchThread(); + setThreadPriority (threadHandle, threadPriority); + startSuspensionEvent.signal(); + } +} + +void Thread::startThread (const int priority) +{ + const ScopedLock sl (startStopLock); + + if (threadHandle == nullptr) + { + threadPriority = priority; + startThread(); + } + else + { + setPriority (priority); + } +} + +bool Thread::isThreadRunning() const +{ + return threadHandle != nullptr; +} + +Thread* JUCE_CALLTYPE Thread::getCurrentThread() +{ + return getCurrentThreadHolder()->value.get(); +} + +//============================================================================== +void Thread::signalThreadShouldExit() +{ + shouldExit = true; +} + +bool Thread::waitForThreadToExit (const int timeOutMilliseconds) const +{ + // Doh! So how exactly do you expect this thread to wait for itself to stop?? + jassert (getThreadId() != getCurrentThreadId() || getCurrentThreadId() == 0); + + const uint32 timeoutEnd = Time::getMillisecondCounter() + (uint32) timeOutMilliseconds; + + while (isThreadRunning()) + { + if (timeOutMilliseconds >= 0 && Time::getMillisecondCounter() > timeoutEnd) + return false; + + sleep (2); + } + + return true; +} + +bool Thread::stopThread (const int timeOutMilliseconds) +{ + // agh! You can't stop the thread that's calling this method! How on earth + // would that work?? + jassert (getCurrentThreadId() != getThreadId()); + + const ScopedLock sl (startStopLock); + + if (isThreadRunning()) + { + signalThreadShouldExit(); + notify(); + + if (timeOutMilliseconds != 0) + waitForThreadToExit (timeOutMilliseconds); + + if (isThreadRunning()) + { + // very bad karma if this point is reached, as there are bound to be + // locks and events left in silly states when a thread is killed by force.. + jassertfalse; + Logger::writeToLog ("!! killing thread by force !!"); + + killThread(); + + threadHandle = nullptr; + threadId = 0; + return false; + } + } + + return true; +} + +//============================================================================== +bool Thread::setPriority (const int newPriority) +{ + // NB: deadlock possible if you try to set the thread prio from the thread itself, + // so using setCurrentThreadPriority instead in that case. + if (getCurrentThreadId() == getThreadId()) + return setCurrentThreadPriority (newPriority); + + const ScopedLock sl (startStopLock); + + if ((! isThreadRunning()) || setThreadPriority (threadHandle, newPriority)) + { + threadPriority = newPriority; + return true; + } + + return false; +} + +bool Thread::setCurrentThreadPriority (const int newPriority) +{ + return setThreadPriority (0, newPriority); +} + +void Thread::setAffinityMask (const uint32 newAffinityMask) +{ + affinityMask = newAffinityMask; +} + +//============================================================================== +bool Thread::wait (const int timeOutMilliseconds) const +{ + return defaultEvent.wait (timeOutMilliseconds); +} + +void Thread::notify() const +{ + defaultEvent.signal(); +} + +//============================================================================== +void SpinLock::enter() const noexcept +{ + if (! tryEnter()) + { + for (int i = 20; --i >= 0;) + if (tryEnter()) + return; + + while (! tryEnter()) + Thread::yield(); + } +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class AtomicTests : public UnitTest +{ +public: + AtomicTests() : UnitTest ("Atomics") {} + + void runTest() + { + beginTest ("Misc"); + + char a1[7]; + expect (numElementsInArray(a1) == 7); + int a2[3]; + expect (numElementsInArray(a2) == 3); + + expect (ByteOrder::swap ((uint16) 0x1122) == 0x2211); + expect (ByteOrder::swap ((uint32) 0x11223344) == 0x44332211); + expect (ByteOrder::swap ((uint64) 0x1122334455667788ULL) == 0x8877665544332211LL); + + beginTest ("Atomic int"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic unsigned int"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic int32"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic uint32"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic long"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic void*"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic int*"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic float"); + AtomicTester ::testFloat (*this); + #if ! JUCE_64BIT_ATOMICS_UNAVAILABLE // 64-bit intrinsics aren't available on some old platforms + beginTest ("Atomic int64"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic uint64"); + AtomicTester ::testInteger (*this); + beginTest ("Atomic double"); + AtomicTester ::testFloat (*this); + #endif + } + + template + class AtomicTester + { + public: + AtomicTester() {} + + static void testInteger (UnitTest& test) + { + Atomic a, b; + a.set ((Type) 10); + test.expect (a.value == (Type) 10); + test.expect (a.get() == (Type) 10); + a += (Type) 15; + test.expect (a.get() == (Type) 25); + a.memoryBarrier(); + a -= (Type) 5; + test.expect (a.get() == (Type) 20); + test.expect (++a == (Type) 21); + ++a; + test.expect (--a == (Type) 21); + test.expect (a.get() == (Type) 21); + a.memoryBarrier(); + + testFloat (test); + } + + static void testFloat (UnitTest& test) + { + Atomic a, b; + a = (Type) 21; + a.memoryBarrier(); + + /* These are some simple test cases to check the atomics - let me know + if any of these assertions fail on your system! + */ + test.expect (a.get() == (Type) 21); + test.expect (a.compareAndSetValue ((Type) 100, (Type) 50) == (Type) 21); + test.expect (a.get() == (Type) 21); + test.expect (a.compareAndSetValue ((Type) 101, a.get()) == (Type) 21); + test.expect (a.get() == (Type) 101); + test.expect (! a.compareAndSetBool ((Type) 300, (Type) 200)); + test.expect (a.get() == (Type) 101); + test.expect (a.compareAndSetBool ((Type) 200, a.get())); + test.expect (a.get() == (Type) 200); + + test.expect (a.exchange ((Type) 300) == (Type) 200); + test.expect (a.get() == (Type) 300); + + b = a; + test.expect (b.get() == a.get()); + } + }; +}; + +static AtomicTests atomicUnitTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h new file mode 100644 index 0000000..aef96b7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h @@ -0,0 +1,289 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREAD_H_INCLUDED +#define JUCE_THREAD_H_INCLUDED + + +//============================================================================== +/** + Encapsulates a thread. + + Subclasses derive from Thread and implement the run() method, in which they + do their business. The thread can then be started with the startThread() method + and controlled with various other methods. + + This class also contains some thread-related static methods, such + as sleep(), yield(), getCurrentThreadId() etc. + + @see CriticalSection, WaitableEvent, Process, ThreadWithProgressWindow, + MessageManagerLock +*/ +class JUCE_API Thread +{ +public: + //============================================================================== + /** + Creates a thread. + + When first created, the thread is not running. Use the startThread() + method to start it. + */ + explicit Thread (const String& threadName); + + /** Destructor. + + You must never attempt to delete a Thread object while it's still running - + always call stopThread() and make sure your thread has stopped before deleting + the object. Failing to do so will throw an assertion, and put you firmly into + undefined behaviour territory. + */ + virtual ~Thread(); + + //============================================================================== + /** Must be implemented to perform the thread's actual code. + + Remember that the thread must regularly check the threadShouldExit() + method whilst running, and if this returns true it should return from + the run() method as soon as possible to avoid being forcibly killed. + + @see threadShouldExit, startThread + */ + virtual void run() = 0; + + //============================================================================== + // Thread control functions.. + + /** Starts the thread running. + + This will cause the thread's run() method to be called by a new thread. + If this thread is already running, startThread() won't do anything. + + @see stopThread + */ + void startThread(); + + /** Starts the thread with a given priority. + + Launches the thread with a given priority, where 0 = lowest, 10 = highest. + If the thread is already running, its priority will be changed. + + @see startThread, setPriority + */ + void startThread (int priority); + + /** Attempts to stop the thread running. + + This method will cause the threadShouldExit() method to return true + and call notify() in case the thread is currently waiting. + + Hopefully the thread will then respond to this by exiting cleanly, and + the stopThread method will wait for a given time-period for this to + happen. + + If the thread is stuck and fails to respond after the time-out, it gets + forcibly killed, which is a very bad thing to happen, as it could still + be holding locks, etc. which are needed by other parts of your program. + + @param timeOutMilliseconds The number of milliseconds to wait for the + thread to finish before killing it by force. A negative + value in here will wait forever. + @returns true if the thread was cleanly stopped before the timeout, or false + if it had to be killed by force. + @see signalThreadShouldExit, threadShouldExit, waitForThreadToExit, isThreadRunning + */ + bool stopThread (int timeOutMilliseconds); + + //============================================================================== + /** Returns true if the thread is currently active */ + bool isThreadRunning() const; + + /** Sets a flag to tell the thread it should stop. + + Calling this means that the threadShouldExit() method will then return true. + The thread should be regularly checking this to see whether it should exit. + + If your thread makes use of wait(), you might want to call notify() after calling + this method, to interrupt any waits that might be in progress, and allow it + to reach a point where it can exit. + + @see threadShouldExit + @see waitForThreadToExit + */ + void signalThreadShouldExit(); + + /** Checks whether the thread has been told to stop running. + + Threads need to check this regularly, and if it returns true, they should + return from their run() method at the first possible opportunity. + + @see signalThreadShouldExit + */ + inline bool threadShouldExit() const { return shouldExit; } + + /** Waits for the thread to stop. + + This will waits until isThreadRunning() is false or until a timeout expires. + + @param timeOutMilliseconds the time to wait, in milliseconds. If this value + is less than zero, it will wait forever. + @returns true if the thread exits, or false if the timeout expires first. + */ + bool waitForThreadToExit (int timeOutMilliseconds) const; + + //============================================================================== + /** Changes the thread's priority. + May return false if for some reason the priority can't be changed. + + @param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority + of 5 is normal. + */ + bool setPriority (int priority); + + /** Changes the priority of the caller thread. + + Similar to setPriority(), but this static method acts on the caller thread. + May return false if for some reason the priority can't be changed. + + @see setPriority + */ + static bool setCurrentThreadPriority (int priority); + + //============================================================================== + /** Sets the affinity mask for the thread. + + This will only have an effect next time the thread is started - i.e. if the + thread is already running when called, it'll have no effect. + + @see setCurrentThreadAffinityMask + */ + void setAffinityMask (uint32 affinityMask); + + /** Changes the affinity mask for the caller thread. + This will change the affinity mask for the thread that calls this static method. + @see setAffinityMask + */ + static void JUCE_CALLTYPE setCurrentThreadAffinityMask (uint32 affinityMask); + + //============================================================================== + // this can be called from any thread that needs to pause.. + static void JUCE_CALLTYPE sleep (int milliseconds); + + /** Yields the calling thread's current time-slot. */ + static void JUCE_CALLTYPE yield(); + + //============================================================================== + /** Makes the thread wait for a notification. + + This puts the thread to sleep until either the timeout period expires, or + another thread calls the notify() method to wake it up. + + A negative time-out value means that the method will wait indefinitely. + + @returns true if the event has been signalled, false if the timeout expires. + */ + bool wait (int timeOutMilliseconds) const; + + /** Wakes up the thread. + + If the thread has called the wait() method, this will wake it up. + + @see wait + */ + void notify() const; + + //============================================================================== + /** A value type used for thread IDs. + @see getCurrentThreadId(), getThreadId() + */ + typedef void* ThreadID; + + /** Returns an id that identifies the caller thread. + + To find the ID of a particular thread object, use getThreadId(). + + @returns a unique identifier that identifies the calling thread. + @see getThreadId + */ + static ThreadID JUCE_CALLTYPE getCurrentThreadId(); + + /** Finds the thread object that is currently running. + + Note that the main UI thread (or other non-Juce threads) don't have a Thread + object associated with them, so this will return 0. + */ + static Thread* JUCE_CALLTYPE getCurrentThread(); + + /** Returns the ID of this thread. + + That means the ID of this thread object - not of the thread that's calling the method. + + This can change when the thread is started and stopped, and will be invalid if the + thread's not actually running. + + @see getCurrentThreadId + */ + ThreadID getThreadId() const noexcept { return threadId; } + + /** Returns the name of the thread. + + This is the name that gets set in the constructor. + */ + const String& getThreadName() const { return threadName; } + + /** Changes the name of the caller thread. + Different OSes may place different length or content limits on this name. + */ + static void JUCE_CALLTYPE setCurrentThreadName (const String& newThreadName); + + +private: + //============================================================================== + const String threadName; + void* volatile threadHandle; + ThreadID threadId; + CriticalSection startStopLock; + WaitableEvent startSuspensionEvent, defaultEvent; + int threadPriority; + uint32 affinityMask; + bool volatile shouldExit; + + #ifndef DOXYGEN + friend void JUCE_API juce_threadEntryPoint (void*); + #endif + + void launchThread(); + void closeThreadHandle(); + void killThread(); + void threadEntryPoint(); + static bool setThreadPriority (void*, int); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) +}; + +#endif // JUCE_THREAD_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadLocalValue.h b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadLocalValue.h new file mode 100644 index 0000000..bd5c808 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadLocalValue.h @@ -0,0 +1,198 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREADLOCALVALUE_H_INCLUDED +#define JUCE_THREADLOCALVALUE_H_INCLUDED + +// (NB: on win32, native thread-locals aren't possible in a dynamically loaded DLL in XP). +#if ! ((JUCE_MSVC && (JUCE_64BIT || ! defined (JucePlugin_PluginCode))) \ + || (JUCE_MAC && JUCE_CLANG && defined (MAC_OS_X_VERSION_10_7) \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7)) + #define JUCE_NO_COMPILER_THREAD_LOCAL 1 +#endif + +//============================================================================== +/** + Provides cross-platform support for thread-local objects. + + This class holds an internal list of objects of the templated type, keeping + an instance for each thread that requests one. The first time a thread attempts + to access its value, an object is created and added to the list for that thread. + + Typically, you'll probably want to create a static instance of a ThreadLocalValue + object, or hold one within a singleton. + + The templated class for your value must be a primitive type, or a simple POD struct. + + When a thread no longer needs to use its value, it can call releaseCurrentThreadStorage() + to allow the storage to be re-used by another thread. If a thread exits without calling + this method, the object storage will be left allocated until the ThreadLocalValue object + is deleted. +*/ +template +class ThreadLocalValue +{ +public: + /** */ + ThreadLocalValue() noexcept + { + } + + /** Destructor. + When this object is deleted, all the value objects for all threads will be deleted. + */ + ~ThreadLocalValue() + { + #if JUCE_NO_COMPILER_THREAD_LOCAL + for (ObjectHolder* o = first.value; o != nullptr;) + { + ObjectHolder* const next = o->next; + delete o; + o = next; + } + #endif + } + + /** Returns a reference to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type& operator*() const noexcept { return get(); } + + /** Returns a pointer to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + operator Type*() const noexcept { return &get(); } + + /** Accesses a method or field of the value object. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type* operator->() const noexcept { return &get(); } + + /** Assigns a new value to the thread-local object. */ + ThreadLocalValue& operator= (const Type& newValue) { get() = newValue; return *this; } + + /** Returns a reference to this thread's instance of the value. + Note that the first time a thread tries to access the value, an instance of the + value object will be created - so if your value's class has a non-trivial + constructor, be aware that this method could invoke it. + */ + Type& get() const noexcept + { + #if JUCE_NO_COMPILER_THREAD_LOCAL + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + if (o->threadId == threadId) + return o->object; + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + { + if (o->threadId == nullptr) + { + { + SpinLock::ScopedLockType sl (lock); + + if (o->threadId != nullptr) + continue; + + o->threadId = threadId; + } + + o->object = Type(); + return o->object; + } + } + + ObjectHolder* const newObject = new ObjectHolder (threadId); + + do + { + newObject->next = first.get(); + } + while (! first.compareAndSetBool (newObject, newObject->next)); + + return newObject->object; + #elif JUCE_MAC + static __thread Type object; + return object; + #elif JUCE_MSVC + static __declspec(thread) Type object; + return object; + #endif + } + + /** Called by a thread before it terminates, to allow this class to release + any storage associated with the thread. + */ + void releaseCurrentThreadStorage() + { + #if JUCE_NO_COMPILER_THREAD_LOCAL + const Thread::ThreadID threadId = Thread::getCurrentThreadId(); + + for (ObjectHolder* o = first.get(); o != nullptr; o = o->next) + { + if (o->threadId == threadId) + { + SpinLock::ScopedLockType sl (lock); + o->threadId = nullptr; + } + } + #endif + } + +private: + //============================================================================== + #if JUCE_NO_COMPILER_THREAD_LOCAL + struct ObjectHolder + { + ObjectHolder (const Thread::ThreadID& tid) + : threadId (tid), next (nullptr), object() + {} + + Thread::ThreadID threadId; + ObjectHolder* next; + Type object; + + JUCE_DECLARE_NON_COPYABLE (ObjectHolder) + }; + + mutable Atomic first; + SpinLock lock; + #endif + + JUCE_DECLARE_NON_COPYABLE (ThreadLocalValue) +}; + + +#endif // JUCE_THREADLOCALVALUE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp new file mode 100644 index 0000000..e6b0b9f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp @@ -0,0 +1,384 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class ThreadPool::ThreadPoolThread : public Thread +{ +public: + ThreadPoolThread (ThreadPool& p) + : Thread ("Pool"), currentJob (nullptr), pool (p) + { + } + + void run() override + { + while (! threadShouldExit()) + if (! pool.runNextJob (*this)) + wait (500); + } + + ThreadPoolJob* volatile currentJob; + ThreadPool& pool; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) +}; + +//============================================================================== +ThreadPoolJob::ThreadPoolJob (const String& name) + : jobName (name), pool (nullptr), + shouldStop (false), isActive (false), shouldBeDeleted (false) +{ +} + +ThreadPoolJob::~ThreadPoolJob() +{ + // you mustn't delete a job while it's still in a pool! Use ThreadPool::removeJob() + // to remove it first! + jassert (pool == nullptr || ! pool->contains (this)); +} + +String ThreadPoolJob::getJobName() const +{ + return jobName; +} + +void ThreadPoolJob::setJobName (const String& newName) +{ + jobName = newName; +} + +void ThreadPoolJob::signalJobShouldExit() +{ + shouldStop = true; +} + +ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() +{ + if (ThreadPool::ThreadPoolThread* t = dynamic_cast (Thread::getCurrentThread())) + return t->currentJob; + + return nullptr; +} + +//============================================================================== +ThreadPool::ThreadPool (const int numThreads) +{ + jassert (numThreads > 0); // not much point having a pool without any threads! + + createThreads (numThreads); +} + +ThreadPool::ThreadPool() +{ + createThreads (SystemStats::getNumCpus()); +} + +ThreadPool::~ThreadPool() +{ + removeAllJobs (true, 5000); + stopThreads(); +} + +void ThreadPool::createThreads (int numThreads) +{ + for (int i = jmax (1, numThreads); --i >= 0;) + threads.add (new ThreadPoolThread (*this)); + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->startThread(); +} + +void ThreadPool::stopThreads() +{ + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->signalThreadShouldExit(); + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->stopThread (500); +} + +void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinished) +{ + jassert (job != nullptr); + jassert (job->pool == nullptr); + + if (job->pool == nullptr) + { + job->pool = this; + job->shouldStop = false; + job->isActive = false; + job->shouldBeDeleted = deleteJobWhenFinished; + + { + const ScopedLock sl (lock); + jobs.add (job); + } + + for (int i = threads.size(); --i >= 0;) + threads.getUnchecked(i)->notify(); + } +} + +int ThreadPool::getNumJobs() const +{ + return jobs.size(); +} + +ThreadPoolJob* ThreadPool::getJob (const int index) const +{ + const ScopedLock sl (lock); + return jobs [index]; +} + +bool ThreadPool::contains (const ThreadPoolJob* const job) const +{ + const ScopedLock sl (lock); + return jobs.contains (const_cast (job)); +} + +bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const +{ + const ScopedLock sl (lock); + return jobs.contains (const_cast (job)) && job->isActive; +} + +bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const +{ + if (job != nullptr) + { + const uint32 start = Time::getMillisecondCounter(); + + while (contains (job)) + { + if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + (uint32) timeOutMs) + return false; + + jobFinishedSignal.wait (2); + } + } + + return true; +} + +bool ThreadPool::removeJob (ThreadPoolJob* const job, + const bool interruptIfRunning, + const int timeOutMs) +{ + bool dontWait = true; + OwnedArray deletionList; + + if (job != nullptr) + { + const ScopedLock sl (lock); + + if (jobs.contains (job)) + { + if (job->isActive) + { + if (interruptIfRunning) + job->signalJobShouldExit(); + + dontWait = false; + } + else + { + jobs.removeFirstMatchingValue (job); + addToDeleteList (deletionList, job); + } + } + } + + return dontWait || waitForJobToFinish (job, timeOutMs); +} + +bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, + ThreadPool::JobSelector* const selectedJobsToRemove) +{ + Array jobsToWaitFor; + + { + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + for (int i = jobs.size(); --i >= 0;) + { + ThreadPoolJob* const job = jobs.getUnchecked(i); + + if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) + { + if (job->isActive) + { + jobsToWaitFor.add (job); + + if (interruptRunningJobs) + job->signalJobShouldExit(); + } + else + { + jobs.remove (i); + addToDeleteList (deletionList, job); + } + } + } + } + } + + const uint32 start = Time::getMillisecondCounter(); + + for (;;) + { + for (int i = jobsToWaitFor.size(); --i >= 0;) + { + ThreadPoolJob* const job = jobsToWaitFor.getUnchecked (i); + + if (! isJobRunning (job)) + jobsToWaitFor.remove (i); + } + + if (jobsToWaitFor.size() == 0) + break; + + if (timeOutMs >= 0 && Time::getMillisecondCounter() >= start + (uint32) timeOutMs) + return false; + + jobFinishedSignal.wait (20); + } + + return true; +} + +StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) const +{ + StringArray s; + const ScopedLock sl (lock); + + for (int i = 0; i < jobs.size(); ++i) + { + const ThreadPoolJob* const job = jobs.getUnchecked(i); + if (job->isActive || ! onlyReturnActiveJobs) + s.add (job->getJobName()); + } + + return s; +} + +bool ThreadPool::setThreadPriorities (const int newPriority) +{ + bool ok = true; + + for (int i = threads.size(); --i >= 0;) + if (! threads.getUnchecked(i)->setPriority (newPriority)) + ok = false; + + return ok; +} + +ThreadPoolJob* ThreadPool::pickNextJobToRun() +{ + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + for (int i = 0; i < jobs.size(); ++i) + { + ThreadPoolJob* job = jobs[i]; + + if (job != nullptr && ! job->isActive) + { + if (job->shouldStop) + { + jobs.remove (i); + addToDeleteList (deletionList, job); + --i; + continue; + } + + job->isActive = true; + return job; + } + } + } + + return nullptr; +} + +bool ThreadPool::runNextJob (ThreadPoolThread& thread) +{ + if (ThreadPoolJob* const job = pickNextJobToRun()) + { + ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; + thread.currentJob = job; + + JUCE_TRY + { + result = job->runJob(); + } + JUCE_CATCH_ALL_ASSERT + + thread.currentJob = nullptr; + + OwnedArray deletionList; + + { + const ScopedLock sl (lock); + + if (jobs.contains (job)) + { + job->isActive = false; + + if (result != ThreadPoolJob::jobNeedsRunningAgain || job->shouldStop) + { + jobs.removeFirstMatchingValue (job); + addToDeleteList (deletionList, job); + + jobFinishedSignal.signal(); + } + else + { + // move the job to the end of the queue if it wants another go + jobs.move (jobs.indexOf (job), -1); + } + } + } + + return true; + } + + return false; +} + +void ThreadPool::addToDeleteList (OwnedArray& deletionList, ThreadPoolJob* const job) const +{ + job->shouldStop = true; + job->pool = nullptr; + + if (job->shouldBeDeleted) + deletionList.add (job); +} diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h new file mode 100644 index 0000000..3d5f762 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h @@ -0,0 +1,321 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_THREADPOOL_H_INCLUDED +#define JUCE_THREADPOOL_H_INCLUDED + +class ThreadPool; +class ThreadPoolThread; + + +//============================================================================== +/** + A task that is executed by a ThreadPool object. + + A ThreadPool keeps a list of ThreadPoolJob objects which are executed by + its threads. + + The runJob() method needs to be implemented to do the task, and if the code that + does the work takes a significant time to run, it must keep checking the shouldExit() + method to see if something is trying to interrupt the job. If shouldExit() returns + true, the runJob() method must return immediately. + + @see ThreadPool, Thread +*/ +class JUCE_API ThreadPoolJob +{ +public: + //============================================================================== + /** Creates a thread pool job object. + After creating your job, add it to a thread pool with ThreadPool::addJob(). + */ + explicit ThreadPoolJob (const String& name); + + /** Destructor. */ + virtual ~ThreadPoolJob(); + + //============================================================================== + /** Returns the name of this job. + @see setJobName + */ + String getJobName() const; + + /** Changes the job's name. + @see getJobName + */ + void setJobName (const String& newName); + + //============================================================================== + /** These are the values that can be returned by the runJob() method. + */ + enum JobStatus + { + jobHasFinished = 0, /**< indicates that the job has finished and can be + removed from the pool. */ + + jobNeedsRunningAgain /**< indicates that the job would like to be called + again when a thread is free. */ + }; + + /** Peforms the actual work that this job needs to do. + + Your subclass must implement this method, in which is does its work. + + If the code in this method takes a significant time to run, it must repeatedly check + the shouldExit() method to see if something is trying to interrupt the job. + If shouldExit() ever returns true, the runJob() method must return immediately. + + If this method returns jobHasFinished, then the job will be removed from the pool + immediately. If it returns jobNeedsRunningAgain, then the job will be left in the + pool and will get a chance to run again as soon as a thread is free. + + @see shouldExit() + */ + virtual JobStatus runJob() = 0; + + + //============================================================================== + /** Returns true if this job is currently running its runJob() method. */ + bool isRunning() const noexcept { return isActive; } + + /** Returns true if something is trying to interrupt this job and make it stop. + + Your runJob() method must call this whenever it gets a chance, and if it ever + returns true, the runJob() method must return immediately. + + @see signalJobShouldExit() + */ + bool shouldExit() const noexcept { return shouldStop; } + + /** Calling this will cause the shouldExit() method to return true, and the job + should (if it's been implemented correctly) stop as soon as possible. + + @see shouldExit() + */ + void signalJobShouldExit(); + + //============================================================================== + /** If the calling thread is being invoked inside a runJob() method, this will + return the ThreadPoolJob that it belongs to. + */ + static ThreadPoolJob* getCurrentThreadPoolJob(); + + //============================================================================== +private: + friend class ThreadPool; + friend class ThreadPoolThread; + String jobName; + ThreadPool* pool; + bool shouldStop, isActive, shouldBeDeleted; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolJob) +}; + + +//============================================================================== +/** + A set of threads that will run a list of jobs. + + When a ThreadPoolJob object is added to the ThreadPool's list, its runJob() method + will be called by the next pooled thread that becomes free. + + @see ThreadPoolJob, Thread +*/ +class JUCE_API ThreadPool +{ +public: + //============================================================================== + /** Creates a thread pool. + Once you've created a pool, you can give it some jobs by calling addJob(). + @param numberOfThreads the number of threads to run. These will be started + immediately, and will run until the pool is deleted. + */ + ThreadPool (int numberOfThreads); + + /** Creates a thread pool with one thread per CPU core. + Once you've created a pool, you can give it some jobs by calling addJob(). + If you want to specify the number of threads, use the other constructor; this + one creates a pool which has one thread for each CPU core. + @see SystemStats::getNumCpus() + */ + ThreadPool(); + + /** Destructor. + + This will attempt to remove all the jobs before deleting, but if you want to + specify a timeout, you should call removeAllJobs() explicitly before deleting + the pool. + */ + ~ThreadPool(); + + //============================================================================== + /** A callback class used when you need to select which ThreadPoolJob objects are suitable + for some kind of operation. + @see ThreadPool::removeAllJobs + */ + class JUCE_API JobSelector + { + public: + virtual ~JobSelector() {} + + /** Should return true if the specified thread matches your criteria for whatever + operation that this object is being used for. + + Any implementation of this method must be extremely fast and thread-safe! + */ + virtual bool isJobSuitable (ThreadPoolJob* job) = 0; + }; + + //============================================================================== + /** Adds a job to the queue. + + Once a job has been added, then the next time a thread is free, it will run + the job's ThreadPoolJob::runJob() method. Depending on the return value of the + runJob() method, the pool will either remove the job from the pool or add it to + the back of the queue to be run again. + + If deleteJobWhenFinished is true, then the job object will be owned and deleted by + the pool when not needed - if you do this, make sure that your object's destructor + is thread-safe. + + If deleteJobWhenFinished is false, the pointer will be used but not deleted, and + the caller is responsible for making sure the object is not deleted before it has + been removed from the pool. + */ + void addJob (ThreadPoolJob* job, + bool deleteJobWhenFinished); + + /** Tries to remove a job from the pool. + + If the job isn't yet running, this will simply remove it. If it is running, it + will wait for it to finish. + + If the timeout period expires before the job finishes running, then the job will be + left in the pool and this will return false. It returns true if the job is successfully + stopped and removed. + + @param job the job to remove + @param interruptIfRunning if true, then if the job is currently busy, its + ThreadPoolJob::signalJobShouldExit() method will be called to try + to interrupt it. If false, then if the job will be allowed to run + until it stops normally (or the timeout expires) + @param timeOutMilliseconds the length of time this method should wait for the job to finish + before giving up and returning false + */ + bool removeJob (ThreadPoolJob* job, + bool interruptIfRunning, + int timeOutMilliseconds); + + /** Tries to remove all jobs from the pool. + + @param interruptRunningJobs if true, then all running jobs will have their ThreadPoolJob::signalJobShouldExit() + methods called to try to interrupt them + @param timeOutMilliseconds the length of time this method should wait for all the jobs to finish + before giving up and returning false + @param selectedJobsToRemove if this is non-zero, the JobSelector object is asked to decide which + jobs should be removed. If it is zero, all jobs are removed + @returns true if all jobs are successfully stopped and removed; false if the timeout period + expires while waiting for one or more jobs to stop + */ + bool removeAllJobs (bool interruptRunningJobs, + int timeOutMilliseconds, + JobSelector* selectedJobsToRemove = nullptr); + + /** Returns the number of jobs currently running or queued. + */ + int getNumJobs() const; + + /** Returns one of the jobs in the queue. + + Note that this can be a very volatile list as jobs might be continuously getting shifted + around in the list, and this method may return nullptr if the index is currently out-of-range. + */ + ThreadPoolJob* getJob (int index) const; + + /** Returns true if the given job is currently queued or running. + + @see isJobRunning() + */ + bool contains (const ThreadPoolJob* job) const; + + /** Returns true if the given job is currently being run by a thread. + */ + bool isJobRunning (const ThreadPoolJob* job) const; + + /** Waits until a job has finished running and has been removed from the pool. + + This will wait until the job is no longer in the pool - i.e. until its + runJob() method returns ThreadPoolJob::jobHasFinished. + + If the timeout period expires before the job finishes, this will return false; + it returns true if the job has finished successfully. + */ + bool waitForJobToFinish (const ThreadPoolJob* job, + int timeOutMilliseconds) const; + + /** Returns a list of the names of all the jobs currently running or queued. + If onlyReturnActiveJobs is true, only the ones currently running are returned. + */ + StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; + + /** Changes the priority of all the threads. + + This will call Thread::setPriority() for each thread in the pool. + May return false if for some reason the priority can't be changed. + */ + bool setThreadPriorities (int newPriority); + + +private: + //============================================================================== + Array jobs; + + class ThreadPoolThread; + friend class ThreadPoolJob; + friend class ThreadPoolThread; + friend struct ContainerDeletePolicy; + OwnedArray threads; + + CriticalSection lock; + WaitableEvent jobFinishedSignal; + + bool runNextJob (ThreadPoolThread&); + ThreadPoolJob* pickNextJobToRun(); + void addToDeleteList (OwnedArray&, ThreadPoolJob*) const; + void createThreads (int numThreads); + void stopThreads(); + + // Note that this method has changed, and no longer has a parameter to indicate + // whether the jobs should be deleted - see the new method for details. + void removeAllJobs (bool, int, bool); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPool) +}; + + +#endif // JUCE_THREADPOOL_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.cpp new file mode 100644 index 0000000..f055a15 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.cpp @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +TimeSliceThread::TimeSliceThread (const String& name) + : Thread (name), + clientBeingCalled (nullptr) +{ +} + +TimeSliceThread::~TimeSliceThread() +{ + stopThread (2000); +} + +//============================================================================== +void TimeSliceThread::addTimeSliceClient (TimeSliceClient* const client, int millisecondsBeforeStarting) +{ + if (client != nullptr) + { + const ScopedLock sl (listLock); + client->nextCallTime = Time::getCurrentTime() + RelativeTime::milliseconds (millisecondsBeforeStarting); + clients.addIfNotAlreadyThere (client); + notify(); + } +} + +void TimeSliceThread::removeTimeSliceClient (TimeSliceClient* const client) +{ + const ScopedLock sl1 (listLock); + + // if there's a chance we're in the middle of calling this client, we need to + // also lock the outer lock.. + if (clientBeingCalled == client) + { + const ScopedUnlock ul (listLock); // unlock first to get the order right.. + + const ScopedLock sl2 (callbackLock); + const ScopedLock sl3 (listLock); + + clients.removeFirstMatchingValue (client); + } + else + { + clients.removeFirstMatchingValue (client); + } +} + +void TimeSliceThread::moveToFrontOfQueue (TimeSliceClient* client) +{ + const ScopedLock sl (listLock); + + if (clients.contains (client)) + { + client->nextCallTime = Time::getCurrentTime(); + notify(); + } +} + +int TimeSliceThread::getNumClients() const +{ + return clients.size(); +} + +TimeSliceClient* TimeSliceThread::getClient (const int i) const +{ + const ScopedLock sl (listLock); + return clients [i]; +} + +//============================================================================== +TimeSliceClient* TimeSliceThread::getNextClient (int index) const +{ + Time soonest; + TimeSliceClient* client = nullptr; + + for (int i = clients.size(); --i >= 0;) + { + TimeSliceClient* const c = clients.getUnchecked ((i + index) % clients.size()); + + if (client == nullptr || c->nextCallTime < soonest) + { + client = c; + soonest = c->nextCallTime; + } + } + + return client; +} + +void TimeSliceThread::run() +{ + int index = 0; + + while (! threadShouldExit()) + { + int timeToWait = 500; + + { + Time nextClientTime; + + { + const ScopedLock sl2 (listLock); + + index = clients.size() > 0 ? ((index + 1) % clients.size()) : 0; + + if (TimeSliceClient* const firstClient = getNextClient (index)) + nextClientTime = firstClient->nextCallTime; + } + + const Time now (Time::getCurrentTime()); + + if (nextClientTime > now) + { + timeToWait = (int) jmin ((int64) 500, (nextClientTime - now).inMilliseconds()); + } + else + { + timeToWait = index == 0 ? 1 : 0; + + const ScopedLock sl (callbackLock); + + { + const ScopedLock sl2 (listLock); + clientBeingCalled = getNextClient (index); + } + + if (clientBeingCalled != nullptr) + { + const int msUntilNextCall = clientBeingCalled->useTimeSlice(); + + const ScopedLock sl2 (listLock); + + if (msUntilNextCall >= 0) + clientBeingCalled->nextCallTime = now + RelativeTime::milliseconds (msUntilNextCall); + else + clients.removeFirstMatchingValue (clientBeingCalled); + + clientBeingCalled = nullptr; + } + } + } + + if (timeToWait > 0) + wait (timeToWait); + } +} diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.h b/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.h new file mode 100644 index 0000000..8c30567 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_TimeSliceThread.h @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TIMESLICETHREAD_H_INCLUDED +#define JUCE_TIMESLICETHREAD_H_INCLUDED + +class TimeSliceThread; + + +//============================================================================== +/** + Used by the TimeSliceThread class. + + To register your class with a TimeSliceThread, derive from this class and + use the TimeSliceThread::addTimeSliceClient() method to add it to the list. + + Make sure you always call TimeSliceThread::removeTimeSliceClient() before + deleting your client! + + @see TimeSliceThread +*/ +class JUCE_API TimeSliceClient +{ +public: + /** Destructor. */ + virtual ~TimeSliceClient() {} + + /** Called back by a TimeSliceThread. + + When you register this class with it, a TimeSliceThread will repeatedly call + this method. + + The implementation of this method should use its time-slice to do something that's + quick - never block for longer than absolutely necessary. + + @returns Your method should return the number of milliseconds which it would like to wait before being called + again. Returning 0 will make the thread call again as soon as possible (after possibly servicing + other busy clients). If you return a value below zero, your client will be removed from the list of clients, + and won't be called again. The value you specify isn't a guaranteee, and is only used as a hint by the + thread - the actual time before the next callback may be more or less than specified. + You can force the TimeSliceThread to wake up and poll again immediately by calling its notify() method. + */ + virtual int useTimeSlice() = 0; + + +private: + friend class TimeSliceThread; + Time nextCallTime; +}; + + +//============================================================================== +/** + A thread that keeps a list of clients, and calls each one in turn, giving them + all a chance to run some sort of short task. + + @see TimeSliceClient, Thread +*/ +class JUCE_API TimeSliceThread : public Thread +{ +public: + //============================================================================== + /** + Creates a TimeSliceThread. + + When first created, the thread is not running. Use the startThread() + method to start it. + */ + explicit TimeSliceThread (const String& threadName); + + /** Destructor. + + Deleting a Thread object that is running will only give the thread a + brief opportunity to stop itself cleanly, so it's recommended that you + should always call stopThread() with a decent timeout before deleting, + to avoid the thread being forcibly killed (which is a Bad Thing). + */ + ~TimeSliceThread(); + + //============================================================================== + /** Adds a client to the list. + + The client's callbacks will start after the number of milliseconds specified + by millisecondsBeforeStarting (and this may happen before this method has returned). + */ + void addTimeSliceClient (TimeSliceClient* client, int millisecondsBeforeStarting = 0); + + /** Removes a client from the list. + + This method will make sure that all callbacks to the client have completely + finished before the method returns. + */ + void removeTimeSliceClient (TimeSliceClient* client); + + /** If the given client is waiting in the queue, it will be moved to the front + and given a time-slice as soon as possible. + If the specified client has not been added, nothing will happen. + */ + void moveToFrontOfQueue (TimeSliceClient* client); + + /** Returns the number of registered clients. */ + int getNumClients() const; + + /** Returns one of the registered clients. */ + TimeSliceClient* getClient (int index) const; + + //============================================================================== + #ifndef DOXYGEN + void run() override; + #endif + + //============================================================================== +private: + CriticalSection callbackLock, listLock; + Array clients; + TimeSliceClient* clientBeingCalled; + + TimeSliceClient* getNextClient (int index) const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimeSliceThread) +}; + + +#endif // JUCE_TIMESLICETHREAD_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h new file mode 100644 index 0000000..83f6f06 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h @@ -0,0 +1,118 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_WAITABLEEVENT_H_INCLUDED +#define JUCE_WAITABLEEVENT_H_INCLUDED + + +//============================================================================== +/** + Allows threads to wait for events triggered by other threads. + + A thread can call wait() on a WaitableObject, and this will suspend the + calling thread until another thread wakes it up by calling the signal() + method. +*/ +class JUCE_API WaitableEvent +{ +public: + //============================================================================== + /** Creates a WaitableEvent object. + + The object is initially in an unsignalled state. + + @param manualReset If this is false, the event will be reset automatically when the wait() + method is called. If manualReset is true, then once the event is signalled, + the only way to reset it will be by calling the reset() method. + */ + explicit WaitableEvent (bool manualReset = false) noexcept; + + /** Destructor. + + If other threads are waiting on this object when it gets deleted, this + can cause nasty errors, so be careful! + */ + ~WaitableEvent() noexcept; + + //============================================================================== + /** Suspends the calling thread until the event has been signalled. + + This will wait until the object's signal() method is called by another thread, + or until the timeout expires. + + After the event has been signalled, this method will return true and if manualReset + was set to false in the WaitableEvent's constructor, then the event will be reset. + + @param timeOutMilliseconds the maximum time to wait, in milliseconds. A negative + value will cause it to wait forever. + + @returns true if the object has been signalled, false if the timeout expires first. + @see signal, reset + */ + bool wait (int timeOutMilliseconds = -1) const noexcept; + + //============================================================================== + /** Wakes up any threads that are currently waiting on this object. + + If signal() is called when nothing is waiting, the next thread to call wait() + will return immediately and reset the signal. + + If the WaitableEvent is manual reset, all current and future threads that wait upon this + object will be woken, until reset() is explicitly called. + + If the WaitableEvent is automatic reset, and one or more threads is waiting upon the object, + then one of them will be woken up. If no threads are currently waiting, then the next thread + to call wait() will be woken up. As soon as a thread is woken, the signal is automatically + reset. + + @see wait, reset + */ + void signal() const noexcept; + + //============================================================================== + /** Resets the event to an unsignalled state. + If it's not already signalled, this does nothing. + */ + void reset() const noexcept; + + +private: + //============================================================================== + #if JUCE_WINDOWS + void* handle; + #else + mutable pthread_cond_t condition; + mutable pthread_mutex_t mutex; + mutable bool triggered, manualReset; + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaitableEvent) +}; + + +#endif // JUCE_WAITABLEEVENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp new file mode 100644 index 0000000..b22d5e4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.cpp @@ -0,0 +1,132 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +static void appendToFile (const File& f, const String& s) +{ + if (f.getFullPathName().isNotEmpty()) + { + FileOutputStream out (f); + + if (! out.failedToOpen()) + out << s << newLine; + } +} + +PerformanceCounter::PerformanceCounter (const String& name, int runsPerPrintout, const File& loggingFile) + : runsPerPrint (runsPerPrintout), startTime (0), outputFile (loggingFile) +{ + stats.name = name; + appendToFile (outputFile, "**** Counter for \"" + name + "\" started at: " + Time::getCurrentTime().toString (true, true)); +} + +PerformanceCounter::~PerformanceCounter() +{ + printStatistics(); +} + +PerformanceCounter::Statistics::Statistics() noexcept + : averageSeconds(), maximumSeconds(), minimumSeconds(), totalSeconds(), numRuns() +{ +} + +void PerformanceCounter::Statistics::clear() noexcept +{ + averageSeconds = maximumSeconds = minimumSeconds = totalSeconds = 0; + numRuns = 0; +} + +void PerformanceCounter::Statistics::addResult (double elapsed) noexcept +{ + if (numRuns == 0) + { + maximumSeconds = elapsed; + minimumSeconds = elapsed; + } + else + { + maximumSeconds = jmax (maximumSeconds, elapsed); + minimumSeconds = jmin (minimumSeconds, elapsed); + } + + ++numRuns; + totalSeconds += elapsed; +} + +static String timeToString (double secs) +{ + return String ((int64) (secs * (secs < 0.01 ? 1000000.0 : 1000.0) + 0.5)) + + (secs < 0.01 ? " microsecs" : " millisecs"); +} + +String PerformanceCounter::Statistics::toString() const +{ + MemoryOutputStream s; + + s << "Performance count for \"" << name << "\" over " << numRuns << " run(s)" << newLine + << "Average = " << timeToString (averageSeconds) + << ", minimum = " << timeToString (minimumSeconds) + << ", maximum = " << timeToString (maximumSeconds) + << ", total = " << timeToString (totalSeconds); + + return s.toString(); +} + +void PerformanceCounter::start() noexcept +{ + startTime = Time::getHighResolutionTicks(); +} + +bool PerformanceCounter::stop() +{ + stats.addResult (Time::highResolutionTicksToSeconds (Time::getHighResolutionTicks() - startTime)); + + if (stats.numRuns < runsPerPrint) + return false; + + printStatistics(); + return true; +} + +void PerformanceCounter::printStatistics() +{ + const String desc (getStatisticsAndReset().toString()); + + Logger::outputDebugString (desc); + appendToFile (outputFile, desc); +} + +PerformanceCounter::Statistics PerformanceCounter::getStatisticsAndReset() +{ + Statistics s (stats); + stats.clear(); + + if (s.numRuns > 0) + s.averageSeconds = s.totalSeconds / s.numRuns; + + return s; +} diff --git a/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h new file mode 100644 index 0000000..aac50d2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_PerformanceCounter.h @@ -0,0 +1,127 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_PERFORMANCECOUNTER_H_INCLUDED +#define JUCE_PERFORMANCECOUNTER_H_INCLUDED + + +//============================================================================== +/** A timer for measuring performance of code and dumping the results to a file. + + e.g. @code + + PerformanceCounter pc ("fish", 50, "/temp/myfishlog.txt"); + + for (;;) + { + pc.start(); + + doSomethingFishy(); + + pc.stop(); + } + @endcode + + In this example, the time of each period between calling start/stop will be + measured and averaged over 50 runs, and the results printed to a file + every 50 times round the loop. +*/ +class JUCE_API PerformanceCounter +{ +public: + //============================================================================== + /** Creates a PerformanceCounter object. + + @param counterName the name used when printing out the statistics + @param runsPerPrintout the number of start/stop iterations before calling + printStatistics() + @param loggingFile a file to dump the results to - if this is File::nonexistent, + the results are just written to the debugger output + */ + PerformanceCounter (const String& counterName, + int runsPerPrintout = 100, + const File& loggingFile = File()); + + /** Destructor. */ + ~PerformanceCounter(); + + //============================================================================== + /** Starts timing. + @see stop + */ + void start() noexcept; + + /** Stops timing and prints out the results. + + The number of iterations before doing a printout of the + results is set in the constructor. + + @see start + */ + bool stop(); + + /** Dumps the current metrics to the debugger output and to a file. + + As well as using Logger::outputDebugString to print the results, + this will write then to the file specified in the constructor (if + this was valid). + */ + void printStatistics(); + + /** Holds the current statistics. */ + struct Statistics + { + Statistics() noexcept; + + void clear() noexcept; + String toString() const; + + void addResult (double elapsed) noexcept; + + String name; + double averageSeconds; + double maximumSeconds; + double minimumSeconds; + double totalSeconds; + int64 numRuns; + }; + + /** Returns a copy of the current stats, and resets the internal counter. */ + Statistics getStatisticsAndReset(); + +private: + //============================================================================== + Statistics stats; + int64 runsPerPrint, startTime; + File outputFile; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PerformanceCounter) +}; + + +#endif // JUCE_PERFORMANCECOUNTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp new file mode 100644 index 0000000..6fca4fe --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +RelativeTime::RelativeTime (const double secs) noexcept : numSeconds (secs) {} +RelativeTime::RelativeTime (const RelativeTime& other) noexcept : numSeconds (other.numSeconds) {} +RelativeTime::~RelativeTime() noexcept {} + +//============================================================================== +RelativeTime RelativeTime::milliseconds (const int milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } +RelativeTime RelativeTime::milliseconds (const int64 milliseconds) noexcept { return RelativeTime (milliseconds * 0.001); } +RelativeTime RelativeTime::seconds (double s) noexcept { return RelativeTime (s); } +RelativeTime RelativeTime::minutes (const double numberOfMinutes) noexcept { return RelativeTime (numberOfMinutes * 60.0); } +RelativeTime RelativeTime::hours (const double numberOfHours) noexcept { return RelativeTime (numberOfHours * (60.0 * 60.0)); } +RelativeTime RelativeTime::days (const double numberOfDays) noexcept { return RelativeTime (numberOfDays * (60.0 * 60.0 * 24.0)); } +RelativeTime RelativeTime::weeks (const double numberOfWeeks) noexcept { return RelativeTime (numberOfWeeks * (60.0 * 60.0 * 24.0 * 7.0)); } + +//============================================================================== +int64 RelativeTime::inMilliseconds() const noexcept { return (int64) (numSeconds * 1000.0); } +double RelativeTime::inMinutes() const noexcept { return numSeconds / 60.0; } +double RelativeTime::inHours() const noexcept { return numSeconds / (60.0 * 60.0); } +double RelativeTime::inDays() const noexcept { return numSeconds / (60.0 * 60.0 * 24.0); } +double RelativeTime::inWeeks() const noexcept { return numSeconds / (60.0 * 60.0 * 24.0 * 7.0); } + +//============================================================================== +RelativeTime& RelativeTime::operator= (const RelativeTime& other) noexcept { numSeconds = other.numSeconds; return *this; } + +RelativeTime RelativeTime::operator+= (RelativeTime t) noexcept { numSeconds += t.numSeconds; return *this; } +RelativeTime RelativeTime::operator-= (RelativeTime t) noexcept { numSeconds -= t.numSeconds; return *this; } +RelativeTime RelativeTime::operator+= (const double secs) noexcept { numSeconds += secs; return *this; } +RelativeTime RelativeTime::operator-= (const double secs) noexcept { numSeconds -= secs; return *this; } + +RelativeTime operator+ (RelativeTime t1, RelativeTime t2) noexcept { return t1 += t2; } +RelativeTime operator- (RelativeTime t1, RelativeTime t2) noexcept { return t1 -= t2; } + +bool operator== (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() == t2.inSeconds(); } +bool operator!= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() != t2.inSeconds(); } +bool operator> (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() > t2.inSeconds(); } +bool operator< (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() < t2.inSeconds(); } +bool operator>= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() >= t2.inSeconds(); } +bool operator<= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() <= t2.inSeconds(); } + +//============================================================================== +static void translateTimeField (String& result, int n, const char* singular, const char* plural) +{ + result << TRANS (n == 1 ? singular : plural) + .replace (n == 1 ? "1" : "2", String (n)) + << ' '; +} + +String RelativeTime::getDescription (const String& returnValueForZeroTime) const +{ + if (numSeconds < 0.001 && numSeconds > -0.001) + return returnValueForZeroTime; + + String result; + result.preallocateBytes (32); + + if (numSeconds < 0) + result << '-'; + + int fieldsShown = 0; + int n = std::abs ((int) inWeeks()); + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 week"), NEEDS_TRANS("2 weeks")); + ++fieldsShown; + } + + n = std::abs ((int) inDays()) % 7; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 day"), NEEDS_TRANS("2 days")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inHours()) % 24; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 hr"), NEEDS_TRANS("2 hrs")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inMinutes()) % 60; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 min"), NEEDS_TRANS("2 mins")); + ++fieldsShown; + } + + if (fieldsShown < 2) + { + n = std::abs ((int) inSeconds()) % 60; + if (n > 0) + { + translateTimeField (result, n, NEEDS_TRANS("1 sec"), NEEDS_TRANS("2 secs")); + ++fieldsShown; + } + + if (fieldsShown == 0) + { + n = std::abs ((int) inMilliseconds()) % 1000; + if (n > 0) + result << n << ' ' << TRANS ("ms"); + } + } + } + } + + return result.trimEnd(); +} diff --git a/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.h b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.h new file mode 100644 index 0000000..7e39d21 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.h @@ -0,0 +1,184 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_RELATIVETIME_H_INCLUDED +#define JUCE_RELATIVETIME_H_INCLUDED + + +//============================================================================== +/** A relative measure of time. + + The time is stored as a number of seconds, at double-precision floating + point accuracy, and may be positive or negative. + + If you need an absolute time, (i.e. a date + time), see the Time class. +*/ +class JUCE_API RelativeTime +{ +public: + //============================================================================== + /** Creates a RelativeTime. + + @param seconds the number of seconds, which may be +ve or -ve. + @see milliseconds, minutes, hours, days, weeks + */ + explicit RelativeTime (double seconds = 0.0) noexcept; + + /** Copies another relative time. */ + RelativeTime (const RelativeTime& other) noexcept; + + /** Copies another relative time. */ + RelativeTime& operator= (const RelativeTime& other) noexcept; + + /** Destructor. */ + ~RelativeTime() noexcept; + + //============================================================================== + /** Creates a new RelativeTime object representing a number of milliseconds. + @see seconds, minutes, hours, days, weeks + */ + static RelativeTime milliseconds (int milliseconds) noexcept; + + /** Creates a new RelativeTime object representing a number of milliseconds. + @see seconds, minutes, hours, days, weeks + */ + static RelativeTime milliseconds (int64 milliseconds) noexcept; + + /** Creates a new RelativeTime object representing a number of seconds. + @see milliseconds, minutes, hours, days, weeks + */ + static RelativeTime seconds (double seconds) noexcept; + + /** Creates a new RelativeTime object representing a number of minutes. + @see milliseconds, hours, days, weeks + */ + static RelativeTime minutes (double numberOfMinutes) noexcept; + + /** Creates a new RelativeTime object representing a number of hours. + @see milliseconds, minutes, days, weeks + */ + static RelativeTime hours (double numberOfHours) noexcept; + + /** Creates a new RelativeTime object representing a number of days. + @see milliseconds, minutes, hours, weeks + */ + static RelativeTime days (double numberOfDays) noexcept; + + /** Creates a new RelativeTime object representing a number of weeks. + @see milliseconds, minutes, hours, days + */ + static RelativeTime weeks (double numberOfWeeks) noexcept; + + //============================================================================== + /** Returns the number of milliseconds this time represents. + @see milliseconds, inSeconds, inMinutes, inHours, inDays, inWeeks + */ + int64 inMilliseconds() const noexcept; + + /** Returns the number of seconds this time represents. + @see inMilliseconds, inMinutes, inHours, inDays, inWeeks + */ + double inSeconds() const noexcept { return numSeconds; } + + /** Returns the number of minutes this time represents. + @see inMilliseconds, inSeconds, inHours, inDays, inWeeks + */ + double inMinutes() const noexcept; + + /** Returns the number of hours this time represents. + @see inMilliseconds, inSeconds, inMinutes, inDays, inWeeks + */ + double inHours() const noexcept; + + /** Returns the number of days this time represents. + @see inMilliseconds, inSeconds, inMinutes, inHours, inWeeks + */ + double inDays() const noexcept; + + /** Returns the number of weeks this time represents. + @see inMilliseconds, inSeconds, inMinutes, inHours, inDays + */ + double inWeeks() const noexcept; + + /** Returns a readable textual description of the time. + + The exact format of the string returned will depend on + the magnitude of the time - e.g. + + "1 min 4 secs", "1 hr 45 mins", "2 weeks 5 days", "140 ms" + + so that only the two most significant units are printed. + + The returnValueForZeroTime value is the result that is returned if the + length is zero. Depending on your application you might want to use this + to return something more relevant like "empty" or "0 secs", etc. + + @see inMilliseconds, inSeconds, inMinutes, inHours, inDays, inWeeks + */ + String getDescription (const String& returnValueForZeroTime = "0") const; + + + //============================================================================== + /** Adds another RelativeTime to this one. */ + RelativeTime operator+= (RelativeTime timeToAdd) noexcept; + /** Subtracts another RelativeTime from this one. */ + RelativeTime operator-= (RelativeTime timeToSubtract) noexcept; + + /** Adds a number of seconds to this time. */ + RelativeTime operator+= (double secondsToAdd) noexcept; + /** Subtracts a number of seconds from this time. */ + RelativeTime operator-= (double secondsToSubtract) noexcept; + +private: + //============================================================================== + double numSeconds; +}; + +//============================================================================== +/** Compares two RelativeTimes. */ +bool operator== (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator!= (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator> (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator< (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator>= (RelativeTime t1, RelativeTime t2) noexcept; +/** Compares two RelativeTimes. */ +bool operator<= (RelativeTime t1, RelativeTime t2) noexcept; + +//============================================================================== +/** Adds two RelativeTimes together. */ +RelativeTime operator+ (RelativeTime t1, RelativeTime t2) noexcept; +/** Subtracts two RelativeTimes. */ +RelativeTime operator- (RelativeTime t1, RelativeTime t2) noexcept; + + + +#endif // JUCE_RELATIVETIME_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/time/juce_Time.cpp b/JuceLibraryCode/modules/juce_core/time/juce_Time.cpp new file mode 100644 index 0000000..fabcfde --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_Time.cpp @@ -0,0 +1,469 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +namespace TimeHelpers +{ + static struct tm millisToLocal (const int64 millis) noexcept + { + struct tm result; + const int64 seconds = millis / 1000; + + if (seconds < 86400LL || seconds >= 2145916800LL) + { + // use extended maths for dates beyond 1970 to 2037.. + const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); + const int64 jdm = seconds + timeZoneAdjustment + 210866803200LL; + + const int days = (int) (jdm / 86400LL); + const int a = 32044 + days; + const int b = (4 * a + 3) / 146097; + const int c = a - (b * 146097) / 4; + const int d = (4 * c + 3) / 1461; + const int e = c - (d * 1461) / 4; + const int m = (5 * e + 2) / 153; + + result.tm_mday = e - (153 * m + 2) / 5 + 1; + result.tm_mon = m + 2 - 12 * (m / 10); + result.tm_year = b * 100 + d - 6700 + (m / 10); + result.tm_wday = (days + 1) % 7; + result.tm_yday = -1; + + int t = (int) (jdm % 86400LL); + result.tm_hour = t / 3600; + t %= 3600; + result.tm_min = t / 60; + result.tm_sec = t % 60; + result.tm_isdst = -1; + } + else + { + time_t now = static_cast (seconds); + + #if JUCE_WINDOWS + #ifdef _INC_TIME_INL + if (now >= 0 && now <= 0x793406fff) + localtime_s (&result, &now); + else + zerostruct (result); + #else + result = *localtime (&now); + #endif + #else + + localtime_r (&now, &result); // more thread-safe + #endif + } + + return result; + } + + static int extendedModulo (const int64 value, const int modulo) noexcept + { + return (int) (value >= 0 ? (value % modulo) + : (value - ((value / modulo) + 1) * modulo)); + } + + static inline String formatString (const String& format, const struct tm* const tm) + { + #if JUCE_ANDROID + typedef CharPointer_UTF8 StringType; + #elif JUCE_WINDOWS + typedef CharPointer_UTF16 StringType; + #else + typedef CharPointer_UTF32 StringType; + #endif + + for (size_t bufferSize = 256; ; bufferSize += 256) + { + HeapBlock buffer (bufferSize); + + #if JUCE_ANDROID + const size_t numChars = strftime (buffer, bufferSize - 1, format.toUTF8(), tm); + #elif JUCE_WINDOWS + const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); + #else + const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); + #endif + + if (numChars > 0 || format.isEmpty()) + return String (StringType (buffer), + StringType (buffer) + (int) numChars); + } + } + + static uint32 lastMSCounterValue = 0; +} + +//============================================================================== +Time::Time() noexcept + : millisSinceEpoch (0) +{ +} + +Time::Time (const Time& other) noexcept + : millisSinceEpoch (other.millisSinceEpoch) +{ +} + +Time::Time (const int64 ms) noexcept + : millisSinceEpoch (ms) +{ +} + +Time::Time (const int year, + const int month, + const int day, + const int hours, + const int minutes, + const int seconds, + const int milliseconds, + const bool useLocalTime) noexcept +{ + jassert (year > 100); // year must be a 4-digit version + + if (year < 1971 || year >= 2038 || ! useLocalTime) + { + // use extended maths for dates beyond 1970 to 2037.. + const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) + : 0; + const int a = (13 - month) / 12; + const int y = year + 4800 - a; + const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 + + (y * 365) + (y / 4) - (y / 100) + (y / 400) + - 32045; + + const int64 s = ((int64) jd) * 86400LL - 210866803200LL; + + millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) + + milliseconds; + } + else + { + struct tm t; + t.tm_year = year - 1900; + t.tm_mon = month; + t.tm_mday = day; + t.tm_hour = hours; + t.tm_min = minutes; + t.tm_sec = seconds; + t.tm_isdst = -1; + + millisSinceEpoch = 1000 * (int64) mktime (&t); + + if (millisSinceEpoch < 0) + millisSinceEpoch = 0; + else + millisSinceEpoch += milliseconds; + } +} + +Time::~Time() noexcept +{ +} + +Time& Time::operator= (const Time& other) noexcept +{ + millisSinceEpoch = other.millisSinceEpoch; + return *this; +} + +//============================================================================== +int64 Time::currentTimeMillis() noexcept +{ + #if JUCE_WINDOWS + struct _timeb t; + #ifdef _INC_TIME_INL + _ftime_s (&t); + #else + _ftime (&t); + #endif + return ((int64) t.time) * 1000 + t.millitm; + #else + struct timeval tv; + gettimeofday (&tv, nullptr); + return ((int64) tv.tv_sec) * 1000 + tv.tv_usec / 1000; + #endif +} + +Time JUCE_CALLTYPE Time::getCurrentTime() noexcept +{ + return Time (currentTimeMillis()); +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept; + +uint32 Time::getMillisecondCounter() noexcept +{ + const uint32 now = juce_millisecondsSinceStartup(); + + if (now < TimeHelpers::lastMSCounterValue) + { + // in multi-threaded apps this might be called concurrently, so + // make sure that our last counter value only increases and doesn't + // go backwards.. + if (now < TimeHelpers::lastMSCounterValue - 1000) + TimeHelpers::lastMSCounterValue = now; + } + else + { + TimeHelpers::lastMSCounterValue = now; + } + + return now; +} + +uint32 Time::getApproximateMillisecondCounter() noexcept +{ + if (TimeHelpers::lastMSCounterValue == 0) + getMillisecondCounter(); + + return TimeHelpers::lastMSCounterValue; +} + +void Time::waitForMillisecondCounter (const uint32 targetTime) noexcept +{ + for (;;) + { + const uint32 now = getMillisecondCounter(); + + if (now >= targetTime) + break; + + const int toWait = (int) (targetTime - now); + + if (toWait > 2) + { + Thread::sleep (jmin (20, toWait >> 1)); + } + else + { + // xxx should consider using mutex_pause on the mac as it apparently + // makes it seem less like a spinlock and avoids lowering the thread pri. + for (int i = 10; --i >= 0;) + Thread::yield(); + } + } +} + +//============================================================================== +double Time::highResolutionTicksToSeconds (const int64 ticks) noexcept +{ + return ticks / (double) getHighResolutionTicksPerSecond(); +} + +int64 Time::secondsToHighResolutionTicks (const double seconds) noexcept +{ + return (int64) (seconds * (double) getHighResolutionTicksPerSecond()); +} + +//============================================================================== +String Time::toString (const bool includeDate, + const bool includeTime, + const bool includeSeconds, + const bool use24HourClock) const noexcept +{ + String result; + + if (includeDate) + { + result << getDayOfMonth() << ' ' + << getMonthName (true) << ' ' + << getYear(); + + if (includeTime) + result << ' '; + } + + if (includeTime) + { + const int mins = getMinutes(); + + result << (use24HourClock ? getHours() : getHoursInAmPmFormat()) + << (mins < 10 ? ":0" : ":") << mins; + + if (includeSeconds) + { + const int secs = getSeconds(); + result << (secs < 10 ? ":0" : ":") << secs; + } + + if (! use24HourClock) + result << (isAfternoon() ? "pm" : "am"); + } + + return result.trimEnd(); +} + +String Time::formatted (const String& format) const +{ + struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); + return TimeHelpers::formatString (format, &t); +} + +//============================================================================== +int Time::getYear() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_year + 1900; } +int Time::getMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mon; } +int Time::getDayOfYear() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_yday; } +int Time::getDayOfMonth() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_mday; } +int Time::getDayOfWeek() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_wday; } +int Time::getHours() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_hour; } +int Time::getMinutes() const noexcept { return TimeHelpers::millisToLocal (millisSinceEpoch).tm_min; } +int Time::getSeconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch / 1000, 60); } +int Time::getMilliseconds() const noexcept { return TimeHelpers::extendedModulo (millisSinceEpoch, 1000); } + +int Time::getHoursInAmPmFormat() const noexcept +{ + const int hours = getHours(); + + if (hours == 0) return 12; + if (hours <= 12) return hours; + + return hours - 12; +} + +bool Time::isAfternoon() const noexcept +{ + return getHours() >= 12; +} + +bool Time::isDaylightSavingTime() const noexcept +{ + return TimeHelpers::millisToLocal (millisSinceEpoch).tm_isdst != 0; +} + +String Time::getTimeZone() const noexcept +{ + String zone[2]; + + #if JUCE_WINDOWS + _tzset(); + + #ifdef _INC_TIME_INL + for (int i = 0; i < 2; ++i) + { + char name[128] = { 0 }; + size_t length; + _get_tzname (&length, name, 127, i); + zone[i] = name; + } + #else + const char** const zonePtr = (const char**) _tzname; + zone[0] = zonePtr[0]; + zone[1] = zonePtr[1]; + #endif + #else + tzset(); + const char** const zonePtr = (const char**) tzname; + zone[0] = zonePtr[0]; + zone[1] = zonePtr[1]; + #endif + + if (isDaylightSavingTime()) + { + zone[0] = zone[1]; + + if (zone[0].length() > 3 + && zone[0].containsIgnoreCase ("daylight") + && zone[0].contains ("GMT")) + zone[0] = "BST"; + } + + return zone[0].substring (0, 3); +} + +String Time::getMonthName (const bool threeLetterVersion) const +{ + return getMonthName (getMonth(), threeLetterVersion); +} + +String Time::getWeekdayName (const bool threeLetterVersion) const +{ + return getWeekdayName (getDayOfWeek(), threeLetterVersion); +} + +static const char* const shortMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static const char* const longMonthNames[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + +String Time::getMonthName (int monthNumber, const bool threeLetterVersion) +{ + monthNumber %= 12; + + return TRANS (threeLetterVersion ? shortMonthNames [monthNumber] + : longMonthNames [monthNumber]); +} + +String Time::getWeekdayName (int day, const bool threeLetterVersion) +{ + static const char* const shortDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + static const char* const longDayNames[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + + day %= 7; + + return TRANS (threeLetterVersion ? shortDayNames [day] + : longDayNames [day]); +} + +//============================================================================== +Time& Time::operator+= (RelativeTime delta) noexcept { millisSinceEpoch += delta.inMilliseconds(); return *this; } +Time& Time::operator-= (RelativeTime delta) noexcept { millisSinceEpoch -= delta.inMilliseconds(); return *this; } + +Time operator+ (Time time, RelativeTime delta) noexcept { Time t (time); return t += delta; } +Time operator- (Time time, RelativeTime delta) noexcept { Time t (time); return t -= delta; } +Time operator+ (RelativeTime delta, Time time) noexcept { Time t (time); return t += delta; } +const RelativeTime operator- (Time time1, Time time2) noexcept { return RelativeTime::milliseconds (time1.toMilliseconds() - time2.toMilliseconds()); } + +bool operator== (Time time1, Time time2) noexcept { return time1.toMilliseconds() == time2.toMilliseconds(); } +bool operator!= (Time time1, Time time2) noexcept { return time1.toMilliseconds() != time2.toMilliseconds(); } +bool operator< (Time time1, Time time2) noexcept { return time1.toMilliseconds() < time2.toMilliseconds(); } +bool operator> (Time time1, Time time2) noexcept { return time1.toMilliseconds() > time2.toMilliseconds(); } +bool operator<= (Time time1, Time time2) noexcept { return time1.toMilliseconds() <= time2.toMilliseconds(); } +bool operator>= (Time time1, Time time2) noexcept { return time1.toMilliseconds() >= time2.toMilliseconds(); } + +static int getMonthNumberForCompileDate (const String& m) noexcept +{ + for (int i = 0; i < 12; ++i) + if (m.equalsIgnoreCase (shortMonthNames[i])) + return i; + + // If you hit this because your compiler has a non-standard __DATE__ format, + // let me know so we can add support for it! + jassertfalse; + return 0; +} + +Time Time::getCompilationDate() +{ + StringArray dateTokens; + dateTokens.addTokens (__DATE__, true); + dateTokens.removeEmptyStrings (true); + + return Time (dateTokens[2].getIntValue(), + getMonthNumberForCompileDate (dateTokens[0]), + dateTokens[1].getIntValue(), 12, 0); +} diff --git a/JuceLibraryCode/modules/juce_core/time/juce_Time.h b/JuceLibraryCode/modules/juce_core/time/juce_Time.h new file mode 100644 index 0000000..4a35e08 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/time/juce_Time.h @@ -0,0 +1,404 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_TIME_H_INCLUDED +#define JUCE_TIME_H_INCLUDED + + +//============================================================================== +/** + Holds an absolute date and time. + + Internally, the time is stored at millisecond precision. + + @see RelativeTime +*/ +class JUCE_API Time +{ +public: + //============================================================================== + /** Creates a Time object. + + This default constructor creates a time of 1st January 1970, (which is + represented internally as 0ms). + + To create a time object representing the current time, use getCurrentTime(). + + @see getCurrentTime + */ + Time() noexcept; + + /** Creates a time based on a number of milliseconds. + + The internal millisecond count is set to 0 (1st January 1970). To create a + time object set to the current time, use getCurrentTime(). + + @param millisecondsSinceEpoch the number of milliseconds since the unix + 'epoch' (midnight Jan 1st 1970). + @see getCurrentTime, currentTimeMillis + */ + explicit Time (int64 millisecondsSinceEpoch) noexcept; + + /** Creates a time from a set of date components. + + The timezone is assumed to be whatever the system is using as its locale. + + @param year the year, in 4-digit format, e.g. 2004 + @param month the month, in the range 0 to 11 + @param day the day of the month, in the range 1 to 31 + @param hours hours in 24-hour clock format, 0 to 23 + @param minutes minutes 0 to 59 + @param seconds seconds 0 to 59 + @param milliseconds milliseconds 0 to 999 + @param useLocalTime if true, encode using the current machine's local time; if + false, it will always work in GMT. + */ + Time (int year, + int month, + int day, + int hours, + int minutes, + int seconds = 0, + int milliseconds = 0, + bool useLocalTime = true) noexcept; + + /** Creates a copy of another Time object. */ + Time (const Time& other) noexcept; + + /** Destructor. */ + ~Time() noexcept; + + /** Copies this time from another one. */ + Time& operator= (const Time& other) noexcept; + + //============================================================================== + /** Returns a Time object that is set to the current system time. + + @see currentTimeMillis + */ + static Time JUCE_CALLTYPE getCurrentTime() noexcept; + + /** Returns the time as a number of milliseconds. + + @returns the number of milliseconds this Time object represents, since + midnight jan 1st 1970. + @see getMilliseconds + */ + int64 toMilliseconds() const noexcept { return millisSinceEpoch; } + + /** Returns the year. + + A 4-digit format is used, e.g. 2004. + */ + int getYear() const noexcept; + + /** Returns the number of the month. + + The value returned is in the range 0 to 11. + @see getMonthName + */ + int getMonth() const noexcept; + + /** Returns the name of the month. + + @param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false + it'll return the long form, e.g. "January" + @see getMonth + */ + String getMonthName (bool threeLetterVersion) const; + + /** Returns the day of the month. + The value returned is in the range 1 to 31. + */ + int getDayOfMonth() const noexcept; + + /** Returns the number of the day of the week. + The value returned is in the range 0 to 6 (0 = sunday, 1 = monday, etc). + */ + int getDayOfWeek() const noexcept; + + /** Returns the number of the day of the year. + The value returned is in the range 0 to 365. + */ + int getDayOfYear() const noexcept; + + /** Returns the name of the weekday. + + @param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if + false, it'll return the full version, e.g. "Tuesday". + */ + String getWeekdayName (bool threeLetterVersion) const; + + /** Returns the number of hours since midnight. + + This is in 24-hour clock format, in the range 0 to 23. + + @see getHoursInAmPmFormat, isAfternoon + */ + int getHours() const noexcept; + + /** Returns true if the time is in the afternoon. + + So it returns true for "PM", false for "AM". + + @see getHoursInAmPmFormat, getHours + */ + bool isAfternoon() const noexcept; + + /** Returns the hours in 12-hour clock format. + + This will return a value 1 to 12 - use isAfternoon() to find out + whether this is in the afternoon or morning. + + @see getHours, isAfternoon + */ + int getHoursInAmPmFormat() const noexcept; + + /** Returns the number of minutes, 0 to 59. */ + int getMinutes() const noexcept; + + /** Returns the number of seconds, 0 to 59. */ + int getSeconds() const noexcept; + + /** Returns the number of milliseconds, 0 to 999. + + Unlike toMilliseconds(), this just returns the position within the + current second rather than the total number since the epoch. + + @see toMilliseconds + */ + int getMilliseconds() const noexcept; + + /** Returns true if the local timezone uses a daylight saving correction. */ + bool isDaylightSavingTime() const noexcept; + + /** Returns a 3-character string to indicate the local timezone. */ + String getTimeZone() const noexcept; + + //============================================================================== + /** Quick way of getting a string version of a date and time. + + For a more powerful way of formatting the date and time, see the formatted() method. + + @param includeDate whether to include the date in the string + @param includeTime whether to include the time in the string + @param includeSeconds if the time is being included, this provides an option not to include + the seconds in it + @param use24HourClock if the time is being included, sets whether to use am/pm or 24 + hour notation. + @see formatted + */ + String toString (bool includeDate, + bool includeTime, + bool includeSeconds = true, + bool use24HourClock = false) const noexcept; + + /** Converts this date/time to a string with a user-defined format. + + This uses the C strftime() function to format this time as a string. To save you + looking it up, these are the escape codes that strftime uses (other codes might + work on some platforms and not others, but these are the common ones): + + %a is replaced by the locale's abbreviated weekday name. + %A is replaced by the locale's full weekday name. + %b is replaced by the locale's abbreviated month name. + %B is replaced by the locale's full month name. + %c is replaced by the locale's appropriate date and time representation. + %d is replaced by the day of the month as a decimal number [01,31]. + %H is replaced by the hour (24-hour clock) as a decimal number [00,23]. + %I is replaced by the hour (12-hour clock) as a decimal number [01,12]. + %j is replaced by the day of the year as a decimal number [001,366]. + %m is replaced by the month as a decimal number [01,12]. + %M is replaced by the minute as a decimal number [00,59]. + %p is replaced by the locale's equivalent of either a.m. or p.m. + %S is replaced by the second as a decimal number [00,61]. + %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + %w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday. + %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. + %x is replaced by the locale's appropriate date representation. + %X is replaced by the locale's appropriate time representation. + %y is replaced by the year without century as a decimal number [00,99]. + %Y is replaced by the year with century as a decimal number. + %Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists. + %% is replaced by %. + + @see toString + */ + String formatted (const String& format) const; + + //============================================================================== + /** Adds a RelativeTime to this time. */ + Time& operator+= (RelativeTime delta) noexcept; + /** Subtracts a RelativeTime from this time. */ + Time& operator-= (RelativeTime delta) noexcept; + + //============================================================================== + /** Tries to set the computer's clock. + + @returns true if this succeeds, although depending on the system, the + application might not have sufficient privileges to do this. + */ + bool setSystemTimeToThisTime() const; + + //============================================================================== + /** Returns the name of a day of the week. + + @param dayNumber the day, 0 to 6 (0 = sunday, 1 = monday, etc) + @param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if + false, it'll return the full version, e.g. "Tuesday". + */ + static String getWeekdayName (int dayNumber, bool threeLetterVersion); + + /** Returns the name of one of the months. + + @param monthNumber the month, 0 to 11 + @param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false + it'll return the long form, e.g. "January" + */ + static String getMonthName (int monthNumber, bool threeLetterVersion); + + //============================================================================== + // Static methods for getting system timers directly.. + + /** Returns the current system time. + + Returns the number of milliseconds since midnight jan 1st 1970. + + Should be accurate to within a few millisecs, depending on platform, + hardware, etc. + */ + static int64 currentTimeMillis() noexcept; + + /** Returns the number of millisecs since a fixed event (usually system startup). + + This returns a monotonically increasing value which it unaffected by changes to the + system clock. It should be accurate to within a few millisecs, depending on platform, + hardware, etc. + + Being a 32-bit return value, it will of course wrap back to 0 after 2^32 seconds of + uptime, so be careful to take that into account. If you need a 64-bit time, you can + use currentTimeMillis() instead. + + @see getApproximateMillisecondCounter + */ + static uint32 getMillisecondCounter() noexcept; + + /** Returns the number of millisecs since a fixed event (usually system startup). + + This has the same function as getMillisecondCounter(), but returns a more accurate + value, using a higher-resolution timer if one is available. + + @see getMillisecondCounter + */ + static double getMillisecondCounterHiRes() noexcept; + + /** Waits until the getMillisecondCounter() reaches a given value. + + This will make the thread sleep as efficiently as it can while it's waiting. + */ + static void waitForMillisecondCounter (uint32 targetTime) noexcept; + + /** Less-accurate but faster version of getMillisecondCounter(). + + This will return the last value that getMillisecondCounter() returned, so doesn't + need to make a system call, but is less accurate - it shouldn't be more than + 100ms away from the correct time, though, so is still accurate enough for a + lot of purposes. + + @see getMillisecondCounter + */ + static uint32 getApproximateMillisecondCounter() noexcept; + + //============================================================================== + // High-resolution timers.. + + /** Returns the current high-resolution counter's tick-count. + + This is a similar idea to getMillisecondCounter(), but with a higher + resolution. + + @see getHighResolutionTicksPerSecond, highResolutionTicksToSeconds, + secondsToHighResolutionTicks + */ + static int64 getHighResolutionTicks() noexcept; + + /** Returns the resolution of the high-resolution counter in ticks per second. + + @see getHighResolutionTicks, highResolutionTicksToSeconds, + secondsToHighResolutionTicks + */ + static int64 getHighResolutionTicksPerSecond() noexcept; + + /** Converts a number of high-resolution ticks into seconds. + + @see getHighResolutionTicks, getHighResolutionTicksPerSecond, + secondsToHighResolutionTicks + */ + static double highResolutionTicksToSeconds (int64 ticks) noexcept; + + /** Converts a number seconds into high-resolution ticks. + + @see getHighResolutionTicks, getHighResolutionTicksPerSecond, + highResolutionTicksToSeconds + */ + static int64 secondsToHighResolutionTicks (double seconds) noexcept; + + /** Returns a Time based on the value of the __DATE__ macro when this module was compiled */ + static Time getCompilationDate(); + +private: + //============================================================================== + int64 millisSinceEpoch; +}; + +//============================================================================== +/** Adds a RelativeTime to a Time. */ +JUCE_API Time operator+ (Time time, RelativeTime delta) noexcept; +/** Adds a RelativeTime to a Time. */ +JUCE_API Time operator+ (RelativeTime delta, Time time) noexcept; + +/** Subtracts a RelativeTime from a Time. */ +JUCE_API Time operator- (Time time, RelativeTime delta) noexcept; +/** Returns the relative time difference between two times. */ +JUCE_API const RelativeTime operator- (Time time1, Time time2) noexcept; + +/** Compares two Time objects. */ +JUCE_API bool operator== (Time time1, Time time2) noexcept; +/** Compares two Time objects. */ +JUCE_API bool operator!= (Time time1, Time time2) noexcept; +/** Compares two Time objects. */ +JUCE_API bool operator< (Time time1, Time time2) noexcept; +/** Compares two Time objects. */ +JUCE_API bool operator<= (Time time1, Time time2) noexcept; +/** Compares two Time objects. */ +JUCE_API bool operator> (Time time1, Time time2) noexcept; +/** Compares two Time objects. */ +JUCE_API bool operator>= (Time time1, Time time2) noexcept; + + +#endif // JUCE_TIME_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.cpp b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.cpp new file mode 100644 index 0000000..f5a60c1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.cpp @@ -0,0 +1,260 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +UnitTest::UnitTest (const String& nm) + : name (nm), runner (nullptr) +{ + getAllTests().add (this); +} + +UnitTest::~UnitTest() +{ + getAllTests().removeFirstMatchingValue (this); +} + +Array& UnitTest::getAllTests() +{ + static Array tests; + return tests; +} + +void UnitTest::initialise() {} +void UnitTest::shutdown() {} + +void UnitTest::performTest (UnitTestRunner* const newRunner) +{ + jassert (newRunner != nullptr); + runner = newRunner; + + initialise(); + runTest(); + shutdown(); +} + +void UnitTest::logMessage (const String& message) +{ + // This method's only valid while the test is being run! + jassert (runner != nullptr); + + runner->logMessage (message); +} + +void UnitTest::beginTest (const String& testName) +{ + // This method's only valid while the test is being run! + jassert (runner != nullptr); + + runner->beginNewTest (this, testName); +} + +void UnitTest::expect (const bool result, const String& failureMessage) +{ + // This method's only valid while the test is being run! + jassert (runner != nullptr); + + if (result) + runner->addPass(); + else + runner->addFail (failureMessage); +} + +Random UnitTest::getRandom() const +{ + // This method's only valid while the test is being run! + jassert (runner != nullptr); + + return runner->randomForTest; +} + +//============================================================================== +UnitTestRunner::UnitTestRunner() + : currentTest (nullptr), + assertOnFailure (true), + logPasses (false) +{ +} + +UnitTestRunner::~UnitTestRunner() +{ +} + +void UnitTestRunner::setAssertOnFailure (bool shouldAssert) noexcept +{ + assertOnFailure = shouldAssert; +} + +void UnitTestRunner::setPassesAreLogged (bool shouldDisplayPasses) noexcept +{ + logPasses = shouldDisplayPasses; +} + +int UnitTestRunner::getNumResults() const noexcept +{ + return results.size(); +} + +const UnitTestRunner::TestResult* UnitTestRunner::getResult (int index) const noexcept +{ + return results [index]; +} + +void UnitTestRunner::resultsUpdated() +{ +} + +void UnitTestRunner::runTests (const Array& tests, int64 randomSeed) +{ + results.clear(); + resultsUpdated(); + + if (randomSeed == 0) + randomSeed = Random().nextInt (0x7ffffff); + + randomForTest = Random (randomSeed); + logMessage ("Random seed: 0x" + String::toHexString (randomSeed)); + + for (int i = 0; i < tests.size(); ++i) + { + if (shouldAbortTests()) + break; + + try + { + tests.getUnchecked(i)->performTest (this); + } + catch (...) + { + addFail ("An unhandled exception was thrown!"); + } + } + + endTest(); +} + +void UnitTestRunner::runAllTests (int64 randomSeed) +{ + runTests (UnitTest::getAllTests(), randomSeed); +} + +void UnitTestRunner::logMessage (const String& message) +{ + Logger::writeToLog (message); +} + +bool UnitTestRunner::shouldAbortTests() +{ + return false; +} + +void UnitTestRunner::beginNewTest (UnitTest* const test, const String& subCategory) +{ + endTest(); + currentTest = test; + + TestResult* const r = new TestResult(); + results.add (r); + r->unitTestName = test->getName(); + r->subcategoryName = subCategory; + r->passes = 0; + r->failures = 0; + + logMessage ("-----------------------------------------------------------------"); + logMessage ("Starting test: " + r->unitTestName + " / " + subCategory + "..."); + + resultsUpdated(); +} + +void UnitTestRunner::endTest() +{ + if (results.size() > 0) + { + TestResult* const r = results.getLast(); + + if (r->failures > 0) + { + String m ("FAILED!! "); + m << r->failures << (r->failures == 1 ? " test" : " tests") + << " failed, out of a total of " << (r->passes + r->failures); + + logMessage (String::empty); + logMessage (m); + logMessage (String::empty); + } + else + { + logMessage ("All tests completed successfully"); + } + } +} + +void UnitTestRunner::addPass() +{ + { + const ScopedLock sl (results.getLock()); + + TestResult* const r = results.getLast(); + jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! + + r->passes++; + + if (logPasses) + { + String message ("Test "); + message << (r->failures + r->passes) << " passed"; + logMessage (message); + } + } + + resultsUpdated(); +} + +void UnitTestRunner::addFail (const String& failureMessage) +{ + { + const ScopedLock sl (results.getLock()); + + TestResult* const r = results.getLast(); + jassert (r != nullptr); // You need to call UnitTest::beginTest() before performing any tests! + + r->failures++; + + String message ("!!! Test "); + message << (r->failures + r->passes) << " failed"; + + if (failureMessage.isNotEmpty()) + message << ": " << failureMessage; + + r->messages.add (message); + + logMessage (message); + } + + resultsUpdated(); + + if (assertOnFailure) { jassertfalse; } +} diff --git a/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h new file mode 100644 index 0000000..f5908c1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h @@ -0,0 +1,311 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_UNITTEST_H_INCLUDED +#define JUCE_UNITTEST_H_INCLUDED + +class UnitTestRunner; + + +//============================================================================== +/** + This is a base class for classes that perform a unit test. + + To write a test using this class, your code should look something like this: + + @code + class MyTest : public UnitTest + { + public: + MyTest() : UnitTest ("Foobar testing") {} + + void runTest() + { + beginTest ("Part 1"); + + expect (myFoobar.doesSomething()); + expect (myFoobar.doesSomethingElse()); + + beginTest ("Part 2"); + + expect (myOtherFoobar.doesSomething()); + expect (myOtherFoobar.doesSomethingElse()); + + ...etc.. + } + }; + + // Creating a static instance will automatically add the instance to the array + // returned by UnitTest::getAllTests(), so the test will be included when you call + // UnitTestRunner::runAllTests() + static MyTest test; + @endcode + + To run a test, use the UnitTestRunner class. + + @see UnitTestRunner +*/ +class JUCE_API UnitTest +{ +public: + //============================================================================== + /** Creates a test with the given name. */ + explicit UnitTest (const String& name); + + /** Destructor. */ + virtual ~UnitTest(); + + /** Returns the name of the test. */ + const String& getName() const noexcept { return name; } + + /** Runs the test, using the specified UnitTestRunner. + You shouldn't need to call this method directly - use + UnitTestRunner::runTests() instead. + */ + void performTest (UnitTestRunner* runner); + + /** Returns the set of all UnitTest objects that currently exist. */ + static Array& getAllTests(); + + //============================================================================== + /** You can optionally implement this method to set up your test. + This method will be called before runTest(). + */ + virtual void initialise(); + + /** You can optionally implement this method to clear up after your test has been run. + This method will be called after runTest() has returned. + */ + virtual void shutdown(); + + /** Implement this method in your subclass to actually run your tests. + + The content of your implementation should call beginTest() and expect() + to perform the tests. + */ + virtual void runTest() = 0; + + //============================================================================== + /** Tells the system that a new subsection of tests is beginning. + This should be called from your runTest() method, and may be called + as many times as you like, to demarcate different sets of tests. + */ + void beginTest (const String& testName); + + //============================================================================== + /** Checks that the result of a test is true, and logs this result. + + In your runTest() method, you should call this method for each condition that + you want to check, e.g. + + @code + void runTest() + { + beginTest ("basic tests"); + expect (x + y == 2); + expect (getThing() == someThing); + ...etc... + } + @endcode + + If testResult is true, a pass is logged; if it's false, a failure is logged. + If the failure message is specified, it will be written to the log if the test fails. + */ + void expect (bool testResult, const String& failureMessage = String::empty); + + /** Compares two values, and if they don't match, prints out a message containing the + expected and actual result values. + */ + template + void expectEquals (ValueType actual, ValueType expected, String failureMessage = String::empty) + { + const bool result = (actual == expected); + + if (! result) + { + if (failureMessage.isNotEmpty()) + failureMessage << " -- "; + + failureMessage << "Expected value: " << expected << ", Actual value: " << actual; + } + + expect (result, failureMessage); + } + + //============================================================================== + /** Writes a message to the test log. + This can only be called from within your runTest() method. + */ + void logMessage (const String& message); + + /** Returns a shared RNG that all unit tests should use. + If a test needs random numbers, it's important that when an error is found, the + exact circumstances can be re-created in order to re-test the problem, by + repeating the test with the same random seed value. + To make this possible, the UnitTestRunner class creates a master seed value + for the run, writes this number to the log, and then this method returns a + Random object based on that seed. All tests should only use this method to + create any Random objects that they need. + + Note that this method will return an identical object each time it's called + for a given run, so if you need several different Random objects, the best + way to do that is to call Random::combineSeed() on the result to permute it + with a constant value. + */ + Random getRandom() const; + +private: + //============================================================================== + const String name; + UnitTestRunner* runner; + + JUCE_DECLARE_NON_COPYABLE (UnitTest) +}; + + +//============================================================================== +/** + Runs a set of unit tests. + + You can instantiate one of these objects and use it to invoke tests on a set of + UnitTest objects. + + By using a subclass of UnitTestRunner, you can intercept logging messages and + perform custom behaviour when each test completes. + + @see UnitTest +*/ +class JUCE_API UnitTestRunner +{ +public: + //============================================================================== + /** */ + UnitTestRunner(); + + /** Destructor. */ + virtual ~UnitTestRunner(); + + /** Runs a set of tests. + + The tests are performed in order, and the results are logged. To run all the + registered UnitTest objects that exist, use runAllTests(). + + If you want to run the tests with a predetermined seed, you can pass that into + the randomSeed argument, or pass 0 to have a randomly-generated seed chosen. + */ + void runTests (const Array& tests, int64 randomSeed = 0); + + /** Runs all the UnitTest objects that currently exist. + This calls runTests() for all the objects listed in UnitTest::getAllTests(). + + If you want to run the tests with a predetermined seed, you can pass that into + the randomSeed argument, or pass 0 to have a randomly-generated seed chosen. + */ + void runAllTests (int64 randomSeed = 0); + + /** Sets a flag to indicate whether an assertion should be triggered if a test fails. + This is true by default. + */ + void setAssertOnFailure (bool shouldAssert) noexcept; + + /** Sets a flag to indicate whether successful tests should be logged. + By default, this is set to false, so that only failures will be displayed in the log. + */ + void setPassesAreLogged (bool shouldDisplayPasses) noexcept; + + //============================================================================== + /** Contains the results of a test. + + One of these objects is instantiated each time UnitTest::beginTest() is called, and + it contains details of the number of subsequent UnitTest::expect() calls that are + made. + */ + struct TestResult + { + /** The main name of this test (i.e. the name of the UnitTest object being run). */ + String unitTestName; + /** The name of the current subcategory (i.e. the name that was set when UnitTest::beginTest() was called). */ + String subcategoryName; + + /** The number of UnitTest::expect() calls that succeeded. */ + int passes; + /** The number of UnitTest::expect() calls that failed. */ + int failures; + + /** A list of messages describing the failed tests. */ + StringArray messages; + }; + + /** Returns the number of TestResult objects that have been performed. + @see getResult + */ + int getNumResults() const noexcept; + + /** Returns one of the TestResult objects that describes a test that has been run. + @see getNumResults + */ + const TestResult* getResult (int index) const noexcept; + +protected: + /** Called when the list of results changes. + You can override this to perform some sort of behaviour when results are added. + */ + virtual void resultsUpdated(); + + /** Logs a message about the current test progress. + By default this just writes the message to the Logger class, but you could override + this to do something else with the data. + */ + virtual void logMessage (const String& message); + + /** This can be overridden to let the runner know that it should abort the tests + as soon as possible, e.g. because the thread needs to stop. + */ + virtual bool shouldAbortTests(); + +private: + //============================================================================== + friend class UnitTest; + + UnitTest* currentTest; + String currentSubCategory; + OwnedArray results; + bool assertOnFailure, logPasses; + Random randomForTest; + + void beginNewTest (UnitTest* test, const String& subCategory); + void endTest(); + + void addPass(); + void addFail (const String& failureMessage); + + JUCE_DECLARE_NON_COPYABLE (UnitTestRunner) +}; + + +#endif // JUCE_UNITTEST_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp new file mode 100644 index 0000000..2eab527 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.cpp @@ -0,0 +1,891 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +XmlDocument::XmlDocument (const String& documentText) + : originalText (documentText), + input (nullptr), + outOfData (false), + errorOccurred (false), + needToLoadDTD (false), + ignoreEmptyTextElements (true) +{ +} + +XmlDocument::XmlDocument (const File& file) + : input (nullptr), + outOfData (false), + errorOccurred (false), + needToLoadDTD (false), + ignoreEmptyTextElements (true), + inputSource (new FileInputSource (file)) +{ +} + +XmlDocument::~XmlDocument() +{ +} + +XmlElement* XmlDocument::parse (const File& file) +{ + XmlDocument doc (file); + return doc.getDocumentElement(); +} + +XmlElement* XmlDocument::parse (const String& xmlData) +{ + XmlDocument doc (xmlData); + return doc.getDocumentElement(); +} + +void XmlDocument::setInputSource (InputSource* const newSource) noexcept +{ + inputSource = newSource; +} + +void XmlDocument::setEmptyTextElementsIgnored (const bool shouldBeIgnored) noexcept +{ + ignoreEmptyTextElements = shouldBeIgnored; +} + +namespace XmlIdentifierChars +{ + static bool isIdentifierCharSlow (const juce_wchar c) noexcept + { + return CharacterFunctions::isLetterOrDigit (c) + || c == '_' || c == '-' || c == ':' || c == '.'; + } + + static bool isIdentifierChar (const juce_wchar c) noexcept + { + static const uint32 legalChars[] = { 0, 0x7ff6000, 0x87fffffe, 0x7fffffe, 0 }; + + return ((int) c < (int) numElementsInArray (legalChars) * 32) ? ((legalChars [c >> 5] & (1 << (c & 31))) != 0) + : isIdentifierCharSlow (c); + } + + /*static void generateIdentifierCharConstants() + { + uint32 n[8] = { 0 }; + for (int i = 0; i < 256; ++i) + if (isIdentifierCharSlow (i)) + n[i >> 5] |= (1 << (i & 31)); + + String s; + for (int i = 0; i < 8; ++i) + s << "0x" << String::toHexString ((int) n[i]) << ", "; + + DBG (s); + }*/ + + static String::CharPointerType findEndOfToken (String::CharPointerType p) + { + while (isIdentifierChar (*p)) + ++p; + + return p; + } +} + +XmlElement* XmlDocument::getDocumentElement (const bool onlyReadOuterDocumentElement) +{ + if (originalText.isEmpty() && inputSource != nullptr) + { + ScopedPointer in (inputSource->createInputStream()); + + if (in != nullptr) + { + MemoryOutputStream data; + data.writeFromInputStream (*in, onlyReadOuterDocumentElement ? 8192 : -1); + + #if JUCE_STRING_UTF_TYPE == 8 + if (data.getDataSize() > 2) + { + data.writeByte (0); + const char* text = static_cast (data.getData()); + + if (CharPointer_UTF16::isByteOrderMarkBigEndian (text) + || CharPointer_UTF16::isByteOrderMarkLittleEndian (text)) + { + originalText = data.toString(); + } + else + { + if (CharPointer_UTF8::isByteOrderMark (text)) + text += 3; + + // parse the input buffer directly to avoid copying it all to a string.. + return parseDocumentElement (String::CharPointerType (text), onlyReadOuterDocumentElement); + } + } + #else + originalText = data.toString(); + #endif + } + } + + return parseDocumentElement (originalText.getCharPointer(), onlyReadOuterDocumentElement); +} + +const String& XmlDocument::getLastParseError() const noexcept +{ + return lastError; +} + +void XmlDocument::setLastError (const String& desc, const bool carryOn) +{ + lastError = desc; + errorOccurred = ! carryOn; +} + +String XmlDocument::getFileContents (const String& filename) const +{ + if (inputSource != nullptr) + { + const ScopedPointer in (inputSource->createInputStreamFor (filename.trim().unquoted())); + + if (in != nullptr) + return in->readEntireStreamAsString(); + } + + return String::empty; +} + +juce_wchar XmlDocument::readNextChar() noexcept +{ + const juce_wchar c = input.getAndAdvance(); + + if (c == 0) + { + outOfData = true; + --input; + } + + return c; +} + +XmlElement* XmlDocument::parseDocumentElement (String::CharPointerType textToParse, + const bool onlyReadOuterDocumentElement) +{ + input = textToParse; + errorOccurred = false; + outOfData = false; + needToLoadDTD = true; + + if (textToParse.isEmpty()) + { + lastError = "not enough input"; + } + else if (! parseHeader()) + { + lastError = "malformed header"; + } + else if (! parseDTD()) + { + lastError = "malformed DTD"; + } + else + { + lastError.clear(); + + ScopedPointer result (readNextElement (! onlyReadOuterDocumentElement)); + + if (! errorOccurred) + return result.release(); + } + + return nullptr; +} + +bool XmlDocument::parseHeader() +{ + skipNextWhiteSpace(); + + if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII (""))); + + if (headerEnd.isEmpty()) + return false; + + #if JUCE_DEBUG + const String encoding (String (input, headerEnd) + .fromFirstOccurrenceOf ("encoding", false, true) + .fromFirstOccurrenceOf ("=", false, false) + .fromFirstOccurrenceOf ("\"", false, false) + .upToFirstOccurrenceOf ("\"", false, false).trim()); + + /* If you load an XML document with a non-UTF encoding type, it may have been + loaded wrongly.. Since all the files are read via the normal juce file streams, + they're treated as UTF-8, so by the time it gets to the parser, the encoding will + have been lost. Best plan is to stick to utf-8 or if you have specific files to + read, use your own code to convert them to a unicode String, and pass that to the + XML parser. + */ + jassert (encoding.isEmpty() || encoding.startsWithIgnoreCase ("utf-")); + #endif + + input = headerEnd + 2; + skipNextWhiteSpace(); + } + + return true; +} + +bool XmlDocument::parseDTD() +{ + if (CharacterFunctions::compareUpTo (input, CharPointer_ASCII (" 0;) + { + const juce_wchar c = readNextChar(); + + if (outOfData) + return false; + + if (c == '<') + ++n; + else if (c == '>') + --n; + } + + dtdText = String (dtdStart, input - 1).trim(); + } + + return true; +} + +void XmlDocument::skipNextWhiteSpace() +{ + for (;;) + { + input = input.findEndOfWhitespace(); + + if (input.isEmpty()) + { + outOfData = true; + break; + } + + if (*input == '<') + { + if (input[1] == '!' + && input[2] == '-' + && input[3] == '-') + { + input += 4; + const int closeComment = input.indexOf (CharPointer_ASCII ("-->")); + + if (closeComment < 0) + { + outOfData = true; + break; + } + + input += closeComment + 3; + continue; + } + + if (input[1] == '?') + { + input += 2; + const int closeBracket = input.indexOf (CharPointer_ASCII ("?>")); + + if (closeBracket < 0) + { + outOfData = true; + break; + } + + input += closeBracket + 2; + continue; + } + } + + break; + } +} + +void XmlDocument::readQuotedString (String& result) +{ + const juce_wchar quote = readNextChar(); + + while (! outOfData) + { + const juce_wchar c = readNextChar(); + + if (c == quote) + break; + + --input; + + if (c == '&') + { + readEntity (result); + } + else + { + const String::CharPointerType start (input); + + for (;;) + { + const juce_wchar character = *input; + + if (character == quote) + { + result.appendCharPointer (start, input); + ++input; + return; + } + else if (character == '&') + { + result.appendCharPointer (start, input); + break; + } + else if (character == 0) + { + setLastError ("unmatched quotes", false); + outOfData = true; + break; + } + + ++input; + } + } + } +} + +XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements) +{ + XmlElement* node = nullptr; + + skipNextWhiteSpace(); + if (outOfData) + return nullptr; + + if (*input == '<') + { + ++input; + String::CharPointerType endOfToken (XmlIdentifierChars::findEndOfToken (input)); + + if (endOfToken == input) + { + // no tag name - but allow for a gap after the '<' before giving an error + skipNextWhiteSpace(); + endOfToken = XmlIdentifierChars::findEndOfToken (input); + + if (endOfToken == input) + { + setLastError ("tag name missing", false); + return node; + } + } + + node = new XmlElement (input, endOfToken); + input = endOfToken; + LinkedListPointer::Appender attributeAppender (node->attributes); + + // look for attributes + for (;;) + { + skipNextWhiteSpace(); + + const juce_wchar c = *input; + + // empty tag.. + if (c == '/' && input[1] == '>') + { + input += 2; + break; + } + + // parse the guts of the element.. + if (c == '>') + { + ++input; + + if (alsoParseSubElements) + readChildElements (*node); + + break; + } + + // get an attribute.. + if (XmlIdentifierChars::isIdentifierChar (c)) + { + String::CharPointerType attNameEnd (XmlIdentifierChars::findEndOfToken (input)); + + if (attNameEnd != input) + { + const String::CharPointerType attNameStart (input); + input = attNameEnd; + + skipNextWhiteSpace(); + + if (readNextChar() == '=') + { + skipNextWhiteSpace(); + + const juce_wchar nextChar = *input; + + if (nextChar == '"' || nextChar == '\'') + { + XmlElement::XmlAttributeNode* const newAtt + = new XmlElement::XmlAttributeNode (attNameStart, attNameEnd); + + readQuotedString (newAtt->value); + attributeAppender.append (newAtt); + continue; + } + } + else + { + setLastError ("expected '=' after attribute '" + + String (attNameStart, attNameEnd) + "'", false); + return node; + } + } + } + else + { + if (! outOfData) + setLastError ("illegal character found in " + node->getTagName() + ": '" + c + "'", false); + } + + break; + } + } + + return node; +} + +void XmlDocument::readChildElements (XmlElement& parent) +{ + LinkedListPointer::Appender childAppender (parent.firstChildElement); + + for (;;) + { + const String::CharPointerType preWhitespaceInput (input); + skipNextWhiteSpace(); + + if (outOfData) + { + setLastError ("unmatched tags", false); + break; + } + + if (*input == '<') + { + const juce_wchar c1 = input[1]; + + if (c1 == '/') + { + // our close tag.. + const int closeTag = input.indexOf ((juce_wchar) '>'); + + if (closeTag >= 0) + input += closeTag + 1; + + break; + } + + if (c1 == '!' && CharacterFunctions::compareUpTo (input + 2, CharPointer_ASCII ("[CDATA["), 7) == 0) + { + input += 9; + const String::CharPointerType inputStart (input); + + for (;;) + { + const juce_wchar c0 = *input; + + if (c0 == 0) + { + setLastError ("unterminated CDATA section", false); + outOfData = true; + break; + } + else if (c0 == ']' + && input[1] == ']' + && input[2] == '>') + { + childAppender.append (XmlElement::createTextElement (String (inputStart, input))); + input += 3; + break; + } + + ++input; + } + } + else + { + // this is some other element, so parse and add it.. + if (XmlElement* const n = readNextElement (true)) + childAppender.append (n); + else + break; + } + } + else // must be a character block + { + input = preWhitespaceInput; // roll back to include the leading whitespace + String textElementContent; + + for (;;) + { + const juce_wchar c = *input; + + if (c == '<') + { + if (input[1] == '!' && input[2] == '-' && input[3] == '-') + { + input += 4; + const int closeComment = input.indexOf (CharPointer_ASCII ("-->")); + + if (closeComment < 0) + { + setLastError ("unterminated comment", false); + outOfData = true; + return; + } + + input += closeComment + 3; + continue; + } + + break; + } + + if (c == 0) + { + setLastError ("unmatched tags", false); + outOfData = true; + return; + } + + if (c == '&') + { + String entity; + readEntity (entity); + + if (entity.startsWithChar ('<') && entity [1] != 0) + { + const String::CharPointerType oldInput (input); + const bool oldOutOfData = outOfData; + + input = entity.getCharPointer(); + outOfData = false; + + for (;;) + { + XmlElement* const n = readNextElement (true); + + if (n == nullptr) + break; + + childAppender.append (n); + } + + input = oldInput; + outOfData = oldOutOfData; + } + else + { + textElementContent += entity; + } + } + else + { + const String::CharPointerType start (input); + + for (;;) + { + const juce_wchar nextChar = *input; + + if (nextChar == '<' || nextChar == '&') + break; + + if (nextChar == 0) + { + setLastError ("unmatched tags", false); + outOfData = true; + return; + } + + ++input; + } + + textElementContent.appendCharPointer (start, input); + } + } + + if ((! ignoreEmptyTextElements) || textElementContent.containsNonWhitespaceChars()) + childAppender.append (XmlElement::createTextElement (textElementContent)); + } + } +} + +void XmlDocument::readEntity (String& result) +{ + // skip over the ampersand + ++input; + + if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("amp;"), 4) == 0) + { + input += 4; + result += '&'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("quot;"), 5) == 0) + { + input += 5; + result += '"'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("apos;"), 5) == 0) + { + input += 5; + result += '\''; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("lt;"), 3) == 0) + { + input += 3; + result += '<'; + } + else if (input.compareIgnoreCaseUpTo (CharPointer_ASCII ("gt;"), 3) == 0) + { + input += 3; + result += '>'; + } + else if (*input == '#') + { + int charCode = 0; + ++input; + + if (*input == 'x' || *input == 'X') + { + ++input; + int numChars = 0; + + while (input[0] != ';') + { + const int hexValue = CharacterFunctions::getHexDigitValue (input[0]); + + if (hexValue < 0 || ++numChars > 8) + { + setLastError ("illegal escape sequence", true); + break; + } + + charCode = (charCode << 4) | hexValue; + ++input; + } + + ++input; + } + else if (input[0] >= '0' && input[0] <= '9') + { + int numChars = 0; + + while (input[0] != ';') + { + if (++numChars > 12) + { + setLastError ("illegal escape sequence", true); + break; + } + + charCode = charCode * 10 + ((int) input[0] - '0'); + ++input; + } + + ++input; + } + else + { + setLastError ("illegal escape sequence", true); + result += '&'; + return; + } + + result << (juce_wchar) charCode; + } + else + { + const String::CharPointerType entityNameStart (input); + const int closingSemiColon = input.indexOf ((juce_wchar) ';'); + + if (closingSemiColon < 0) + { + outOfData = true; + result += '&'; + } + else + { + input += closingSemiColon + 1; + + result += expandExternalEntity (String (entityNameStart, (size_t) closingSemiColon)); + } + } +} + +String XmlDocument::expandEntity (const String& ent) +{ + if (ent.equalsIgnoreCase ("amp")) return String::charToString ('&'); + if (ent.equalsIgnoreCase ("quot")) return String::charToString ('"'); + if (ent.equalsIgnoreCase ("apos")) return String::charToString ('\''); + if (ent.equalsIgnoreCase ("lt")) return String::charToString ('<'); + if (ent.equalsIgnoreCase ("gt")) return String::charToString ('>'); + + if (ent[0] == '#') + { + const juce_wchar char1 = ent[1]; + + if (char1 == 'x' || char1 == 'X') + return String::charToString (static_cast (ent.substring (2).getHexValue32())); + + if (char1 >= '0' && char1 <= '9') + return String::charToString (static_cast (ent.substring (1).getIntValue())); + + setLastError ("illegal escape sequence", false); + return String::charToString ('&'); + } + + return expandExternalEntity (ent); +} + +String XmlDocument::expandExternalEntity (const String& entity) +{ + if (needToLoadDTD) + { + if (dtdText.isNotEmpty()) + { + dtdText = dtdText.trimCharactersAtEnd (">"); + tokenisedDTD.addTokens (dtdText, true); + + if (tokenisedDTD [tokenisedDTD.size() - 2].equalsIgnoreCase ("system") + && tokenisedDTD [tokenisedDTD.size() - 1].isQuotedString()) + { + const String fn (tokenisedDTD [tokenisedDTD.size() - 1]); + + tokenisedDTD.clear(); + tokenisedDTD.addTokens (getFileContents (fn), true); + } + else + { + tokenisedDTD.clear(); + const int openBracket = dtdText.indexOfChar ('['); + + if (openBracket > 0) + { + const int closeBracket = dtdText.lastIndexOfChar (']'); + + if (closeBracket > openBracket) + tokenisedDTD.addTokens (dtdText.substring (openBracket + 1, + closeBracket), true); + } + } + + for (int i = tokenisedDTD.size(); --i >= 0;) + { + if (tokenisedDTD[i].startsWithChar ('%') + && tokenisedDTD[i].endsWithChar (';')) + { + const String parsed (getParameterEntity (tokenisedDTD[i].substring (1, tokenisedDTD[i].length() - 1))); + StringArray newToks; + newToks.addTokens (parsed, true); + + tokenisedDTD.remove (i); + + for (int j = newToks.size(); --j >= 0;) + tokenisedDTD.insert (i, newToks[j]); + } + } + } + + needToLoadDTD = false; + } + + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity) + { + if (tokenisedDTD[i - 1].equalsIgnoreCase ("").trim().unquoted()); + + // check for sub-entities.. + int ampersand = ent.indexOfChar ('&'); + + while (ampersand >= 0) + { + const int semiColon = ent.indexOf (i + 1, ";"); + + if (semiColon < 0) + { + setLastError ("entity without terminating semi-colon", false); + break; + } + + const String resolved (expandEntity (ent.substring (i + 1, semiColon))); + + ent = ent.substring (0, ampersand) + + resolved + + ent.substring (semiColon + 1); + + ampersand = ent.indexOfChar (semiColon + 1, '&'); + } + + return ent; + } + } + } + + setLastError ("unknown entity", true); + + return entity; +} + +String XmlDocument::getParameterEntity (const String& entity) +{ + for (int i = 0; i < tokenisedDTD.size(); ++i) + { + if (tokenisedDTD[i] == entity + && tokenisedDTD [i - 1] == "%" + && tokenisedDTD [i - 2].equalsIgnoreCase ("")); + + if (ent.equalsIgnoreCase ("system")) + return getFileContents (tokenisedDTD [i + 2].trimCharactersAtEnd (">")); + + return ent.trim().unquoted(); + } + } + + return entity; +} diff --git a/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.h b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.h new file mode 100644 index 0000000..c032b85 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/xml/juce_XmlDocument.h @@ -0,0 +1,181 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_XMLDOCUMENT_H_INCLUDED +#define JUCE_XMLDOCUMENT_H_INCLUDED + + +//============================================================================== +/** + Parses a text-based XML document and creates an XmlElement object from it. + + The parser will parse DTDs to load external entities but won't + check the document for validity against the DTD. + + e.g. + @code + + XmlDocument myDocument (File ("myfile.xml")); + ScopedPointer mainElement (myDocument.getDocumentElement()); + + if (mainElement == nullptr) + { + String error = myDocument.getLastParseError(); + } + else + { + ..use the element + } + + @endcode + + Or you can use the static helper methods for quick parsing.. + + @code + ScopedPointer xml (XmlDocument::parse (myXmlFile)); + + if (xml != nullptr && xml->hasTagName ("foobar")) + { + ...etc + @endcode + + @see XmlElement +*/ +class JUCE_API XmlDocument +{ +public: + //============================================================================== + /** Creates an XmlDocument from the xml text. + The text doesn't actually get parsed until the getDocumentElement() method is called. + */ + XmlDocument (const String& documentText); + + /** Creates an XmlDocument from a file. + The text doesn't actually get parsed until the getDocumentElement() method is called. + */ + XmlDocument (const File& file); + + /** Destructor. */ + ~XmlDocument(); + + //============================================================================== + /** Creates an XmlElement object to represent the main document node. + + This method will do the actual parsing of the text, and if there's a + parse error, it may returns nullptr (and you can find out the error using + the getLastParseError() method). + + See also the parse() methods, which provide a shorthand way to quickly + parse a file or string. + + @param onlyReadOuterDocumentElement if true, the parser will only read the + first section of the file, and will only + return the outer document element - this + allows quick checking of large files to + see if they contain the correct type of + tag, without having to parse the entire file + @returns a new XmlElement which the caller will need to delete, or null if + there was an error. + @see getLastParseError + */ + XmlElement* getDocumentElement (bool onlyReadOuterDocumentElement = false); + + /** Returns the parsing error that occurred the last time getDocumentElement was called. + + @returns the error, or an empty string if there was no error. + */ + const String& getLastParseError() const noexcept; + + /** Sets an input source object to use for parsing documents that reference external entities. + + If the document has been created from a file, this probably won't be needed, but + if you're parsing some text and there might be a DTD that references external + files, you may need to create a custom input source that can retrieve the + other files it needs. + + The object that is passed-in will be deleted automatically when no longer needed. + + @see InputSource + */ + void setInputSource (InputSource* newSource) noexcept; + + /** Sets a flag to change the treatment of empty text elements. + + If this is true (the default state), then any text elements that contain only + whitespace characters will be ingored during parsing. If you need to catch + whitespace-only text, then you should set this to false before calling the + getDocumentElement() method. + */ + void setEmptyTextElementsIgnored (bool shouldBeIgnored) noexcept; + + //============================================================================== + /** A handy static method that parses a file. + This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. + @returns a new XmlElement which the caller will need to delete, or null if there was an error. + */ + static XmlElement* parse (const File& file); + + /** A handy static method that parses some XML data. + This is a shortcut for creating an XmlDocument object and calling getDocumentElement() on it. + @returns a new XmlElement which the caller will need to delete, or null if there was an error. + */ + static XmlElement* parse (const String& xmlData); + + + //============================================================================== +private: + String originalText; + String::CharPointerType input; + bool outOfData, errorOccurred; + + String lastError, dtdText; + StringArray tokenisedDTD; + bool needToLoadDTD, ignoreEmptyTextElements; + ScopedPointer inputSource; + + XmlElement* parseDocumentElement (String::CharPointerType, bool outer); + void setLastError (const String&, bool carryOn); + bool parseHeader(); + bool parseDTD(); + void skipNextWhiteSpace(); + juce_wchar readNextChar() noexcept; + XmlElement* readNextElement (bool alsoParseSubElements); + void readChildElements (XmlElement&); + void readQuotedString (String&); + void readEntity (String&); + + String getFileContents (const String&) const; + String expandEntity (const String&); + String expandExternalEntity (const String&); + String getParameterEntity (const String&); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlDocument) +}; + + +#endif // JUCE_XMLDOCUMENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.cpp b/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.cpp new file mode 100644 index 0000000..866d10a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.cpp @@ -0,0 +1,888 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept + : name (other.name), + value (other.value) +{ +} + +XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept + : name (n), value (v) +{ + #if JUCE_DEBUG + // this checks whether the attribute name string contains any illegal characters.. + for (String::CharPointerType t (name.getCharPointer()); ! t.isEmpty(); ++t) + jassert (t.isLetterOrDigit() || *t == '_' || *t == '-' || *t == ':'); + #endif +} + +XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd) + : name (nameStart, nameEnd) +{ +} + +//============================================================================== +static void sanityCheckTagName (const String& tag) +{ + (void) tag; + + // the tag name mustn't be empty, or it'll look like a text element! + jassert (tag.containsNonWhitespaceChars()); + + // The tag can't contain spaces or other characters that would create invalid XML! + jassert (! tag.containsAnyOf (" <>/&(){}")); +} + +XmlElement::XmlElement (const String& tag) + : tagName (StringPool::getGlobalPool().getPooledString (tag)) +{ + sanityCheckTagName (tagName); +} + +XmlElement::XmlElement (const char* tag) + : tagName (StringPool::getGlobalPool().getPooledString (tag)) +{ + sanityCheckTagName (tagName); +} + +XmlElement::XmlElement (StringRef tag) + : tagName (StringPool::getGlobalPool().getPooledString (tag)) +{ + sanityCheckTagName (tagName); +} + +XmlElement::XmlElement (const Identifier& tag) + : tagName (tag.toString()) +{ + sanityCheckTagName (tagName); +} + +XmlElement::XmlElement (String::CharPointerType tagNameStart, String::CharPointerType tagNameEnd) + : tagName (StringPool::getGlobalPool().getPooledString (tagNameStart, tagNameEnd)) +{ + sanityCheckTagName (tagName); +} + +XmlElement::XmlElement (int /*dummy*/) noexcept +{ +} + +XmlElement::XmlElement (const XmlElement& other) + : tagName (other.tagName) +{ + copyChildrenAndAttributesFrom (other); +} + +XmlElement& XmlElement::operator= (const XmlElement& other) +{ + if (this != &other) + { + removeAllAttributes(); + deleteAllChildElements(); + tagName = other.tagName; + copyChildrenAndAttributesFrom (other); + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +XmlElement::XmlElement (XmlElement&& other) noexcept + : nextListItem (static_cast&&> (other.nextListItem)), + firstChildElement (static_cast&&> (other.firstChildElement)), + attributes (static_cast&&> (other.attributes)), + tagName (static_cast (other.tagName)) +{ +} + +XmlElement& XmlElement::operator= (XmlElement&& other) noexcept +{ + jassert (this != &other); // hopefully the compiler should make this situation impossible! + + removeAllAttributes(); + deleteAllChildElements(); + + nextListItem = static_cast&&> (other.nextListItem); + firstChildElement = static_cast&&> (other.firstChildElement); + attributes = static_cast&&> (other.attributes); + tagName = static_cast (other.tagName); + + return *this; +} +#endif + +void XmlElement::copyChildrenAndAttributesFrom (const XmlElement& other) +{ + jassert (firstChildElement.get() == nullptr); + firstChildElement.addCopyOfList (other.firstChildElement); + + jassert (attributes.get() == nullptr); + attributes.addCopyOfList (other.attributes); +} + +XmlElement::~XmlElement() noexcept +{ + firstChildElement.deleteAll(); + attributes.deleteAll(); +} + +//============================================================================== +namespace XmlOutputFunctions +{ + #if 0 // (These functions are just used to generate the lookup table used below) + bool isLegalXmlCharSlow (const juce_wchar character) noexcept + { + if ((character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || (character >= '0' && character <= '9')) + return true; + + const char* t = " .,;:-()_+=?!'#@[]/\\*%~{}$|"; + + do + { + if (((juce_wchar) (uint8) *t) == character) + return true; + } + while (*++t != 0); + + return false; + } + + void generateLegalCharLookupTable() + { + uint8 n[32] = { 0 }; + for (int i = 0; i < 256; ++i) + if (isLegalXmlCharSlow (i)) + n[i >> 3] |= (1 << (i & 7)); + + String s; + for (int i = 0; i < 32; ++i) + s << (int) n[i] << ", "; + + DBG (s); + } + #endif + + static bool isLegalXmlChar (const uint32 c) noexcept + { + static const unsigned char legalChars[] = { 0, 0, 0, 0, 187, 255, 255, 175, 255, + 255, 255, 191, 254, 255, 255, 127 }; + return c < sizeof (legalChars) * 8 + && (legalChars [c >> 3] & (1 << (c & 7))) != 0; + } + + static void escapeIllegalXmlChars (OutputStream& outputStream, const String& text, const bool changeNewLines) + { + String::CharPointerType t (text.getCharPointer()); + + for (;;) + { + const uint32 character = (uint32) t.getAndAdvance(); + + if (character == 0) + break; + + if (isLegalXmlChar (character)) + { + outputStream << (char) character; + } + else + { + switch (character) + { + case '&': outputStream << "&"; break; + case '"': outputStream << """; break; + case '>': outputStream << ">"; break; + case '<': outputStream << "<"; break; + + case '\n': + case '\r': + if (! changeNewLines) + { + outputStream << (char) character; + break; + } + // Note: deliberate fall-through here! + default: + outputStream << "&#" << ((int) character) << ';'; + break; + } + } + } + } + + static void writeSpaces (OutputStream& out, const size_t numSpaces) + { + out.writeRepeatedByte (' ', numSpaces); + } +} + +void XmlElement::writeElementAsText (OutputStream& outputStream, + const int indentationLevel, + const int lineWrapLength) const +{ + using namespace XmlOutputFunctions; + + if (indentationLevel >= 0) + writeSpaces (outputStream, (size_t) indentationLevel); + + if (! isTextElement()) + { + outputStream.writeByte ('<'); + outputStream << tagName; + + { + const size_t attIndent = (size_t) (indentationLevel + tagName.length() + 1); + int lineLen = 0; + + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + { + if (lineLen > lineWrapLength && indentationLevel >= 0) + { + outputStream << newLine; + writeSpaces (outputStream, attIndent); + lineLen = 0; + } + + const int64 startPos = outputStream.getPosition(); + outputStream.writeByte (' '); + outputStream << att->name; + outputStream.write ("=\"", 2); + escapeIllegalXmlChars (outputStream, att->value, true); + outputStream.writeByte ('"'); + lineLen += (int) (outputStream.getPosition() - startPos); + } + } + + if (firstChildElement != nullptr) + { + outputStream.writeByte ('>'); + + bool lastWasTextNode = false; + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + { + if (child->isTextElement()) + { + escapeIllegalXmlChars (outputStream, child->getText(), false); + lastWasTextNode = true; + } + else + { + if (indentationLevel >= 0 && ! lastWasTextNode) + outputStream << newLine; + + child->writeElementAsText (outputStream, + lastWasTextNode ? 0 : (indentationLevel + (indentationLevel >= 0 ? 2 : 0)), lineWrapLength); + lastWasTextNode = false; + } + } + + if (indentationLevel >= 0 && ! lastWasTextNode) + { + outputStream << newLine; + writeSpaces (outputStream, (size_t) indentationLevel); + } + + outputStream.write ("'); + } + else + { + outputStream.write ("/>", 2); + } + } + else + { + escapeIllegalXmlChars (outputStream, getText(), false); + } +} + +String XmlElement::createDocument (StringRef dtdToUse, + const bool allOnOneLine, + const bool includeXmlHeader, + StringRef encodingType, + const int lineWrapLength) const +{ + MemoryOutputStream mem (2048); + writeToStream (mem, dtdToUse, allOnOneLine, includeXmlHeader, encodingType, lineWrapLength); + + return mem.toUTF8(); +} + +void XmlElement::writeToStream (OutputStream& output, + StringRef dtdToUse, + const bool allOnOneLine, + const bool includeXmlHeader, + StringRef encodingType, + const int lineWrapLength) const +{ + using namespace XmlOutputFunctions; + + if (includeXmlHeader) + { + output << ""; + + if (allOnOneLine) + output.writeByte (' '); + else + output << newLine << newLine; + } + + if (dtdToUse.isNotEmpty()) + { + output << dtdToUse; + + if (allOnOneLine) + output.writeByte (' '); + else + output << newLine; + } + + writeElementAsText (output, allOnOneLine ? -1 : 0, lineWrapLength); + + if (! allOnOneLine) + output << newLine; +} + +bool XmlElement::writeToFile (const File& file, + StringRef dtdToUse, + StringRef encodingType, + const int lineWrapLength) const +{ + TemporaryFile tempFile (file); + + { + FileOutputStream out (tempFile.getFile()); + + if (! out.openedOk()) + return false; + + writeToStream (out, dtdToUse, false, true, encodingType, lineWrapLength); + } + + return tempFile.overwriteTargetFileWithTemporary(); +} + +//============================================================================== +bool XmlElement::hasTagName (StringRef possibleTagName) const noexcept +{ + const bool matches = tagName.equalsIgnoreCase (possibleTagName); + + // XML tags should be case-sensitive, so although this method allows a + // case-insensitive match to pass, you should try to avoid this. + jassert ((! matches) || tagName == possibleTagName); + + return matches; +} + +String XmlElement::getNamespace() const +{ + return tagName.upToFirstOccurrenceOf (":", false, false); +} + +String XmlElement::getTagNameWithoutNamespace() const +{ + return tagName.fromLastOccurrenceOf (":", false, false); +} + +bool XmlElement::hasTagNameIgnoringNamespace (StringRef possibleTagName) const +{ + return hasTagName (possibleTagName) || getTagNameWithoutNamespace() == possibleTagName; +} + +XmlElement* XmlElement::getNextElementWithTagName (StringRef requiredTagName) const +{ + XmlElement* e = nextListItem; + + while (e != nullptr && ! e->hasTagName (requiredTagName)) + e = e->nextListItem; + + return e; +} + +//============================================================================== +int XmlElement::getNumAttributes() const noexcept +{ + return attributes.size(); +} + +const String& XmlElement::getAttributeName (const int index) const noexcept +{ + if (const XmlAttributeNode* const att = attributes [index]) + return att->name.toString(); + + return String::empty; +} + +const String& XmlElement::getAttributeValue (const int index) const noexcept +{ + if (const XmlAttributeNode* const att = attributes [index]) + return att->value; + + return String::empty; +} + +XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept +{ + for (XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + if (att->name == attributeName) + return att; + + return nullptr; +} + +bool XmlElement::hasAttribute (StringRef attributeName) const noexcept +{ + return getAttribute (attributeName) != nullptr; +} + +//============================================================================== +const String& XmlElement::getStringAttribute (StringRef attributeName) const noexcept +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + return att->value; + + return String::empty; +} + +String XmlElement::getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + return att->value; + + return defaultReturnValue; +} + +int XmlElement::getIntAttribute (StringRef attributeName, const int defaultReturnValue) const +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + return att->value.getIntValue(); + + return defaultReturnValue; +} + +double XmlElement::getDoubleAttribute (StringRef attributeName, const double defaultReturnValue) const +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + return att->value.getDoubleValue(); + + return defaultReturnValue; +} + +bool XmlElement::getBoolAttribute (StringRef attributeName, const bool defaultReturnValue) const +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + { + const juce_wchar firstChar = *(att->value.getCharPointer().findEndOfWhitespace()); + + return firstChar == '1' + || firstChar == 't' + || firstChar == 'y' + || firstChar == 'T' + || firstChar == 'Y'; + } + + return defaultReturnValue; +} + +bool XmlElement::compareAttribute (StringRef attributeName, + StringRef stringToCompareAgainst, + const bool ignoreCase) const noexcept +{ + if (const XmlAttributeNode* att = getAttribute (attributeName)) + return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst) + : att->value == stringToCompareAgainst; + + return false; +} + +//============================================================================== +void XmlElement::setAttribute (const Identifier& attributeName, const String& value) +{ + if (attributes == nullptr) + { + attributes = new XmlAttributeNode (attributeName, value); + } + else + { + for (XmlAttributeNode* att = attributes; ; att = att->nextListItem) + { + if (att->name == attributeName) + { + att->value = value; + break; + } + + if (att->nextListItem == nullptr) + { + att->nextListItem = new XmlAttributeNode (attributeName, value); + break; + } + } + } +} + +void XmlElement::setAttribute (const Identifier& attributeName, const int number) +{ + setAttribute (attributeName, String (number)); +} + +void XmlElement::setAttribute (const Identifier& attributeName, const double number) +{ + setAttribute (attributeName, String (number, 20)); +} + +void XmlElement::removeAttribute (const Identifier& attributeName) noexcept +{ + for (LinkedListPointer* att = &attributes; + att->get() != nullptr; + att = &(att->get()->nextListItem)) + { + if (att->get()->name == attributeName) + { + delete att->removeNext(); + break; + } + } +} + +void XmlElement::removeAllAttributes() noexcept +{ + attributes.deleteAll(); +} + +//============================================================================== +int XmlElement::getNumChildElements() const noexcept +{ + return firstChildElement.size(); +} + +XmlElement* XmlElement::getChildElement (const int index) const noexcept +{ + return firstChildElement [index].get(); +} + +XmlElement* XmlElement::getChildByName (StringRef childName) const noexcept +{ + jassert (! childName.isEmpty()); + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + if (child->hasTagName (childName)) + return child; + + return nullptr; +} + +XmlElement* XmlElement::getChildByAttribute (StringRef attributeName, StringRef attributeValue) const noexcept +{ + jassert (! attributeName.isEmpty()); + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + if (child->compareAttribute (attributeName, attributeValue)) + return child; + + return nullptr; +} + +void XmlElement::addChildElement (XmlElement* const newNode) noexcept +{ + if (newNode != nullptr) + { + // The element being added must not be a child of another node! + jassert (newNode->nextListItem == nullptr); + + firstChildElement.append (newNode); + } +} + +void XmlElement::insertChildElement (XmlElement* const newNode, int indexToInsertAt) noexcept +{ + if (newNode != nullptr) + { + // The element being added must not be a child of another node! + jassert (newNode->nextListItem == nullptr); + + firstChildElement.insertAtIndex (indexToInsertAt, newNode); + } +} + +void XmlElement::prependChildElement (XmlElement* newNode) noexcept +{ + if (newNode != nullptr) + { + // The element being added must not be a child of another node! + jassert (newNode->nextListItem == nullptr); + + firstChildElement.insertNext (newNode); + } +} + +XmlElement* XmlElement::createNewChildElement (StringRef childTagName) +{ + XmlElement* const newElement = new XmlElement (childTagName); + addChildElement (newElement); + return newElement; +} + +bool XmlElement::replaceChildElement (XmlElement* const currentChildElement, + XmlElement* const newNode) noexcept +{ + if (newNode != nullptr) + { + if (LinkedListPointer* const p = firstChildElement.findPointerTo (currentChildElement)) + { + if (currentChildElement != newNode) + delete p->replaceNext (newNode); + + return true; + } + } + + return false; +} + +void XmlElement::removeChildElement (XmlElement* const childToRemove, + const bool shouldDeleteTheChild) noexcept +{ + if (childToRemove != nullptr) + { + firstChildElement.remove (childToRemove); + + if (shouldDeleteTheChild) + delete childToRemove; + } +} + +bool XmlElement::isEquivalentTo (const XmlElement* const other, + const bool ignoreOrderOfAttributes) const noexcept +{ + if (this != other) + { + if (other == nullptr || tagName != other->tagName) + return false; + + if (ignoreOrderOfAttributes) + { + int totalAtts = 0; + + for (const XmlAttributeNode* att = attributes; att != nullptr; att = att->nextListItem) + { + if (! other->compareAttribute (att->name, att->value)) + return false; + + ++totalAtts; + } + + if (totalAtts != other->getNumAttributes()) + return false; + } + else + { + const XmlAttributeNode* thisAtt = attributes; + const XmlAttributeNode* otherAtt = other->attributes; + + for (;;) + { + if (thisAtt == nullptr || otherAtt == nullptr) + { + if (thisAtt == otherAtt) // both nullptr, so it's a match + break; + + return false; + } + + if (thisAtt->name != otherAtt->name + || thisAtt->value != otherAtt->value) + { + return false; + } + + thisAtt = thisAtt->nextListItem; + otherAtt = otherAtt->nextListItem; + } + } + + const XmlElement* thisChild = firstChildElement; + const XmlElement* otherChild = other->firstChildElement; + + for (;;) + { + if (thisChild == nullptr || otherChild == nullptr) + { + if (thisChild == otherChild) // both 0, so it's a match + break; + + return false; + } + + if (! thisChild->isEquivalentTo (otherChild, ignoreOrderOfAttributes)) + return false; + + thisChild = thisChild->nextListItem; + otherChild = otherChild->nextListItem; + } + } + + return true; +} + +void XmlElement::deleteAllChildElements() noexcept +{ + firstChildElement.deleteAll(); +} + +void XmlElement::deleteAllChildElementsWithTagName (StringRef name) noexcept +{ + for (XmlElement* child = firstChildElement; child != nullptr;) + { + XmlElement* const nextChild = child->nextListItem; + + if (child->hasTagName (name)) + removeChildElement (child, true); + + child = nextChild; + } +} + +bool XmlElement::containsChildElement (const XmlElement* const possibleChild) const noexcept +{ + return firstChildElement.contains (possibleChild); +} + +XmlElement* XmlElement::findParentElementOf (const XmlElement* const elementToLookFor) noexcept +{ + if (this == elementToLookFor || elementToLookFor == nullptr) + return nullptr; + + for (XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + { + if (elementToLookFor == child) + return this; + + if (XmlElement* const found = child->findParentElementOf (elementToLookFor)) + return found; + } + + return nullptr; +} + +void XmlElement::getChildElementsAsArray (XmlElement** elems) const noexcept +{ + firstChildElement.copyToArray (elems); +} + +void XmlElement::reorderChildElements (XmlElement** const elems, const int num) noexcept +{ + XmlElement* e = firstChildElement = elems[0]; + + for (int i = 1; i < num; ++i) + { + e->nextListItem = elems[i]; + e = e->nextListItem; + } + + e->nextListItem = nullptr; +} + +//============================================================================== +bool XmlElement::isTextElement() const noexcept +{ + return tagName.isEmpty(); +} + +static const String juce_xmltextContentAttributeName ("text"); + +const String& XmlElement::getText() const noexcept +{ + jassert (isTextElement()); // you're trying to get the text from an element that + // isn't actually a text element.. If this contains text sub-nodes, you + // probably want to use getAllSubText instead. + + return getStringAttribute (juce_xmltextContentAttributeName); +} + +void XmlElement::setText (const String& newText) +{ + if (isTextElement()) + setAttribute (juce_xmltextContentAttributeName, newText); + else + jassertfalse; // you can only change the text in a text element, not a normal one. +} + +String XmlElement::getAllSubText() const +{ + if (isTextElement()) + return getText(); + + if (getNumChildElements() == 1) + return firstChildElement.get()->getAllSubText(); + + MemoryOutputStream mem (1024); + + for (const XmlElement* child = firstChildElement; child != nullptr; child = child->nextListItem) + mem << child->getAllSubText(); + + return mem.toUTF8(); +} + +String XmlElement::getChildElementAllSubText (StringRef childTagName, const String& defaultReturnValue) const +{ + if (const XmlElement* const child = getChildByName (childTagName)) + return child->getAllSubText(); + + return defaultReturnValue; +} + +XmlElement* XmlElement::createTextElement (const String& text) +{ + XmlElement* const e = new XmlElement ((int) 0); + e->setAttribute (juce_xmltextContentAttributeName, text); + return e; +} + +void XmlElement::addTextElement (const String& text) +{ + addChildElement (createTextElement (text)); +} + +void XmlElement::deleteAllTextElements() noexcept +{ + for (XmlElement* child = firstChildElement; child != nullptr;) + { + XmlElement* const next = child->nextListItem; + + if (child->isTextElement()) + removeChildElement (child, true); + + child = next; + } +} diff --git a/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.h b/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.h new file mode 100644 index 0000000..2f03e93 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/xml/juce_XmlElement.h @@ -0,0 +1,770 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_XMLELEMENT_H_INCLUDED +#define JUCE_XMLELEMENT_H_INCLUDED + + +//============================================================================== +/** A handy macro to make it easy to iterate all the child elements in an XmlElement. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElement (*myParentXml, child) + { + if (child->hasTagName ("FOO")) + doSomethingWithXmlElement (child); + } + + @endcode + + @see forEachXmlChildElementWithTagName +*/ +#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ +\ + for (juce::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \ + childElementVariableName != nullptr; \ + childElementVariableName = childElementVariableName->getNextElement()) + +/** A macro that makes it easy to iterate all the child elements of an XmlElement + which have a specified tag. + + This does the same job as the forEachXmlChildElement macro, but only for those + elements that have a particular tag name. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. The requiredTagName is the + tag name to match. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") + { + // the child object is now guaranteed to be a element.. + doSomethingWithMYTAGElement (child); + } + + @endcode + + @see forEachXmlChildElement +*/ +#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ +\ + for (juce::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \ + childElementVariableName != nullptr; \ + childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName)) + + +//============================================================================== +/** Used to build a tree of elements representing an XML document. + + An XML document can be parsed into a tree of XmlElements, each of which + represents an XML tag structure, and which may itself contain other + nested elements. + + An XmlElement can also be converted back into a text document, and has + lots of useful methods for manipulating its attributes and sub-elements, + so XmlElements can actually be used as a handy general-purpose data + structure. + + Here's an example of parsing some elements: @code + // check we're looking at the right kind of document.. + if (myElement->hasTagName ("ANIMALS")) + { + // now we'll iterate its sub-elements looking for 'giraffe' elements.. + forEachXmlChildElement (*myElement, e) + { + if (e->hasTagName ("GIRAFFE")) + { + // found a giraffe, so use some of its attributes.. + + String giraffeName = e->getStringAttribute ("name"); + int giraffeAge = e->getIntAttribute ("age"); + bool isFriendly = e->getBoolAttribute ("friendly"); + } + } + } + @endcode + + And here's an example of how to create an XML document from scratch: @code + // create an outer node called "ANIMALS" + XmlElement animalsList ("ANIMALS"); + + for (int i = 0; i < numAnimals; ++i) + { + // create an inner element.. + XmlElement* giraffe = new XmlElement ("GIRAFFE"); + + giraffe->setAttribute ("name", "nigel"); + giraffe->setAttribute ("age", 10); + giraffe->setAttribute ("friendly", true); + + // ..and add our new element to the parent node + animalsList.addChildElement (giraffe); + } + + // now we can turn the whole thing into a text document.. + String myXmlDoc = animalsList.createDocument (String::empty); + @endcode + + @see XmlDocument +*/ +class JUCE_API XmlElement +{ +public: + //============================================================================== + /** Creates an XmlElement with this tag name. */ + explicit XmlElement (const String& tagName); + + /** Creates an XmlElement with this tag name. */ + explicit XmlElement (const char* tagName); + + /** Creates an XmlElement with this tag name. */ + explicit XmlElement (const Identifier& tagName); + + /** Creates an XmlElement with this tag name. */ + explicit XmlElement (StringRef tagName); + + /** Creates an XmlElement with this tag name. */ + XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd); + + /** Creates a (deep) copy of another element. */ + XmlElement (const XmlElement&); + + /** Creates a (deep) copy of another element. */ + XmlElement& operator= (const XmlElement&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + XmlElement (XmlElement&&) noexcept; + XmlElement& operator= (XmlElement&&) noexcept; + #endif + + /** Deleting an XmlElement will also delete all of its child elements. */ + ~XmlElement() noexcept; + + //============================================================================== + /** Compares two XmlElements to see if they contain the same text and attiributes. + + The elements are only considered equivalent if they contain the same attiributes + with the same values, and have the same sub-nodes. + + @param other the other element to compare to + @param ignoreOrderOfAttributes if true, this means that two elements with the + same attributes in a different order will be + considered the same; if false, the attributes must + be in the same order as well + */ + bool isEquivalentTo (const XmlElement* other, + bool ignoreOrderOfAttributes) const noexcept; + + //============================================================================== + /** Returns an XML text document that represents this element. + + The string returned can be parsed to recreate the same XmlElement that + was used to create it. + + @param dtdToUse the DTD to add to the document + @param allOnOneLine if true, this means that the document will not contain any + linefeeds, so it'll be smaller but not very easy to read. + @param includeXmlHeader whether to add the ", this would return "MOOSE". + @see hasTagName + */ + const String& getTagName() const noexcept { return tagName; } + + /** Returns the namespace portion of the tag-name, or an empty string if none is specified. */ + String getNamespace() const; + + /** Returns the part of the tag-name that follows any namespace declaration. */ + String getTagNameWithoutNamespace() const; + + /** Tests whether this element has a particular tag name. + @param possibleTagName the tag name you're comparing it with + @see getTagName + */ + bool hasTagName (StringRef possibleTagName) const noexcept; + + /** Tests whether this element has a particular tag name, ignoring any XML namespace prefix. + So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc. + @see getTagName + */ + bool hasTagNameIgnoringNamespace (StringRef possibleTagName) const; + + //============================================================================== + /** Returns the number of XML attributes this element contains. + + E.g. for an element such as \, this would + return 2. + */ + int getNumAttributes() const noexcept; + + /** Returns the name of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "antlers". + + @see getAttributeValue, getStringAttribute + */ + const String& getAttributeName (int attributeIndex) const noexcept; + + /** Returns the value of one of the elements attributes. + + E.g. for an element such as \, then + getAttributeName(1) would return "2". + + @see getAttributeName, getStringAttribute + */ + const String& getAttributeValue (int attributeIndex) const noexcept; + + //============================================================================== + // Attribute-handling methods.. + + /** Checks whether the element contains an attribute with a certain name. */ + bool hasAttribute (StringRef attributeName) const noexcept; + + /** Returns the value of a named attribute. + @param attributeName the name of the attribute to look up + */ + const String& getStringAttribute (StringRef attributeName) const noexcept; + + /** Returns the value of a named attribute. + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + */ + String getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const; + + /** Compares the value of a named attribute with a value passed-in. + + @param attributeName the name of the attribute to look up + @param stringToCompareAgainst the value to compare it with + @param ignoreCase whether the comparison should be case-insensitive + @returns true if the value of the attribute is the same as the string passed-in; + false if it's different (or if no such attribute exists) + */ + bool compareAttribute (StringRef attributeName, + StringRef stringToCompareAgainst, + bool ignoreCase = false) const noexcept; + + /** Returns the value of a named attribute as an integer. + + This will try to find the attribute and convert it to an integer (using + the String::getIntValue() method). + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + @see setAttribute + */ + int getIntAttribute (StringRef attributeName, int defaultReturnValue = 0) const; + + /** Returns the value of a named attribute as floating-point. + + This will try to find the attribute and convert it to an integer (using + the String::getDoubleValue() method). + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + @see setAttribute + */ + double getDoubleAttribute (StringRef attributeName, double defaultReturnValue = 0.0) const; + + /** Returns the value of a named attribute as a boolean. + + This will try to find the attribute and interpret it as a boolean. To do this, + it'll return true if the value is "1", "true", "y", etc, or false for other + values. + + @param attributeName the name of the attribute to look up + @param defaultReturnValue a value to return if the element doesn't have an attribute + with this name + */ + bool getBoolAttribute (StringRef attributeName, bool defaultReturnValue = false) const; + + /** Adds a named attribute to the element. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + @see removeAttribute + */ + void setAttribute (const Identifier& attributeName, const String& newValue); + + /** Adds a named attribute to the element, setting it to an integer value. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + */ + void setAttribute (const Identifier& attributeName, int newValue); + + /** Adds a named attribute to the element, setting it to a floating-point value. + + If the element already contains an attribute with this name, it's value will + be updated to the new value. If there's no such attribute yet, a new one will + be added. + + Note that there are other setAttribute() methods that take integers, + doubles, etc. to make it easy to store numbers. + + @param attributeName the name of the attribute to set + @param newValue the value to set it to + */ + void setAttribute (const Identifier& attributeName, double newValue); + + /** Removes a named attribute from the element. + + @param attributeName the name of the attribute to remove + @see removeAllAttributes + */ + void removeAttribute (const Identifier& attributeName) noexcept; + + /** Removes all attributes from this element. */ + void removeAllAttributes() noexcept; + + //============================================================================== + // Child element methods.. + + /** Returns the first of this element's sub-elements. + see getNextElement() for an example of how to iterate the sub-elements. + @see forEachXmlChildElement + */ + XmlElement* getFirstChildElement() const noexcept { return firstChildElement; } + + /** Returns the next of this element's siblings. + + This can be used for iterating an element's sub-elements, e.g. + @code + XmlElement* child = myXmlDocument->getFirstChildElement(); + + while (child != nullptr) + { + ...do stuff with this child.. + + child = child->getNextElement(); + } + @endcode + + Note that when iterating the child elements, some of them might be + text elements as well as XML tags - use isTextElement() to work this + out. + + Also, it's much easier and neater to use this method indirectly via the + forEachXmlChildElement macro. + + @returns the sibling element that follows this one, or zero if this is the last + element in its parent + + @see getNextElement, isTextElement, forEachXmlChildElement + */ + inline XmlElement* getNextElement() const noexcept { return nextListItem; } + + /** Returns the next of this element's siblings which has the specified tag + name. + + This is like getNextElement(), but will scan through the list until it + finds an element with the given tag name. + + @see getNextElement, forEachXmlChildElementWithTagName + */ + XmlElement* getNextElementWithTagName (StringRef requiredTagName) const; + + /** Returns the number of sub-elements in this element. + @see getChildElement + */ + int getNumChildElements() const noexcept; + + /** Returns the sub-element at a certain index. + + It's not very efficient to iterate the sub-elements by index - see + getNextElement() for an example of how best to iterate. + + @returns the n'th child of this element, or nullptr if the index is out-of-range + @see getNextElement, isTextElement, getChildByName + */ + XmlElement* getChildElement (int index) const noexcept; + + /** Returns the first sub-element with a given tag-name. + + @param tagNameToLookFor the tag name of the element you want to find + @returns the first element with this tag name, or nullptr if none is found + @see getNextElement, isTextElement, getChildElement, getChildByAttribute + */ + XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept; + + /** Returns the first sub-element which has an attribute that matches the given value. + + @param attributeName the name of the attribute to check + @param attributeValue the target value of the attribute + @returns the first element with this attribute value, or nullptr if none is found + @see getChildByName + */ + XmlElement* getChildByAttribute (StringRef attributeName, + StringRef attributeValue) const noexcept; + + //============================================================================== + /** Appends an element to this element's list of children. + + Child elements are deleted automatically when their parent is deleted, so + make sure the object that you pass in will not be deleted by anything else, + and make sure it's not already the child of another element. + + Note that due to the XmlElement using a singly-linked-list, prependChildElement() + is an O(1) operation, but addChildElement() is an O(N) operation - so if + you're adding large number of elements, you may prefer to do so in reverse order! + + @see getFirstChildElement, getNextElement, getNumChildElements, + getChildElement, removeChildElement + */ + void addChildElement (XmlElement* newChildElement) noexcept; + + /** Inserts an element into this element's list of children. + + Child elements are deleted automatically when their parent is deleted, so + make sure the object that you pass in will not be deleted by anything else, + and make sure it's not already the child of another element. + + @param newChildElement the element to add + @param indexToInsertAt the index at which to insert the new element - if this is + below zero, it will be added to the end of the list + @see addChildElement, insertChildElement + */ + void insertChildElement (XmlElement* newChildElement, + int indexToInsertAt) noexcept; + + /** Inserts an element at the beginning of this element's list of children. + + Child elements are deleted automatically when their parent is deleted, so + make sure the object that you pass in will not be deleted by anything else, + and make sure it's not already the child of another element. + + Note that due to the XmlElement using a singly-linked-list, prependChildElement() + is an O(1) operation, but addChildElement() is an O(N) operation - so if + you're adding large number of elements, you may prefer to do so in reverse order! + + @see addChildElement, insertChildElement + */ + void prependChildElement (XmlElement* newChildElement) noexcept; + + /** Creates a new element with the given name and returns it, after adding it + as a child element. + + This is a handy method that means that instead of writing this: + @code + XmlElement* newElement = new XmlElement ("foobar"); + myParentElement->addChildElement (newElement); + @endcode + + ..you could just write this: + @code + XmlElement* newElement = myParentElement->createNewChildElement ("foobar"); + @endcode + */ + XmlElement* createNewChildElement (StringRef tagName); + + /** Replaces one of this element's children with another node. + + If the current element passed-in isn't actually a child of this element, + this will return false and the new one won't be added. Otherwise, the + existing element will be deleted, replaced with the new one, and it + will return true. + */ + bool replaceChildElement (XmlElement* currentChildElement, + XmlElement* newChildNode) noexcept; + + /** Removes a child element. + + @param childToRemove the child to look for and remove + @param shouldDeleteTheChild if true, the child will be deleted, if false it'll + just remove it + */ + void removeChildElement (XmlElement* childToRemove, + bool shouldDeleteTheChild) noexcept; + + /** Deletes all the child elements in the element. + @see removeChildElement, deleteAllChildElementsWithTagName + */ + void deleteAllChildElements() noexcept; + + /** Deletes all the child elements with a given tag name. + @see removeChildElement + */ + void deleteAllChildElementsWithTagName (StringRef tagName) noexcept; + + /** Returns true if the given element is a child of this one. */ + bool containsChildElement (const XmlElement* possibleChild) const noexcept; + + /** Recursively searches all sub-elements of this one, looking for an element + which is the direct parent of the specified element. + + Because elements don't store a pointer to their parent, if you have one + and need to find its parent, the only way to do so is to exhaustively + search the whole tree for it. + + If the given child is found somewhere in this element's hierarchy, then + this method will return its parent. If not, it will return nullptr. + */ + XmlElement* findParentElementOf (const XmlElement* childToSearchFor) noexcept; + + //============================================================================== + /** Sorts the child elements using a comparator. + + This will use a comparator object to sort the elements into order. The object + passed must have a method of the form: + @code + int compareElements (const XmlElement* first, const XmlElement* second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param retainOrderOfEquivalentItems if this is true, then items which the comparator + says are equivalent will be kept in the order in which they + currently appear in the array. This is slower to perform, but + may be important in some cases. If it's false, a faster algorithm + is used, but equivalent elements may be rearranged. + */ + template + void sortChildElements (ElementComparator& comparator, + bool retainOrderOfEquivalentItems = false) + { + const int num = getNumChildElements(); + + if (num > 1) + { + HeapBlock elems ((size_t) num); + getChildElementsAsArray (elems); + sortArray (comparator, (XmlElement**) elems, 0, num - 1, retainOrderOfEquivalentItems); + reorderChildElements (elems, num); + } + } + + //============================================================================== + /** Returns true if this element is a section of text. + + Elements can either be an XML tag element or a secton of text, so this + is used to find out what kind of element this one is. + + @see getAllText, addTextElement, deleteAllTextElements + */ + bool isTextElement() const noexcept; + + /** Returns the text for a text element. + + Note that if you have an element like this: + + @codehello@endcode + + then calling getText on the "xyz" element won't return "hello", because that is + actually stored in a special text sub-element inside the xyz element. To get the + "hello" string, you could either call getText on the (unnamed) sub-element, or + use getAllSubText() to do this automatically. + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getAllSubText, getChildElementAllSubText + */ + const String& getText() const noexcept; + + /** Sets the text in a text element. + + Note that this is only a valid call if this element is a text element. If it's + not, then no action will be performed. If you're trying to add text inside a normal + element, you probably want to use addTextElement() instead. + */ + void setText (const String& newText); + + /** Returns all the text from this element's child nodes. + + This iterates all the child elements and when it finds text elements, + it concatenates their text into a big string which it returns. + + E.g. @codehello there world@endcode + if you called getAllSubText on the "xyz" element, it'd return "hello there world". + + Note that leading and trailing whitespace will be included in the string - to remove + if, just call String::trim() on the result. + + @see isTextElement, getChildElementAllSubText, getText, addTextElement + */ + String getAllSubText() const; + + /** Returns all the sub-text of a named child element. + + If there is a child element with the given tag name, this will return + all of its sub-text (by calling getAllSubText() on it). If there is + no such child element, this will return the default string passed-in. + + @see getAllSubText + */ + String getChildElementAllSubText (StringRef childTagName, + const String& defaultReturnValue) const; + + /** Appends a section of text to this element. + @see isTextElement, getText, getAllSubText + */ + void addTextElement (const String& text); + + /** Removes all the text elements from this element. + @see isTextElement, getText, getAllSubText, addTextElement + */ + void deleteAllTextElements() noexcept; + + /** Creates a text element that can be added to a parent element. */ + static XmlElement* createTextElement (const String& text); + + //============================================================================== +private: + struct XmlAttributeNode + { + XmlAttributeNode (const XmlAttributeNode&) noexcept; + XmlAttributeNode (const Identifier&, const String&) noexcept; + XmlAttributeNode (String::CharPointerType, String::CharPointerType); + + LinkedListPointer nextListItem; + Identifier name; + String value; + + private: + XmlAttributeNode& operator= (const XmlAttributeNode&) JUCE_DELETED_FUNCTION; + }; + + friend class XmlDocument; + friend class LinkedListPointer; + friend class LinkedListPointer; + friend class LinkedListPointer::Appender; + friend class NamedValueSet; + + LinkedListPointer nextListItem; + LinkedListPointer firstChildElement; + LinkedListPointer attributes; + String tagName; + + XmlElement (int) noexcept; + void copyChildrenAndAttributesFrom (const XmlElement&); + void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const; + void getChildElementsAsArray (XmlElement**) const noexcept; + void reorderChildElements (XmlElement**, int) noexcept; + XmlAttributeNode* getAttribute (StringRef) const noexcept; + + // Sigh.. L"" or _T("") string literals are problematic in general, and really inappropriate + // for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use + // UTF-16, cast it to a String and use the other constructor. + XmlElement (const wchar_t*) JUCE_DELETED_FUNCTION; + + JUCE_LEAK_DETECTOR (XmlElement) +}; + + +#endif // JUCE_XMLELEMENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp new file mode 100644 index 0000000..cb9a577 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.cpp @@ -0,0 +1,213 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class GZIPCompressorOutputStream::GZIPCompressorHelper +{ +public: + GZIPCompressorHelper (const int compressionLevel, const int windowBits) + : compLevel ((compressionLevel < 1 || compressionLevel > 9) ? -1 : compressionLevel), + isFirstDeflate (true), + streamIsValid (false), + finished (false) + { + using namespace zlibNamespace; + zerostruct (stream); + + streamIsValid = (deflateInit2 (&stream, compLevel, Z_DEFLATED, + windowBits != 0 ? windowBits : MAX_WBITS, + 8, strategy) == Z_OK); + } + + ~GZIPCompressorHelper() + { + if (streamIsValid) + zlibNamespace::deflateEnd (&stream); + } + + bool write (const uint8* data, size_t dataSize, OutputStream& out) + { + // When you call flush() on a gzip stream, the stream is closed, and you can + // no longer continue to write data to it! + jassert (! finished); + + while (dataSize > 0) + if (! doNextBlock (data, dataSize, out, Z_NO_FLUSH)) + return false; + + return true; + } + + void finish (OutputStream& out) + { + const uint8* data = nullptr; + size_t dataSize = 0; + + while (! finished) + doNextBlock (data, dataSize, out, Z_FINISH); + } + +private: + enum { strategy = 0 }; + + zlibNamespace::z_stream stream; + const int compLevel; + bool isFirstDeflate, streamIsValid, finished; + zlibNamespace::Bytef buffer[32768]; + + bool doNextBlock (const uint8*& data, size_t& dataSize, OutputStream& out, const int flushMode) + { + using namespace zlibNamespace; + + if (streamIsValid) + { + stream.next_in = const_cast (data); + stream.next_out = buffer; + stream.avail_in = (z_uInt) dataSize; + stream.avail_out = (z_uInt) sizeof (buffer); + + const int result = isFirstDeflate ? deflateParams (&stream, compLevel, strategy) + : deflate (&stream, flushMode); + isFirstDeflate = false; + + switch (result) + { + case Z_STREAM_END: + finished = true; + // Deliberate fall-through.. + case Z_OK: + { + data += dataSize - stream.avail_in; + dataSize = stream.avail_in; + const ssize_t bytesDone = (ssize_t) sizeof (buffer) - (ssize_t) stream.avail_out; + return bytesDone <= 0 || out.write (buffer, (size_t) bytesDone); + } + + default: + break; + } + } + + return false; + } + + JUCE_DECLARE_NON_COPYABLE (GZIPCompressorHelper) +}; + +//============================================================================== +GZIPCompressorOutputStream::GZIPCompressorOutputStream (OutputStream* const out, + const int compressionLevel, + const bool deleteDestStream, + const int windowBits) + : destStream (out, deleteDestStream), + helper (new GZIPCompressorHelper (compressionLevel, windowBits)) +{ + jassert (out != nullptr); +} + +GZIPCompressorOutputStream::~GZIPCompressorOutputStream() +{ + flush(); +} + +void GZIPCompressorOutputStream::flush() +{ + helper->finish (*destStream); + destStream->flush(); +} + +bool GZIPCompressorOutputStream::write (const void* destBuffer, size_t howMany) +{ + jassert (destBuffer != nullptr && (ssize_t) howMany >= 0); + + return helper->write (static_cast (destBuffer), howMany, *destStream); +} + +int64 GZIPCompressorOutputStream::getPosition() +{ + return destStream->getPosition(); +} + +bool GZIPCompressorOutputStream::setPosition (int64 /*newPosition*/) +{ + jassertfalse; // can't do it! + return false; +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class GZIPTests : public UnitTest +{ +public: + GZIPTests() : UnitTest ("GZIP") {} + + void runTest() + { + beginTest ("GZIP"); + Random rng = getRandom(); + + for (int i = 100; --i >= 0;) + { + MemoryOutputStream original, compressed, uncompressed; + + { + GZIPCompressorOutputStream zipper (&compressed, rng.nextInt (10), false); + + for (int j = rng.nextInt (100); --j >= 0;) + { + MemoryBlock data ((unsigned int) (rng.nextInt (2000) + 1)); + + for (int k = (int) data.getSize(); --k >= 0;) + data[k] = (char) rng.nextInt (255); + + original << data; + zipper << data; + } + } + + { + MemoryInputStream compressedInput (compressed.getData(), compressed.getDataSize(), false); + GZIPDecompressorInputStream unzipper (compressedInput); + + uncompressed << unzipper; + } + + expectEquals ((int) uncompressed.getDataSize(), + (int) original.getDataSize()); + + if (original.getDataSize() == uncompressed.getDataSize()) + expect (memcmp (uncompressed.getData(), + original.getData(), + original.getDataSize()) == 0); + } + } +}; + +static GZIPTests gzipTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h new file mode 100644 index 0000000..3309486 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPCompressorOutputStream.h @@ -0,0 +1,101 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED +#define JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + A stream which uses zlib to compress the data written into it. + + Important note: When you call flush() on a GZIPCompressorOutputStream, + the gzip data is closed - this means that no more data can be written to + it, and any subsequent attempts to call write() will cause an assertion. + + @see GZIPDecompressorInputStream +*/ +class JUCE_API GZIPCompressorOutputStream : public OutputStream +{ +public: + //============================================================================== + /** Creates a compression stream. + + @param destStream the stream into which the compressed data should + be written + @param compressionLevel how much to compress the data, between 1 and 9, where + 1 is the fastest/lowest compression, and 9 is the + slowest/highest compression. Any value outside this range + indicates that a default compression level should be used. + @param deleteDestStreamWhenDestroyed whether or not to delete the destStream object when + this stream is destroyed + @param windowBits this is used internally to change the window size used + by zlib - leave it as 0 unless you specifically need to set + its value for some reason + */ + GZIPCompressorOutputStream (OutputStream* destStream, + int compressionLevel = 0, + bool deleteDestStreamWhenDestroyed = false, + int windowBits = 0); + + /** Destructor. */ + ~GZIPCompressorOutputStream(); + + //============================================================================== + /** Flushes and closes the stream. + Note that unlike most streams, when you call flush() on a GZIPCompressorOutputStream, + the stream is closed - this means that no more data can be written to it, and any + subsequent attempts to call write() will cause an assertion. + */ + void flush(); + + int64 getPosition() override; + bool setPosition (int64) override; + bool write (const void*, size_t) override; + + /** These are preset values that can be used for the constructor's windowBits parameter. + For more info about this, see the zlib documentation for its windowBits parameter. + */ + enum WindowBitsValues + { + windowBitsRaw = -15, + windowBitsGZIP = 15 + 16 + }; + +private: + //============================================================================== + OptionalScopedPointer destStream; + + class GZIPCompressorHelper; + friend struct ContainerDeletePolicy; + ScopedPointer helper; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPCompressorOutputStream) +}; + +#endif // JUCE_GZIPCOMPRESSOROUTPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp new file mode 100644 index 0000000..71e0850 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.cpp @@ -0,0 +1,288 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4309 4305 4365) +#endif + +namespace zlibNamespace +{ + #if JUCE_INCLUDE_ZLIB_CODE + #if JUCE_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wconversion" + #pragma clang diagnostic ignored "-Wshadow" + #pragma clang diagnostic ignored "-Wdeprecated-register" + #endif + + #undef OS_CODE + #undef fdopen + #define ZLIB_INTERNAL + #define NO_DUMMY_DECL + #include "zlib/zlib.h" + #include "zlib/adler32.c" + #include "zlib/compress.c" + #undef DO1 + #undef DO8 + #include "zlib/crc32.c" + #include "zlib/deflate.c" + #include "zlib/inffast.c" + #undef PULLBYTE + #undef LOAD + #undef RESTORE + #undef INITBITS + #undef NEEDBITS + #undef DROPBITS + #undef BYTEBITS + #include "zlib/inflate.c" + #include "zlib/inftrees.c" + #include "zlib/trees.c" + #include "zlib/zutil.c" + #undef Byte + #undef fdopen + #undef local + #undef Freq + #undef Code + #undef Dad + #undef Len + + #if JUCE_CLANG + #pragma clang diagnostic pop + #endif + #else + #include JUCE_ZLIB_INCLUDE_PATH + #endif +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +// internal helper object that holds the zlib structures so they don't have to be +// included publicly. +class GZIPDecompressorInputStream::GZIPDecompressHelper +{ +public: + GZIPDecompressHelper (const bool dontWrap) + : finished (true), + needsDictionary (false), + error (true), + streamIsValid (false), + data (nullptr), + dataSize (0) + { + using namespace zlibNamespace; + zerostruct (stream); + streamIsValid = (inflateInit2 (&stream, dontWrap ? -MAX_WBITS : MAX_WBITS) == Z_OK); + finished = error = ! streamIsValid; + } + + ~GZIPDecompressHelper() + { + using namespace zlibNamespace; + if (streamIsValid) + inflateEnd (&stream); + } + + bool needsInput() const noexcept { return dataSize <= 0; } + + void setInput (uint8* const data_, const size_t size) noexcept + { + data = data_; + dataSize = size; + } + + int doNextBlock (uint8* const dest, const unsigned int destSize) + { + using namespace zlibNamespace; + if (streamIsValid && data != nullptr && ! finished) + { + stream.next_in = data; + stream.next_out = dest; + stream.avail_in = (z_uInt) dataSize; + stream.avail_out = (z_uInt) destSize; + + switch (inflate (&stream, Z_PARTIAL_FLUSH)) + { + case Z_STREAM_END: + finished = true; + // deliberate fall-through + case Z_OK: + data += dataSize - stream.avail_in; + dataSize = (z_uInt) stream.avail_in; + return (int) (destSize - stream.avail_out); + + case Z_NEED_DICT: + needsDictionary = true; + data += dataSize - stream.avail_in; + dataSize = (size_t) stream.avail_in; + break; + + case Z_DATA_ERROR: + case Z_MEM_ERROR: + error = true; + + default: + break; + } + } + + return 0; + } + + bool finished, needsDictionary, error, streamIsValid; + + enum { gzipDecompBufferSize = 32768 }; + +private: + zlibNamespace::z_stream stream; + uint8* data; + size_t dataSize; + + JUCE_DECLARE_NON_COPYABLE (GZIPDecompressHelper) +}; + +//============================================================================== +GZIPDecompressorInputStream::GZIPDecompressorInputStream (InputStream* const source, + const bool deleteSourceWhenDestroyed, + const bool noWrap_, + const int64 uncompressedStreamLength_) + : sourceStream (source, deleteSourceWhenDestroyed), + uncompressedStreamLength (uncompressedStreamLength_), + noWrap (noWrap_), + isEof (false), + activeBufferSize (0), + originalSourcePos (source->getPosition()), + currentPos (0), + buffer ((size_t) GZIPDecompressHelper::gzipDecompBufferSize), + helper (new GZIPDecompressHelper (noWrap_)) +{ +} + +GZIPDecompressorInputStream::GZIPDecompressorInputStream (InputStream& source) + : sourceStream (&source, false), + uncompressedStreamLength (-1), + noWrap (false), + isEof (false), + activeBufferSize (0), + originalSourcePos (source.getPosition()), + currentPos (0), + buffer ((size_t) GZIPDecompressHelper::gzipDecompBufferSize), + helper (new GZIPDecompressHelper (false)) +{ +} + +GZIPDecompressorInputStream::~GZIPDecompressorInputStream() +{ +} + +int64 GZIPDecompressorInputStream::getTotalLength() +{ + return uncompressedStreamLength; +} + +int GZIPDecompressorInputStream::read (void* destBuffer, int howMany) +{ + jassert (destBuffer != nullptr && howMany >= 0); + + if (howMany > 0 && ! isEof) + { + int numRead = 0; + uint8* d = static_cast (destBuffer); + + while (! helper->error) + { + const int n = helper->doNextBlock (d, (unsigned int) howMany); + currentPos += n; + + if (n == 0) + { + if (helper->finished || helper->needsDictionary) + { + isEof = true; + return numRead; + } + + if (helper->needsInput()) + { + activeBufferSize = sourceStream->read (buffer, (int) GZIPDecompressHelper::gzipDecompBufferSize); + + if (activeBufferSize > 0) + { + helper->setInput (buffer, (size_t) activeBufferSize); + } + else + { + isEof = true; + return numRead; + } + } + } + else + { + numRead += n; + howMany -= n; + d += n; + + if (howMany <= 0) + return numRead; + } + } + } + + return 0; +} + +bool GZIPDecompressorInputStream::isExhausted() +{ + return helper->error || isEof; +} + +int64 GZIPDecompressorInputStream::getPosition() +{ + return currentPos; +} + +bool GZIPDecompressorInputStream::setPosition (int64 newPos) +{ + if (newPos < currentPos) + { + // to go backwards, reset the stream and start again.. + isEof = false; + activeBufferSize = 0; + currentPos = 0; + helper = new GZIPDecompressHelper (noWrap); + + sourceStream->setPosition (originalSourcePos); + } + + skipNextBytes (newPos - currentPos); + return true; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h new file mode 100644 index 0000000..78a0b77 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_GZIPDecompressorInputStream.h @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED +#define JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED + + +//============================================================================== +/** + This stream will decompress a source-stream using zlib. + + Tip: if you're reading lots of small items from one of these streams, you + can increase the performance enormously by passing it through a + BufferedInputStream, so that it has to read larger blocks less often. + + @see GZIPCompressorOutputStream +*/ +class JUCE_API GZIPDecompressorInputStream : public InputStream +{ +public: + //============================================================================== + /** Creates a decompressor stream. + + @param sourceStream the stream to read from + @param deleteSourceWhenDestroyed whether or not to delete the source stream + when this object is destroyed + @param noWrap this is used internally by the ZipFile class + and should be ignored by user applications + @param uncompressedStreamLength if the creator knows the length that the + uncompressed stream will be, then it can supply this + value, which will be returned by getTotalLength() + */ + GZIPDecompressorInputStream (InputStream* sourceStream, + bool deleteSourceWhenDestroyed, + bool noWrap = false, + int64 uncompressedStreamLength = -1); + + /** Creates a decompressor stream. + + @param sourceStream the stream to read from - the source stream must not be + deleted until this object has been destroyed + */ + GZIPDecompressorInputStream (InputStream& sourceStream); + + /** Destructor. */ + ~GZIPDecompressorInputStream(); + + //============================================================================== + int64 getPosition() override; + bool setPosition (int64 pos) override; + int64 getTotalLength() override; + bool isExhausted() override; + int read (void* destBuffer, int maxBytesToRead) override; + +private: + //============================================================================== + OptionalScopedPointer sourceStream; + const int64 uncompressedStreamLength; + const bool noWrap; + bool isEof; + int activeBufferSize; + int64 originalSourcePos, currentPos; + HeapBlock buffer; + + class GZIPDecompressHelper; + friend struct ContainerDeletePolicy; + ScopedPointer helper; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GZIPDecompressorInputStream) +}; + +#endif // JUCE_GZIPDECOMPRESSORINPUTSTREAM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp new file mode 100644 index 0000000..a76d8b8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.cpp @@ -0,0 +1,608 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +class ZipFile::ZipEntryHolder +{ +public: + ZipEntryHolder (const char* const buffer, const int fileNameLen) + { + entry.filename = String::fromUTF8 (buffer + 46, fileNameLen); + + const int time = ByteOrder::littleEndianShort (buffer + 12); + const int date = ByteOrder::littleEndianShort (buffer + 14); + entry.fileTime = getFileTimeFromRawEncodings (time, date); + + compressed = ByteOrder::littleEndianShort (buffer + 10) != 0; + compressedSize = (size_t) ByteOrder::littleEndianInt (buffer + 20); + entry.uncompressedSize = ByteOrder::littleEndianInt (buffer + 24); + + streamOffset = ByteOrder::littleEndianInt (buffer + 42); + } + + struct FileNameComparator + { + static int compareElements (const ZipEntryHolder* first, const ZipEntryHolder* second) + { + return first->entry.filename.compare (second->entry.filename); + } + }; + + ZipEntry entry; + size_t streamOffset; + size_t compressedSize; + bool compressed; + +private: + static Time getFileTimeFromRawEncodings (int time, int date) + { + const int year = 1980 + (date >> 9); + const int month = ((date >> 5) & 15) - 1; + const int day = date & 31; + const int hours = time >> 11; + const int minutes = (time >> 5) & 63; + const int seconds = (time & 31) << 1; + + return Time (year, month, day, hours, minutes, seconds); + } +}; + +//============================================================================== +namespace +{ + int findEndOfZipEntryTable (InputStream& input, int& numEntries) + { + BufferedInputStream in (input, 8192); + + in.setPosition (in.getTotalLength()); + int64 pos = in.getPosition(); + const int64 lowestPos = jmax ((int64) 0, pos - 1024); + + char buffer [32] = { 0 }; + + while (pos > lowestPos) + { + in.setPosition (pos - 22); + pos = in.getPosition(); + memcpy (buffer + 22, buffer, 4); + + if (in.read (buffer, 22) != 22) + return 0; + + for (int i = 0; i < 22; ++i) + { + if (ByteOrder::littleEndianInt (buffer + i) == 0x06054b50) + { + in.setPosition (pos + i); + in.read (buffer, 22); + numEntries = ByteOrder::littleEndianShort (buffer + 10); + + return (int) ByteOrder::littleEndianInt (buffer + 16); + } + } + } + + return 0; + } +} + +//============================================================================== +class ZipFile::ZipInputStream : public InputStream +{ +public: + ZipInputStream (ZipFile& zf, ZipFile::ZipEntryHolder& zei) + : file (zf), + zipEntryHolder (zei), + pos (0), + headerSize (0), + inputStream (zf.inputStream) + { + if (zf.inputSource != nullptr) + { + inputStream = streamToDelete = file.inputSource->createInputStream(); + } + else + { + #if JUCE_DEBUG + zf.streamCounter.numOpenStreams++; + #endif + } + + char buffer [30]; + + if (inputStream != nullptr + && inputStream->setPosition ((int64) zei.streamOffset) + && inputStream->read (buffer, 30) == 30 + && ByteOrder::littleEndianInt (buffer) == 0x04034b50) + { + headerSize = 30 + ByteOrder::littleEndianShort (buffer + 26) + + ByteOrder::littleEndianShort (buffer + 28); + } + } + + ~ZipInputStream() + { + #if JUCE_DEBUG + if (inputStream != nullptr && inputStream == file.inputStream) + file.streamCounter.numOpenStreams--; + #endif + } + + int64 getTotalLength() + { + return (int64) zipEntryHolder.compressedSize; + } + + int read (void* buffer, int howMany) + { + if (headerSize <= 0) + return 0; + + howMany = (int) jmin ((int64) howMany, ((int64) zipEntryHolder.compressedSize) - pos); + + if (inputStream == nullptr) + return 0; + + int num; + + if (inputStream == file.inputStream) + { + const ScopedLock sl (file.lock); + inputStream->setPosition (pos + (int64) zipEntryHolder.streamOffset + headerSize); + num = inputStream->read (buffer, howMany); + } + else + { + inputStream->setPosition (pos + (int64) zipEntryHolder.streamOffset + headerSize); + num = inputStream->read (buffer, howMany); + } + + pos += num; + return num; + } + + bool isExhausted() + { + return headerSize <= 0 || pos >= (int64) zipEntryHolder.compressedSize; + } + + int64 getPosition() + { + return pos; + } + + bool setPosition (int64 newPos) + { + pos = jlimit ((int64) 0, (int64) zipEntryHolder.compressedSize, newPos); + return true; + } + +private: + ZipFile& file; + ZipEntryHolder zipEntryHolder; + int64 pos; + int headerSize; + InputStream* inputStream; + ScopedPointer streamToDelete; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipInputStream) +}; + + +//============================================================================== +ZipFile::ZipFile (InputStream* const stream, const bool deleteStreamWhenDestroyed) + : inputStream (stream) +{ + if (deleteStreamWhenDestroyed) + streamToDelete = inputStream; + + init(); +} + +ZipFile::ZipFile (InputStream& stream) + : inputStream (&stream) +{ + init(); +} + +ZipFile::ZipFile (const File& file) + : inputStream (nullptr), + inputSource (new FileInputSource (file)) +{ + init(); +} + +ZipFile::ZipFile (InputSource* const source) + : inputStream (nullptr), + inputSource (source) +{ + init(); +} + +ZipFile::~ZipFile() +{ + entries.clear(); +} + +#if JUCE_DEBUG +ZipFile::OpenStreamCounter::~OpenStreamCounter() +{ + /* If you hit this assertion, it means you've created a stream to read one of the items in the + zipfile, but you've forgotten to delete that stream object before deleting the file.. + Streams can't be kept open after the file is deleted because they need to share the input + stream that is managed by the ZipFile object. + */ + jassert (numOpenStreams == 0); +} +#endif + +//============================================================================== +int ZipFile::getNumEntries() const noexcept +{ + return entries.size(); +} + +const ZipFile::ZipEntry* ZipFile::getEntry (const int index) const noexcept +{ + if (ZipEntryHolder* const zei = entries [index]) + return &(zei->entry); + + return nullptr; +} + +int ZipFile::getIndexOfFileName (const String& fileName) const noexcept +{ + for (int i = 0; i < entries.size(); ++i) + if (entries.getUnchecked (i)->entry.filename == fileName) + return i; + + return -1; +} + +const ZipFile::ZipEntry* ZipFile::getEntry (const String& fileName) const noexcept +{ + return getEntry (getIndexOfFileName (fileName)); +} + +InputStream* ZipFile::createStreamForEntry (const int index) +{ + InputStream* stream = nullptr; + + if (ZipEntryHolder* const zei = entries[index]) + { + stream = new ZipInputStream (*this, *zei); + + if (zei->compressed) + { + stream = new GZIPDecompressorInputStream (stream, true, true, (int64) zei->entry.uncompressedSize); + + // (much faster to unzip in big blocks using a buffer..) + stream = new BufferedInputStream (stream, 32768, true); + } + } + + return stream; +} + +InputStream* ZipFile::createStreamForEntry (const ZipEntry& entry) +{ + for (int i = 0; i < entries.size(); ++i) + if (&entries.getUnchecked (i)->entry == &entry) + return createStreamForEntry (i); + + return nullptr; +} + +void ZipFile::sortEntriesByFilename() +{ + ZipEntryHolder::FileNameComparator sorter; + entries.sort (sorter); +} + +//============================================================================== +void ZipFile::init() +{ + ScopedPointer toDelete; + InputStream* in = inputStream; + + if (inputSource != nullptr) + { + in = inputSource->createInputStream(); + toDelete = in; + } + + if (in != nullptr) + { + int numEntries = 0; + int pos = findEndOfZipEntryTable (*in, numEntries); + + if (pos >= 0 && pos < in->getTotalLength()) + { + const int size = (int) (in->getTotalLength() - pos); + + in->setPosition (pos); + MemoryBlock headerData; + + if (in->readIntoMemoryBlock (headerData, size) == (size_t) size) + { + pos = 0; + + for (int i = 0; i < numEntries; ++i) + { + if (pos + 46 > size) + break; + + const char* const buffer = static_cast (headerData.getData()) + pos; + + const int fileNameLen = ByteOrder::littleEndianShort (buffer + 28); + + if (pos + 46 + fileNameLen > size) + break; + + entries.add (new ZipEntryHolder (buffer, fileNameLen)); + + pos += 46 + fileNameLen + + ByteOrder::littleEndianShort (buffer + 30) + + ByteOrder::littleEndianShort (buffer + 32); + } + } + } + } +} + +Result ZipFile::uncompressTo (const File& targetDirectory, + const bool shouldOverwriteFiles) +{ + for (int i = 0; i < entries.size(); ++i) + { + Result result (uncompressEntry (i, targetDirectory, shouldOverwriteFiles)); + if (result.failed()) + return result; + } + + return Result::ok(); +} + +Result ZipFile::uncompressEntry (const int index, + const File& targetDirectory, + bool shouldOverwriteFiles) +{ + const ZipEntryHolder* zei = entries.getUnchecked (index); + + #if JUCE_WINDOWS + const String entryPath (zei->entry.filename); + #else + const String entryPath (zei->entry.filename.replaceCharacter ('\\', '/')); + #endif + + const File targetFile (targetDirectory.getChildFile (entryPath)); + + if (entryPath.endsWithChar ('/') || entryPath.endsWithChar ('\\')) + return targetFile.createDirectory(); // (entry is a directory, not a file) + + ScopedPointer in (createStreamForEntry (index)); + + if (in == nullptr) + return Result::fail ("Failed to open the zip file for reading"); + + if (targetFile.exists()) + { + if (! shouldOverwriteFiles) + return Result::ok(); + + if (! targetFile.deleteFile()) + return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); + } + + if (! targetFile.getParentDirectory().createDirectory()) + return Result::fail ("Failed to create target folder: " + targetFile.getParentDirectory().getFullPathName()); + + { + FileOutputStream out (targetFile); + + if (out.failedToOpen()) + return Result::fail ("Failed to write to target file: " + targetFile.getFullPathName()); + + out << *in; + } + + targetFile.setCreationTime (zei->entry.fileTime); + targetFile.setLastModificationTime (zei->entry.fileTime); + targetFile.setLastAccessTime (zei->entry.fileTime); + + return Result::ok(); +} + + +//============================================================================= +class ZipFile::Builder::Item +{ +public: + Item (const File& f, InputStream* s, const int compression, const String& storedPath, Time time) + : file (f), stream (s), storedPathname (storedPath), + fileTime (time), compressionLevel (compression), + compressedSize (0), uncompressedSize (0), headerStart (0), checksum (0) + { + } + + bool writeData (OutputStream& target, const int64 overallStartPosition) + { + MemoryOutputStream compressedData ((size_t) file.getSize()); + + if (compressionLevel > 0) + { + GZIPCompressorOutputStream compressor (&compressedData, compressionLevel, false, + GZIPCompressorOutputStream::windowBitsRaw); + if (! writeSource (compressor)) + return false; + } + else + { + if (! writeSource (compressedData)) + return false; + } + + compressedSize = (int) compressedData.getDataSize(); + headerStart = (int) (target.getPosition() - overallStartPosition); + + target.writeInt (0x04034b50); + writeFlagsAndSizes (target); + target << storedPathname + << compressedData; + + return true; + } + + bool writeDirectoryEntry (OutputStream& target) + { + target.writeInt (0x02014b50); + target.writeShort (20); // version written + writeFlagsAndSizes (target); + target.writeShort (0); // comment length + target.writeShort (0); // start disk num + target.writeShort (0); // internal attributes + target.writeInt (0); // external attributes + target.writeInt (headerStart); + target << storedPathname; + + return true; + } + +private: + const File file; + ScopedPointer stream; + String storedPathname; + Time fileTime; + int compressionLevel, compressedSize, uncompressedSize, headerStart; + unsigned long checksum; + + static void writeTimeAndDate (OutputStream& target, Time t) + { + target.writeShort ((short) (t.getSeconds() + (t.getMinutes() << 5) + (t.getHours() << 11))); + target.writeShort ((short) (t.getDayOfMonth() + ((t.getMonth() + 1) << 5) + ((t.getYear() - 1980) << 9))); + } + + bool writeSource (OutputStream& target) + { + if (stream == nullptr) + { + stream = file.createInputStream(); + + if (stream == nullptr) + return false; + } + + checksum = 0; + uncompressedSize = 0; + const int bufferSize = 4096; + HeapBlock buffer (bufferSize); + + while (! stream->isExhausted()) + { + const int bytesRead = stream->read (buffer, bufferSize); + + if (bytesRead < 0) + return false; + + checksum = zlibNamespace::crc32 (checksum, buffer, (unsigned int) bytesRead); + target.write (buffer, (size_t) bytesRead); + uncompressedSize += bytesRead; + } + + stream = nullptr; + return true; + } + + void writeFlagsAndSizes (OutputStream& target) const + { + target.writeShort (10); // version needed + target.writeShort ((short) (1 << 11)); // this flag indicates UTF-8 filename encoding + target.writeShort (compressionLevel > 0 ? (short) 8 : (short) 0); + writeTimeAndDate (target, fileTime); + target.writeInt ((int) checksum); + target.writeInt (compressedSize); + target.writeInt (uncompressedSize); + target.writeShort ((short) storedPathname.toUTF8().sizeInBytes() - 1); + target.writeShort (0); // extra field length + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Item) +}; + +//============================================================================= +ZipFile::Builder::Builder() {} +ZipFile::Builder::~Builder() {} + +void ZipFile::Builder::addFile (const File& file, const int compression, const String& path) +{ + items.add (new Item (file, nullptr, compression, + path.isEmpty() ? file.getFileName() : path, + file.getLastModificationTime())); +} + +void ZipFile::Builder::addEntry (InputStream* stream, int compression, const String& path, Time time) +{ + jassert (stream != nullptr); // must not be null! + jassert (path.isNotEmpty()); + items.add (new Item (File(), stream, compression, path, time)); +} + +bool ZipFile::Builder::writeToStream (OutputStream& target, double* const progress) const +{ + const int64 fileStart = target.getPosition(); + + for (int i = 0; i < items.size(); ++i) + { + if (progress != nullptr) + *progress = (i + 0.5) / items.size(); + + if (! items.getUnchecked (i)->writeData (target, fileStart)) + return false; + } + + const int64 directoryStart = target.getPosition(); + + for (int i = 0; i < items.size(); ++i) + if (! items.getUnchecked (i)->writeDirectoryEntry (target)) + return false; + + const int64 directoryEnd = target.getPosition(); + + target.writeInt (0x06054b50); + target.writeShort (0); + target.writeShort (0); + target.writeShort ((short) items.size()); + target.writeShort ((short) items.size()); + target.writeInt ((int) (directoryEnd - directoryStart)); + target.writeInt ((int) (directoryStart - fileStart)); + target.writeShort (0); + + if (progress != nullptr) + *progress = 1.0; + + return true; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.h b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.h new file mode 100644 index 0000000..ccf9064 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/juce_ZipFile.h @@ -0,0 +1,259 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#ifndef JUCE_ZIPFILE_H_INCLUDED +#define JUCE_ZIPFILE_H_INCLUDED + + +//============================================================================== +/** + Decodes a ZIP file from a stream. + + This can enumerate the items in a ZIP file and can create suitable stream objects + to read each one. +*/ +class JUCE_API ZipFile +{ +public: + /** Creates a ZipFile based for a file. */ + explicit ZipFile (const File& file); + + //============================================================================== + /** Creates a ZipFile for a given stream. + + @param inputStream the stream to read from + @param deleteStreamWhenDestroyed if set to true, the object passed-in + will be deleted when this ZipFile object is deleted + */ + ZipFile (InputStream* inputStream, bool deleteStreamWhenDestroyed); + + /** Creates a ZipFile for a given stream. + The stream will not be owned or deleted by this class - if you want the ZipFile to + manage the stream's lifetime, use the other constructor. + */ + explicit ZipFile (InputStream& inputStream); + + /** Creates a ZipFile for an input source. + + The inputSource object will be owned by the zip file, which will delete + it later when not needed. + */ + explicit ZipFile (InputSource* inputSource); + + /** Destructor. */ + ~ZipFile(); + + //============================================================================== + /** + Contains information about one of the entries in a ZipFile. + + @see ZipFile::getEntry + */ + struct ZipEntry + { + /** The name of the file, which may also include a partial pathname. */ + String filename; + + /** The file's original size. */ + unsigned int uncompressedSize; + + /** The last time the file was modified. */ + Time fileTime; + }; + + //============================================================================== + /** Returns the number of items in the zip file. */ + int getNumEntries() const noexcept; + + /** Returns a structure that describes one of the entries in the zip file. + + This may return zero if the index is out of range. + + @see ZipFile::ZipEntry + */ + const ZipEntry* getEntry (int index) const noexcept; + + /** Returns the index of the first entry with a given filename. + + This uses a case-sensitive comparison to look for a filename in the + list of entries. It might return -1 if no match is found. + + @see ZipFile::ZipEntry + */ + int getIndexOfFileName (const String& fileName) const noexcept; + + /** Returns a structure that describes one of the entries in the zip file. + + This uses a case-sensitive comparison to look for a filename in the + list of entries. It might return 0 if no match is found. + + @see ZipFile::ZipEntry + */ + const ZipEntry* getEntry (const String& fileName) const noexcept; + + /** Sorts the list of entries, based on the filename. + */ + void sortEntriesByFilename(); + + //============================================================================== + /** Creates a stream that can read from one of the zip file's entries. + + The stream that is returned must be deleted by the caller (and + zero might be returned if a stream can't be opened for some reason). + + The stream must not be used after the ZipFile object that created + has been deleted. + */ + InputStream* createStreamForEntry (int index); + + /** Creates a stream that can read from one of the zip file's entries. + + The stream that is returned must be deleted by the caller (and + zero might be returned if a stream can't be opened for some reason). + + The stream must not be used after the ZipFile object that created + has been deleted. + */ + InputStream* createStreamForEntry (const ZipEntry& entry); + + //============================================================================== + /** Uncompresses all of the files in the zip file. + + This will expand all the entries into a target directory. The relative + paths of the entries are used. + + @param targetDirectory the root folder to uncompress to + @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones + @returns success if the file is successfully unzipped + */ + Result uncompressTo (const File& targetDirectory, + bool shouldOverwriteFiles = true); + + /** Uncompresses one of the entries from the zip file. + + This will expand the entry and write it in a target directory. The entry's path is used to + determine which subfolder of the target should contain the new file. + + @param index the index of the entry to uncompress - this must be a valid index + between 0 and (getNumEntries() - 1). + @param targetDirectory the root folder to uncompress into + @param shouldOverwriteFiles whether to overwrite existing files with similarly-named ones + @returns success if all the files are successfully unzipped + */ + Result uncompressEntry (int index, + const File& targetDirectory, + bool shouldOverwriteFiles = true); + + + //============================================================================== + /** Used to create a new zip file. + + Create a ZipFile::Builder object, and call its addFile() method to add some files, + then you can write it to a stream with write(). + + Currently this just stores the files with no compression.. That will be added + soon! + */ + class Builder + { + public: + Builder(); + ~Builder(); + + /** Adds a file while should be added to the archive. + The file isn't read immediately, all the files will be read later when the writeToStream() + method is called. + + The compressionLevel can be between 0 (no compression), and 9 (maximum compression). + If the storedPathName parameter is specified, you can customise the partial pathname that + will be stored for this file. + */ + void addFile (const File& fileToAdd, int compressionLevel, + const String& storedPathName = String::empty); + + /** Adds a file while should be added to the archive. + + @param streamToRead this stream isn't read immediately - a pointer to the stream is + stored, then used later when the writeToStream() method is called, and + deleted by the Builder object when no longer needed, so be very careful + about its lifetime and the lifetime of any objects on which it depends! + This must not be null. + @param compressionLevel this can be between 0 (no compression), and 9 (maximum compression). + @param storedPathName the partial pathname that will be stored for this file + @param fileModificationTime the timestamp that will be stored as the last modification time + of this entry + */ + void addEntry (InputStream* streamToRead, int compressionLevel, + const String& storedPathName, Time fileModificationTime); + + /** Generates the zip file, writing it to the specified stream. + If the progress parameter is non-null, it will be updated with an approximate + progress status between 0 and 1.0 + */ + bool writeToStream (OutputStream& target, double* progress) const; + + //============================================================================== + private: + class Item; + friend struct ContainerDeletePolicy; + OwnedArray items; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Builder) + }; + +private: + //============================================================================== + class ZipInputStream; + class ZipEntryHolder; + friend class ZipInputStream; + friend class ZipEntryHolder; + + OwnedArray entries; + CriticalSection lock; + InputStream* inputStream; + ScopedPointer streamToDelete; + ScopedPointer inputSource; + + #if JUCE_DEBUG + struct OpenStreamCounter + { + OpenStreamCounter() : numOpenStreams (0) {} + ~OpenStreamCounter(); + + int numOpenStreams; + }; + + OpenStreamCounter streamCounter; + #endif + + void init(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZipFile) +}; + +#endif // JUCE_ZIPFILE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/README b/JuceLibraryCode/modules/juce_core/zip/zlib/README new file mode 100644 index 0000000..758cc50 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/README @@ -0,0 +1,125 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.3 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). These documents are also available in other +formats from ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file example.c which also tests that the library +is working correctly. Another example is given in the file minigzip.c. The +compression library itself is composed of all source files except example.c and +minigzip.c. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile. In short "make test; make install" should work for most +machines. For Unix: "./configure; make test; make install". For MSDOS, use one +of the special makefiles such as Makefile.msc. For VMS, use make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://www.zlib.org or http://www.gzip.org/zlib/ Before reporting a problem, +please check this site to verify that you have the latest version of zlib; +otherwise get the latest version and check whether the problem still exists or +not. + +PLEASE read the zlib FAQ http://www.gzip.org/zlib/zlib_faq.html before asking +for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available in +http://dogma.net/markn/articles/zlibtool/zlibtool.htm + +The changes made in version 1.2.3 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory "contrib". + +A Java implementation of zlib is available in the Java Development Kit +http://java.sun.com/j2se/1.4.2/docs/api/java/util/zip/package-summary.html +See the zlib home page http://www.zlib.org for details. + +A Perl interface to zlib written by Paul Marquess is in the +CPAN (Comprehensive Perl Archive Network) sites +http://www.cpan.org/modules/by-module/Compress/ + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://www.python.org/doc/lib/module-zlib.html + +A zlib binding for TCL written by Andreas Kupries is +availlable at http://www.oche.de/~akupries/soft/trf/trf_zip.html + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS, BEOS and by some Mac compilers. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + +- When building a shared, i.e. dynamic library on Mac OS X, the library must be + installed before testing (do "make install" before "make test"), since the + library location is specified in the library. + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate + and zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; + they are too numerous to cite here. + +Copyright notice: + + (C) 1995-2004 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* +receiving lengthy legal documents to sign. The sources are provided +for free but without warranty of any kind. The library has been +entirely written by Jean-loup Gailly and Mark Adler; it does not +include third-party code. + +If you redistribute modified sources, we would appreciate that you include +in the file ChangeLog history information documenting your changes. Please +read the FAQ for more information on the distribution of modified source +versions. diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/adler32.c b/JuceLibraryCode/modules/juce_core/zip/zlib/adler32.c new file mode 100644 index 0000000..bf5cbd2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/adler32.c @@ -0,0 +1,143 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: adler32.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +#define BASE 65521UL /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware */ +#ifdef NO_DIVIDE +# define MOD(a) \ + do { \ + if (a >= (BASE << 16)) a -= (BASE << 16); \ + if (a >= (BASE << 15)) a -= (BASE << 15); \ + if (a >= (BASE << 14)) a -= (BASE << 14); \ + if (a >= (BASE << 13)) a -= (BASE << 13); \ + if (a >= (BASE << 12)) a -= (BASE << 12); \ + if (a >= (BASE << 11)) a -= (BASE << 11); \ + if (a >= (BASE << 10)) a -= (BASE << 10); \ + if (a >= (BASE << 9)) a -= (BASE << 9); \ + if (a >= (BASE << 8)) a -= (BASE << 8); \ + if (a >= (BASE << 7)) a -= (BASE << 7); \ + if (a >= (BASE << 6)) a -= (BASE << 6); \ + if (a >= (BASE << 5)) a -= (BASE << 5); \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD4(a) \ + do { \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD4(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len) +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD4(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2) +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* the derivation of this formula is left as an exercise for the reader */ + rem = (unsigned)(len2 % BASE); + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 > BASE) sum1 -= BASE; + if (sum1 > BASE) sum1 -= BASE; + if (sum2 > (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 > BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/compress.c b/JuceLibraryCode/modules/juce_core/zip/zlib/compress.c new file mode 100644 index 0000000..05c49c5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/compress.c @@ -0,0 +1,70 @@ +/* compress.c -- compress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: compress.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ +int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen, int level) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; +#ifdef MAXSEG_64K + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen) +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} + +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound (uLong sourceLen) +{ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + 11; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.c b/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.c new file mode 100644 index 0000000..500d536 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.c @@ -0,0 +1,407 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id: crc32.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) (((w)>>24)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32 (unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = (u4) (crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24]) +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = (u4) (crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8)); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = (u4) (crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8)); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = (u4) (crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]) +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big (unsigned long crc, const unsigned char FAR *buf, unsigned len) +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = (u4) (crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8)); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = (u4) (crc_table[4][(c >> 24) ^ (u4) *buf++] ^ (c << 8)); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times (unsigned long *mat, unsigned long vec) +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square (unsigned long *square, unsigned long *mat) +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine (uLong crc1, uLong crc2, z_off_t len2) +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case */ + if (len2 == 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320L; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.h b/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.h new file mode 100644 index 0000000..5de49bc --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.c b/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.c new file mode 100644 index 0000000..4b8bda5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.c @@ -0,0 +1,1679 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in http://www.ietf.org/rfc/rfc1951.txt + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id: deflate.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#include "deflate.h" + +const char deflate_copyright[] = + " deflate 1.2.3 Copyright 1995-2005 Jean-loup Gailly "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST +local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifndef FASTEST +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif +#endif +local uInt longest_match_fast OF((deflate_state *s, IPos cur_match)); + +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +#ifndef NO_DUMMY_DECL +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ +#endif + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to to INSERT_STRING are made with consecutive + * input characters and the first MIN_MATCH bytes of str are valid + * (except for the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* ========================================================================= */ +int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version, int stream_size) +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_ (z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size) +{ + deflate_state *s; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + + s->wrap = wrap; + s->gzhead = Z_NULL; + s->w_bits = windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + s->status = FINISH_STATE; + strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength) +{ + deflate_state *s; + uInt length = dictLength; + uInt n; + IPos hash_head = 0; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || + strm->state->wrap == 2 || + (strm->state->wrap == 1 && strm->state->status != INIT_STATE)) + return Z_STREAM_ERROR; + + s = strm->state; + if (s->wrap) + strm->adler = adler32(strm->adler, dictionary, dictLength); + + if (length < MIN_MATCH) return Z_OK; + if (length > MAX_DIST(s)) { + length = MAX_DIST(s); + dictionary += dictLength - length; /* use the tail of the dictionary */ + } + zmemcpy(s->window, dictionary, length); + s->strstart = length; + s->block_start = (long)length; + + /* Insert all strings in the hash table (except for the last two bytes). + * s->lookahead stays null, so s->ins_h will be recomputed at the next + * call of fill_window. + */ + s->ins_h = s->window[0]; + UPDATE_HASH(s, s->ins_h, s->window[1]); + for (n = 0; n <= length - MIN_MATCH; n++) { + INSERT_STRING(s, n, hash_head); + } + + (void) hash_head; /* to make compiler happy */ + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (z_streamp strm) +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) { + return Z_STREAM_ERROR; + } + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ + } + s->status = s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + lm_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head) +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (strm->state->wrap != 2) return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime (z_streamp strm, int bits, int value) +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + strm->state->bi_valid = bits; + strm->state->bi_buf = (ush)(value & ((1 << bits) - 1)); + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams (z_streamp strm, int level, int strategy) +{ + deflate_state *s; + compress_func func; + int err = Z_OK; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if (func != configuration_table[level].func && strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_PARTIAL_FLUSH); + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} + +/* ========================================================================= */ +int ZEXPORT deflateTune (z_streamp strm, int good_length, int max_lazy, int nice_length, int max_chain) +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = good_length; + s->max_lazy_match = max_lazy; + s->nice_match = nice_length; + s->max_chain_length = max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds + * for every combination of windowBits and memLevel, as well as wrap. + * But even the conservative upper bound of about 14% expansion does not + * seem onerous for output buffer allocation. + */ +uLong ZEXPORT deflateBound (z_streamp strm, uLong sourceLen) +{ + deflate_state *s; + uLong destLen; + + /* conservative upper bound */ + destLen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 11; + + /* if can't get parameters, return conservative bound */ + if (strm == Z_NULL || strm->state == Z_NULL) + return destLen; + + /* if not default parameters, return conservative bound */ + s = strm->state; + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return destLen; + + /* default settings: return tight bound for that case */ + return compressBound(sourceLen); +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (deflate_state *s, uInt b) +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending (z_streamp strm) +{ + unsigned len = strm->state->pending; + + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, strm->state->pending_out, len); + strm->next_out += len; + strm->state->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + strm->state->pending -= len; + if (strm->state->pending == 0) { + strm->state->pending_out = strm->state->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate (z_streamp strm, int flush) +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_FINISH || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; + + /* Write the header */ + if (s->status == INIT_STATE) { +#ifdef GZIP + if (s->wrap == 2) { + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + else +#endif + { + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + } + } +#ifdef GZIP + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + + while (s->gzindex < (s->gzhead->extra_len & 0xffff)) { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) + break; + } + put_byte(s, s->gzhead->extra[s->gzindex]); + s->gzindex++; + } + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (s->gzindex == s->gzhead->extra_len) { + s->gzindex = 0; + s->status = NAME_STATE; + } + } + else + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) { + s->gzindex = 0; + s->status = COMMENT_STATE; + } + } + else + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; + + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) + s->status = HCRC_STATE; + } + else + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) + flush_pending(strm); + if (s->pending + 2 <= s->pending_buf_size) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + } + } + else + s->status = BUSY_STATE; + } +#endif + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm->avail_in == 0 && flush <= old_flush && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = (*(configuration_table[s->level].func))(s, flush); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + Assert(strm->avail_out > 0, "bug2"); + + if (flush != Z_FINISH) return Z_OK; + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (z_streamp strm) +{ + int status; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + + status = strm->state->status; + if (status != INIT_STATE && + status != EXTRA_STATE && + status != NAME_STATE && + status != COMMENT_STATE && + status != HCRC_STATE && + status != BUSY_STATE && + status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (z_streamp dest, z_streamp source) +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy(dest, source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy(ds, ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local int read_buf (z_streamp strm, Bytef *buf, unsigned size) +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, strm->next_in, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, strm->next_in, len); + } +#endif + zmemcpy(buf, strm->next_in, len); + strm->next_in += len; + strm->total_in += len; + + return (int)len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (deflate_state *s) +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif +} + +#ifndef FASTEST +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +local uInt longest_match(deflate_state *s, IPos cur_match) +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} +#endif /* ASMV */ +#endif /* FASTEST */ + +/* --------------------------------------------------------------------------- + * Optimized version for level == 1 or strategy == Z_RLE only + */ +local uInt longest_match_fast (deflate_state *s, IPos cur_match) +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; +} + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(deflate_state *s, IPos start, IPos match, int length) +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif /* DEBUG */ + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window (deflate_state *s) +{ + register unsigned n, m; + register Posf *p; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + /* %%% avoid this when Z_RLE */ + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif + more += wsize; + } + if (s->strm->avail_in == 0) return; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead >= MIN_MATCH) { + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, eof) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (eof)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, eof) { \ + FLUSH_BLOCK_ONLY(s, eof); \ + if (s->strm->avail_out == 0) return (eof) ? finish_started : need_more; \ +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +local block_state deflate_stored(deflate_state *s, int flush) +{ + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + ulg max_block_size = 0xffff; + ulg max_start; + + if (max_block_size > s->pending_buf_size - 5) { + max_block_size = s->pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s->lookahead <= 1) { + + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); + + fill_window(s); + if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; + + if (s->lookahead == 0) break; /* flush the current block */ + } + Assert(s->block_start >= 0L, "block gone"); + + s->strstart += s->lookahead; + s->lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; + if (s->strstart == 0 || (ulg)s->strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; + FLUSH_BLOCK(s, 0); + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { + FLUSH_BLOCK(s, 0); + } + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(deflate_state *s, int flush) +{ + IPos hash_head = NIL; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ +#ifdef FASTEST + if ((s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) || + (s->strategy == Z_RLE && s->strstart - hash_head == 1)) { + s->match_length = longest_match_fast (s, hash_head); + } +#else + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } +#endif + /* longest_match() or longest_match_fast() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +#ifndef FASTEST +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(deflate_state *s, int flush) +{ + IPos hash_head = NIL; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + if (s->strategy != Z_HUFFMAN_ONLY && s->strategy != Z_RLE) { + s->match_length = longest_match (s, hash_head); + } else if (s->strategy == Z_RLE && s->strstart - hash_head == 1) { + s->match_length = longest_match_fast (s, hash_head); + } + /* longest_match() or longest_match_fast() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif /* FASTEST */ + +#if 0 +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + uInt run; /* length of run */ + uInt max; /* maximum length of run */ + uInt prev; /* byte at distance one to match */ + Bytef *scan; /* scan for end of run */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest encodable run. + */ + if (s->lookahead < MAX_MATCH) { + fill_window(s); + if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + run = 0; + if (s->strstart > 0) { /* if there is a previous byte, that is */ + max = s->lookahead < MAX_MATCH ? s->lookahead : MAX_MATCH; + scan = s->window + s->strstart - 1; + prev = *scan++; + do { + if (*scan++ != prev) + break; + } while (++run < max); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (run >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, run); + _tr_tally_dist(s, 1, run - MIN_MATCH, bflush); + s->lookahead -= run; + s->strstart += run; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} +#endif diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.h b/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.h new file mode 100644 index 0000000..a3bae5f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/deflate.h @@ -0,0 +1,333 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-2004 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: deflate.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +#include "zutil.h" + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + +#define NO_DUMMY_DECL + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define INIT_STATE 42 +#define EXTRA_STATE 69 +#define NAME_STATE 73 +#define COMMENT_STATE 91 +#define HCRC_STATE 103 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + uInt pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + uInt gzindex; /* where in extra, name, or comment */ + Byte method; /* STORED (for zip only) or DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to supress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + int last_eob_len; /* bit length of EOB code for last block */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + + /* in trees.c */ +void _tr_init OF((deflate_state *s)); +int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); +void _tr_align OF((deflate_state *s)); +void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch _length_code[]; + extern uch _dist_code[]; +#else + extern const uch _length_code[]; + extern const uch _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif /* DEFLATE_H */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/infback.c b/JuceLibraryCode/modules/juce_core/zip/zlib/infback.c new file mode 100644 index 0000000..c259d01 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/infback.c @@ -0,0 +1,611 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* function prototypes */ +local void fixedtables1 OF((struct inflate_state FAR *state)); + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(z_streamp strm, int windowBits, unsigned char FAR *window, const char *version, int stream_size) +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->write = 0; + state->whave = 0; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables1 (struct inflate_state FAR *state) +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(z_streamp strm, in_func in, void FAR *in_desc, out_func out, void FAR *out_desc) +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code thisx; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables1(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.val < 16) { + NEEDBITS(thisx.bits); + DROPBITS(thisx.bits); + state->lens[state->have++] = thisx.val; + } + else { + if (thisx.val == 16) { + NEEDBITS(thisx.bits + 2); + DROPBITS(thisx.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (thisx.val == 17) { + NEEDBITS(thisx.bits + 3); + DROPBITS(thisx.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(thisx.bits + 7); + DROPBITS(thisx.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.op && (thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + state->length = (unsigned)thisx.val; + + /* process literal */ + if (thisx.op == 0) { + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (thisx.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (thisx.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(thisx.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + thisx = state->distcode[BITS(state->distbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if ((thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + if (thisx.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)thisx.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(thisx.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left)) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd (z_streamp strm) +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.c b/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.c new file mode 100644 index 0000000..6ac383d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.c @@ -0,0 +1,316 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast (z_streamp strm, unsigned start) +{ + struct inflate_state FAR *state; + unsigned char FAR *in; /* local strm->next_in */ + unsigned char FAR *last; /* while in < last, enough input available */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code thisx; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + write = state->write; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + thisx = lcode[hold & lmask]; + dolen: + op = (unsigned)(thisx.bits); + hold >>= op; + bits -= op; + op = (unsigned)(thisx.op); + if (op == 0) { /* literal */ + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + PUP(out) = (unsigned char)(thisx.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(thisx.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + thisx = dcode[hold & dmask]; + dodist: + op = (unsigned)(thisx.bits); + hold >>= op; + bits -= op; + op = (unsigned)(thisx.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(thisx.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + from = window - OFF; + if (write == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (write < op) { /* wrap around window */ + from += wsize + write - op; + op -= write; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (write < len) { /* some from start of window */ + op = write; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += write - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + thisx = dcode[thisx.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + thisx = lcode[thisx.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and write == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.h b/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.h new file mode 100644 index 0000000..614fa78 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inffixed.h b/JuceLibraryCode/modules/juce_core/zip/zlib/inffixed.h new file mode 100644 index 0000000..423d5c5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. It + is part of the implementation of the compression library and + is subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.c b/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.c new file mode 100644 index 0000000..eef0d37 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.c @@ -0,0 +1,1339 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common write == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, unsigned out)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateReset (z_streamp strm) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + strm->adler = 1; /* to support ill-conceived Java test suite */ + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->wsize = 0; + state->whave = 0; + state->write = 0; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflatePrime (z_streamp strm, int bits, int value) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1 << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, int stream_size) +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + if (windowBits < 0) { + state->wrap = 0; + windowBits = -windowBits; + } + else { + state->wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) windowBits &= 15; +#endif + } + if (windowBits < 8 || windowBits > 15) { + ZFREE(strm, state); + strm->state = Z_NULL; + return Z_STREAM_ERROR; + } + state->wbits = (unsigned)windowBits; + state->window = Z_NULL; + return inflateReset(strm); +} + +int ZEXPORT inflateInit_ (z_streamp strm, const char *version, int stream_size) +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables (struct inflate_state FAR *state) +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, + state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow (z_streamp strm, unsigned out) +{ + struct inflate_state FAR *state; + unsigned copy, dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->write = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + copy = out - strm->avail_out; + if (copy >= state->wsize) { + zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + state->write = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->write; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->write, strm->next_out - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, strm->next_out - copy, copy); + state->write = copy; + state->whave = state->wsize; + } + else { + state->write += dist; + if (state->write == state->wsize) state->write = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Reverse the bytes in a 32-bit value */ +#define REVERSE(q) \ + ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate (z_streamp strm, int flush) +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code thisx; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = REVERSE(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.val < 16) { + NEEDBITS(thisx.bits); + DROPBITS(thisx.bits); + state->lens[state->have++] = thisx.val; + } + else { + if (thisx.val == 16) { + NEEDBITS(thisx.bits + 2); + DROPBITS(thisx.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (thisx.val == 17) { + NEEDBITS(thisx.bits + 3); + DROPBITS(thisx.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(thisx.bits + 7); + DROPBITS(thisx.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + break; + } + for (;;) { + thisx = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if (thisx.op && (thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + state->length = (unsigned)thisx.val; + if ((int)(thisx.op) == 0) { + Tracevv((stderr, thisx.val >= 0x20 && thisx.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", thisx.val)); + state->mode = LIT; + break; + } + if (thisx.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + if (thisx.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(thisx.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->mode = DIST; + case DIST: + for (;;) { + thisx = state->distcode[BITS(state->distbits)]; + if ((unsigned)(thisx.bits) <= bits) break; + PULLBYTE(); + } + if ((thisx.op & 0xf0) == 0) { + last = thisx; + for (;;) { + thisx = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + thisx.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(thisx.bits); + if (thisx.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)thisx.val; + state->extra = (unsigned)(thisx.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + if (state->offset > state->whave + out - left) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->write) { + copy -= state->write; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->write - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + REVERSE(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) + if (updatewindow(strm, out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd (z_streamp strm) +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateSetDictionary (z_streamp strm, const Bytef *dictionary, uInt dictLength) +{ + struct inflate_state FAR *state; + unsigned long id_; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary id */ + if (state->mode == DICT) { + id_ = adler32(0L, Z_NULL, 0); + id_ = adler32(id_, dictionary, dictLength); + if (id_ != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window */ + if (updatewindow(strm, strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + if (dictLength > state->wsize) { + zmemcpy(state->window, dictionary + dictLength - state->wsize, + state->wsize); + state->whave = state->wsize; + } + else { + zmemcpy(state->window + state->wsize - dictLength, dictionary, + dictLength); + state->whave = dictLength; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader (z_streamp strm, gz_headerp head) +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch (unsigned FAR *have, unsigned char FAR *buf, unsigned len) +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync (z_streamp strm) +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint (z_streamp strm) +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(z_streamp dest, z_streamp source) +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.h b/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.h new file mode 100644 index 0000000..31b1279 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inflate.h @@ -0,0 +1,121 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFLATE_H_ +#define _INFLATE_H_ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN, /* i: waiting for length/lit code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD or MEM mode -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME + NAME -> COMMENT -> HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or CHECK + STORED -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; + + +#endif diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.c b/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.c new file mode 100644 index 0000000..dfc0aa7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.c @@ -0,0 +1,328 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.3 Copyright 1995-2005 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table (codetype type, + unsigned short FAR *lens, + unsigned codes, + code FAR * FAR *table, + unsigned FAR *bits, + unsigned short FAR *work) +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code thisx; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 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, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 201, 196}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 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, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + thisx.op = (unsigned char)64; /* invalid code marker */ + thisx.bits = (unsigned char)1; + thisx.val = (unsigned short)0; + *(*table)++ = thisx; /* make a table to force an error */ + *(*table)++ = thisx; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked when a LENS table is being made + against the space in *table, ENOUGH, minus the maximum space needed by + the worst case distance code, MAXD. This should never happen, but the + sufficiency of ENOUGH has not been proven exhaustively, hence the check. + This assumes that when type == LENS, bits == 9. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + thisx.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + thisx.op = (unsigned char)0; + thisx.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + thisx.op = (unsigned char)(extra[work[sym]]); + thisx.val = base[work[sym]]; + } + else { + thisx.op = (unsigned char)(32 + 64); /* end of block */ + thisx.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = thisx; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + thisx.op = (unsigned char)64; /* invalid code marker */ + thisx.bits = (unsigned char)(len - drop); + thisx.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + thisx.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = thisx; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.h b/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.h new file mode 100644 index 0000000..ea64af0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/inftrees.h @@ -0,0 +1,61 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFTREES_H_ +#define _INFTREES_H_ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1444 code structures (852 for length/literals + and 592 for distances, the latter actually the result of an + exhaustive search). The true maximum is not known, but the value + below is more than safe. */ +#define ENOUGH 2048 +#define MAXD 592 + +/* Type of code to build for inftable() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); + + +#endif diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/trees.c b/JuceLibraryCode/modules/juce_core/zip/zlib/trees.c new file mode 100644 index 0000000..36a124d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/trees.c @@ -0,0 +1,1191 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-2005 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id: trees.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +/* #define GEN_TREES_H */ + +#include "deflate.h" + +#ifdef DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {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}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {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}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, ct_data *ltree, + ct_data *dtree)); +local void set_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); +local void copy_block OF((deflate_state *s, charf *buf, unsigned len, + int header)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits (deflate_state *s, int value, int length) +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (value << s->bi_valid); + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += (int) (length - Buf_size); + } else { + s->bi_buf |= value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (val << s->bi_valid);\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* DEBUG */ + + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, "const uch _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void _tr_init(deflate_state *s) +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; + s->last_eob_len = 8; /* enough lookahead for inflate */ +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block (deflate_state *s) +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap (deflate_state *s, + ct_data *tree, /* the tree to restore */ + int k) /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen (deflate_state *s, tree_desc *desc) +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (bits + xbits); + if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits); + } + if (overflow == 0) return; + + Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if ((unsigned) tree[m].Len != (unsigned) bits) { + Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((long)bits - (long)tree[m].Len) + *(long)tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (ct_data *tree, /* the tree to decorate */ + int max_code, /* largest code with non zero frequency */ + ushf *bl_count) /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code) /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code) /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree (deflate_state *s) +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*(max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees (deflate_state *s, + int lcodes, int dcodes, int blcodes) /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void _tr_stored_block (deflate_state *s, charf *buf, ulg stored_len, int eof) +{ + send_bits(s, (STORED_BLOCK<<1)+eof, 3); /* send block type */ +#ifdef DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; +#endif + copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + * The current inflate code requires 9 bits of lookahead. If the + * last two codes for the previous block (real code plus EOB) were coded + * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + * the last real code. In this case we send two empty static blocks instead + * of one. (There are no problems if the previous block is stored or fixed.) + * To simplify the code, we assume the worst case of last real code encoded + * on one bit only. + */ +void _tr_align (deflate_state *s) +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); + /* Of the 10 bits for the empty block, we have already sent + * (10 - bi_valid) bits. The lookahead for the last real code (before + * the EOB of the previous block) was thus at least one plus the length + * of the EOB plus what we have just sent of the empty static block. + */ + if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; +#endif + bi_flush(s); + } + s->last_eob_len = 7; +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +void _tr_flush_block (deflate_state *s, + charf *buf, /* input block, or NULL if too old */ + ulg stored_len, /* length of input block */ + int eof) /* true if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is binary or text */ + if (stored_len > 0 && s->strm->data_type == Z_UNKNOWN) + set_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, eof); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+eof, 3); + compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+eof, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (eof) { + bi_windup(s); +#ifdef DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*eof)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int _tr_tally (deflate_state *s, + unsigned dist, /* distance of matched string */ + unsigned lc) /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block (deflate_state *s, + ct_data *ltree, /* literal tree */ + ct_data *dtree) /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); + s->last_eob_len = ltree[END_BLOCK].Len; +} + +/* =========================================================================== + * Set the data type to BINARY or TEXT, using a crude approximation: + * set it to Z_TEXT if all symbols are either printable characters (33 to 255) + * or white spaces (9 to 13, or 32); or set it to Z_BINARY otherwise. + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local void set_data_type (deflate_state *s) +{ + int n; + + for (n = 0; n < 9; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + if (n == 9) + for (n = 14; n < 32; n++) + if (s->dyn_ltree[n].Freq != 0) + break; + s->strm->data_type = (n == 32) ? Z_TEXT : Z_BINARY; +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse (unsigned code, int len) +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush (deflate_state *s) +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup (deflate_state *s) +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +local void copy_block(deflate_state *s, + charf *buf, /* the input data */ + unsigned len, /* its length */ + int header) /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + s->last_eob_len = 8; /* enough lookahead for inflate */ + + if (header) { + put_short(s, (ush)len); + put_short(s, (ush)~len); +#ifdef DEBUG + s->bits_sent += 2*16; +#endif + } +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif + while (len--) { + put_byte(s, *buf++); + } +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/trees.h b/JuceLibraryCode/modules/juce_core/zip/zlib/trees.h new file mode 100644 index 0000000..5ac45a7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/trees.h @@ -0,0 +1,127 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/uncompr.c b/JuceLibraryCode/modules/juce_core/zip/zlib/uncompr.c new file mode 100644 index 0000000..839602f --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/uncompr.c @@ -0,0 +1,60 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: uncompr.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ +int ZEXPORT uncompress (Bytef *dest, + uLongf *destLen, + const Bytef *source, + uLong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.h b/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.h new file mode 100644 index 0000000..f1e9e87 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.h @@ -0,0 +1,345 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zconf.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZCONF_H +#define ZCONF_H + +// *** Just a few hacks here to make it compile nicely with Juce.. +#define Z_PREFIX 1 +#undef __MACTYPES__ + +#ifdef _MSC_VER + #pragma warning (disable : 4131 4127 4244 4267) +#endif + + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define inflatePrime z_inflatePrime +# define inflateGetHeader z_inflateGetHeader +# define adler32_combine z_adler32_combine +# define crc32_combine z_crc32_combine +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.in.h b/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.in.h new file mode 100644 index 0000000..018173a --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/zconf.in.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zconf.in.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/zlib.h b/JuceLibraryCode/modules/juce_core/zip/zlib/zlib.h new file mode 100644 index 0000000..bdf8af7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/zlib.h @@ -0,0 +1,1358 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +//extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +//ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +//ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +//} +#endif + +#endif /* ZLIB_H */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.c b/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.c new file mode 100644 index 0000000..630305c --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.c @@ -0,0 +1,311 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zutil.c,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#include "zutil.h" + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +/*const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch (sizeof(uInt)) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch (sizeof(uLong)) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch (sizeof(voidpf)) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch (sizeof(z_off_t)) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#ifdef STDC +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +}*/ + +#if 0 + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (const char *m) +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(int err) +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.h b/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.h new file mode 100644 index 0000000..3939858 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/zip/zlib/zutil.h @@ -0,0 +1,271 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: zutil.h,v 1.1 2007/06/07 17:54:37 jules_rms Exp $ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#define ZLIB_INTERNAL +#include "zlib.h" + +#ifdef STDC +# ifndef _WIN32_WCE +# include +# endif +# include +# include +#endif +#ifdef NO_ERRNO_H +# ifdef _WIN32_WCE + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. We rename it to + * avoid conflict with other libraries that use the same workaround. + */ +# define errno z_errno +# endif + extern int errno; +#else +# ifndef _WIN32_WCE +# include +# endif +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# ifdef M_I86 + #include +# endif +#endif + +#if defined(MACOS) || TARGET_OS_MAC +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS + /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 + /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# define vsnprintf _vsnprintf +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +#endif +#ifdef VMS +# define NO_vsnprintf +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#if 0 +# include + extern int z_verbose; + extern void z_error OF((const char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +# define z_error(x) +# define z_verbose 0 +#endif + + +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* ZUTIL_H */ diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.cpp b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.cpp new file mode 100644 index 0000000..6db825e --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.cpp @@ -0,0 +1,102 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ApplicationProperties::ApplicationProperties() + : commonSettingsAreReadOnly (0) +{ +} + +ApplicationProperties::~ApplicationProperties() +{ + closeFiles(); +} + +//============================================================================== +void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions) +{ + options = newOptions; +} + +//============================================================================== +void ApplicationProperties::openFiles() +{ + // You need to call setStorageParameters() before trying to get hold of the properties! + jassert (options.applicationName.isNotEmpty()); + + if (options.applicationName.isNotEmpty()) + { + PropertiesFile::Options o (options); + + if (userProps == nullptr) + { + o.commonToAllUsers = false; + userProps = new PropertiesFile (o); + } + + if (commonProps == nullptr) + { + o.commonToAllUsers = true; + commonProps = new PropertiesFile (o); + } + + userProps->setFallbackPropertySet (commonProps); + } +} + +PropertiesFile* ApplicationProperties::getUserSettings() +{ + if (userProps == nullptr) + openFiles(); + + return userProps; +} + +PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) +{ + if (commonProps == nullptr) + openFiles(); + + if (returnUserPropsIfReadOnly) + { + if (commonSettingsAreReadOnly == 0) + commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; + + if (commonSettingsAreReadOnly > 0) + return userProps; + } + + return commonProps; +} + +bool ApplicationProperties::saveIfNeeded() +{ + return (userProps == nullptr || userProps->saveIfNeeded()) + && (commonProps == nullptr || commonProps->saveIfNeeded()); +} + +void ApplicationProperties::closeFiles() +{ + userProps = nullptr; + commonProps = nullptr; +} diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.h b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.h new file mode 100644 index 0000000..21edbc2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_ApplicationProperties.h @@ -0,0 +1,132 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONPROPERTIES_H_INCLUDED +#define JUCE_APPLICATIONPROPERTIES_H_INCLUDED + + +//============================================================================== +/** + Manages a collection of properties. + + This is a slightly higher-level wrapper for managing PropertiesFile objects. + + It holds two different PropertiesFile objects internally, one for user-specific + settings (stored in your user directory), and one for settings that are common to + all users (stored in a folder accessible to all users). + + The class manages the creation of these files on-demand, allowing access via the + getUserSettings() and getCommonSettings() methods. + + After creating an instance of an ApplicationProperties object, you should first + of all call setStorageParameters() to tell it the parameters to use to create + its files. + + @see PropertiesFile +*/ +class JUCE_API ApplicationProperties +{ +public: + //============================================================================== + /** + Creates an ApplicationProperties object. + + Before using it, you must call setStorageParameters() to give it the info + it needs to create the property files. + */ + ApplicationProperties(); + + /** Destructor. */ + ~ApplicationProperties(); + + //============================================================================== + /** Gives the object the information it needs to create the appropriate properties files. + See the PropertiesFile::Options class for details about what options you need to set. + */ + void setStorageParameters (const PropertiesFile::Options& options); + + /** Returns the current storage parameters. + @see setStorageParameters + */ + const PropertiesFile::Options& getStorageParameters() const noexcept { return options; } + + //============================================================================== + /** Returns the user settings file. + + The first time this is called, it will create and load the properties file. + + Note that when you search the user PropertiesFile for a value that it doesn't contain, + the common settings are used as a second-chance place to look. This is done via the + PropertySet::setFallbackPropertySet() method - by default the common settings are set + to the fallback for the user settings. + + @see getCommonSettings + */ + PropertiesFile* getUserSettings(); + + /** Returns the common settings file. + + The first time this is called, it will create and load the properties file. + + @param returnUserPropsIfReadOnly if this is true, and the common properties file is + read-only (e.g. because the user doesn't have permission to write + to shared files), then this will return the user settings instead, + (like getUserSettings() would do). This is handy if you'd like to + write a value to the common settings, but if that's no possible, + then you'd rather write to the user settings than none at all. + If returnUserPropsIfReadOnly is false, this method will always return + the common settings, even if any changes to them can't be saved. + @see getUserSettings + */ + PropertiesFile* getCommonSettings (bool returnUserPropsIfReadOnly); + + //============================================================================== + /** Saves both files if they need to be saved. + + @see PropertiesFile::saveIfNeeded + */ + bool saveIfNeeded(); + + /** Flushes and closes both files if they are open. + + This flushes any pending changes to disk with PropertiesFile::saveIfNeeded() + and closes both files. They will then be re-opened the next time getUserSettings() + or getCommonSettings() is called. + */ + void closeFiles(); + + +private: + //============================================================================== + PropertiesFile::Options options; + ScopedPointer userProps, commonProps; + int commonSettingsAreReadOnly; + + void openFiles(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationProperties) +}; + + +#endif // JUCE_APPLICATIONPROPERTIES_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp new file mode 100644 index 0000000..ece2121 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -0,0 +1,355 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +namespace PropertyFileConstants +{ + static const int magicNumber = (int) ByteOrder::littleEndianInt ("PROP"); + static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ("CPRP"); + + static const char* const fileTag = "PROPERTIES"; + static const char* const valueTag = "VALUE"; + static const char* const nameAttribute = "name"; + static const char* const valueAttribute = "val"; +} + +//============================================================================== +PropertiesFile::Options::Options() + : commonToAllUsers (false), + ignoreCaseOfKeyNames (false), + doNotSave (false), + millisecondsBeforeSaving (3000), + storageFormat (PropertiesFile::storeAsXML), + processLock (nullptr) +{ +} + +File PropertiesFile::Options::getDefaultFile() const +{ + // mustn't have illegal characters in this name.. + jassert (applicationName == File::createLegalFileName (applicationName)); + + #if JUCE_MAC || JUCE_IOS + File dir (commonToAllUsers ? "/Library/" + : "~/Library/"); + + if (osxLibrarySubFolder != "Preferences" && ! osxLibrarySubFolder.startsWith ("Application Support")) + { + /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple + have changed their advice, and now stipulate that settings should go in "Library/Application Support". + + Because older apps would be broken by a silent change in this class's behaviour, you must now + explicitly set the osxLibrarySubFolder value to indicate which path you want to use. + + In newer apps, you should always set this to "Application Support" + or "Application Support/YourSubFolderName". + + If your app needs to load settings files that were created by older versions of juce and + you want to maintain backwards-compatibility, then you can set this to "Preferences". + But.. for better Apple-compliance, the recommended approach would be to write some code that + finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, + and then uses the new path. + */ + jassertfalse; + + dir = dir.getChildFile ("Application Support"); + } + else + { + dir = dir.getChildFile (osxLibrarySubFolder); + } + + if (folderName.isNotEmpty()) + dir = dir.getChildFile (folderName); + + #elif JUCE_LINUX || JUCE_ANDROID + const File dir (File (commonToAllUsers ? "/var" : "~") + .getChildFile (folderName.isNotEmpty() ? folderName + : ("." + applicationName))); + + #elif JUCE_WINDOWS + File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory + : File::userApplicationDataDirectory)); + + if (dir == File()) + return File(); + + dir = dir.getChildFile (folderName.isNotEmpty() ? folderName + : applicationName); + #endif + + return dir.getChildFile (applicationName) + .withFileExtension (filenameSuffix); +} + + +//============================================================================== +PropertiesFile::PropertiesFile (const File& f, const Options& o) + : PropertySet (o.ignoreCaseOfKeyNames), + file (f), options (o), + loadedOk (false), needsWriting (false) +{ + reload(); +} + +PropertiesFile::PropertiesFile (const Options& o) + : PropertySet (o.ignoreCaseOfKeyNames), + file (o.getDefaultFile()), options (o), + loadedOk (false), needsWriting (false) +{ + reload(); +} + +bool PropertiesFile::reload() +{ + ProcessScopedLock pl (createProcessLock()); + + if (pl != nullptr && ! pl->isLocked()) + return false; // locking failure.. + + loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml(); + return loadedOk; +} + +PropertiesFile::~PropertiesFile() +{ + saveIfNeeded(); +} + +InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const +{ + return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr; +} + +bool PropertiesFile::saveIfNeeded() +{ + const ScopedLock sl (getLock()); + return (! needsWriting) || save(); +} + +bool PropertiesFile::needsToBeSaved() const +{ + const ScopedLock sl (getLock()); + return needsWriting; +} + +void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) +{ + const ScopedLock sl (getLock()); + needsWriting = needsToBeSaved_; +} + +bool PropertiesFile::save() +{ + const ScopedLock sl (getLock()); + + stopTimer(); + + if (options.doNotSave + || file == File() + || file.isDirectory() + || ! file.getParentDirectory().createDirectory()) + return false; + + if (options.storageFormat == storeAsXML) + return saveAsXml(); + + return saveAsBinary(); +} + +bool PropertiesFile::loadAsXml() +{ + XmlDocument parser (file); + ScopedPointer doc (parser.getDocumentElement (true)); + + if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag)) + { + doc = parser.getDocumentElement(); + + if (doc != nullptr) + { + forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) + { + const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); + + if (name.isNotEmpty()) + { + getAllProperties().set (name, + e->getFirstChildElement() != nullptr + ? e->getFirstChildElement()->createDocument ("", true) + : e->getStringAttribute (PropertyFileConstants::valueAttribute)); + } + } + + return true; + } + + // must be a pretty broken XML file we're trying to parse here, + // or a sign that this object needs an InterProcessLock, + // or just a failure reading the file. This last reason is why + // we don't jassertfalse here. + } + + return false; +} + +bool PropertiesFile::saveAsXml() +{ + XmlElement doc (PropertyFileConstants::fileTag); + const StringPairArray& props = getAllProperties(); + + for (int i = 0; i < props.size(); ++i) + { + XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); + e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]); + + // if the value seems to contain xml, store it as such.. + if (XmlElement* const childElement = XmlDocument::parse (props.getAllValues() [i])) + e->addChildElement (childElement); + else + e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); + } + + ProcessScopedLock pl (createProcessLock()); + + if (pl != nullptr && ! pl->isLocked()) + return false; // locking failure.. + + if (doc.writeToFile (file, String())) + { + needsWriting = false; + return true; + } + + return false; +} + +bool PropertiesFile::loadAsBinary() +{ + FileInputStream fileStream (file); + + if (fileStream.openedOk()) + { + const int magicNumber = fileStream.readInt(); + + if (magicNumber == PropertyFileConstants::magicNumberCompressed) + { + SubregionStream subStream (&fileStream, 4, -1, false); + GZIPDecompressorInputStream gzip (subStream); + return loadAsBinary (gzip); + } + + if (magicNumber == PropertyFileConstants::magicNumber) + return loadAsBinary (fileStream); + } + + return false; +} + +bool PropertiesFile::loadAsBinary (InputStream& input) +{ + BufferedInputStream in (input, 2048); + + int numValues = in.readInt(); + + while (--numValues >= 0 && ! in.isExhausted()) + { + const String key (in.readString()); + const String value (in.readString()); + + jassert (key.isNotEmpty()); + if (key.isNotEmpty()) + getAllProperties().set (key, value); + } + + return true; +} + +bool PropertiesFile::saveAsBinary() +{ + ProcessScopedLock pl (createProcessLock()); + + if (pl != nullptr && ! pl->isLocked()) + return false; // locking failure.. + + TemporaryFile tempFile (file); + ScopedPointer out (tempFile.getFile().createOutputStream()); + + if (out != nullptr) + { + if (options.storageFormat == storeAsCompressedBinary) + { + out->writeInt (PropertyFileConstants::magicNumberCompressed); + out->flush(); + + out = new GZIPCompressorOutputStream (out.release(), 9, true); + } + else + { + // have you set up the storage option flags correctly? + jassert (options.storageFormat == storeAsBinary); + + out->writeInt (PropertyFileConstants::magicNumber); + } + + const StringPairArray& props = getAllProperties(); + const int numProperties = props.size(); + const StringArray& keys = props.getAllKeys(); + const StringArray& values = props.getAllValues(); + + out->writeInt (numProperties); + + for (int i = 0; i < numProperties; ++i) + { + out->writeString (keys[i]); + out->writeString (values[i]); + } + + out = nullptr; + + if (tempFile.overwriteTargetFileWithTemporary()) + { + needsWriting = false; + return true; + } + } + + return false; +} + +void PropertiesFile::timerCallback() +{ + saveIfNeeded(); +} + +void PropertiesFile::propertyChanged() +{ + sendChangeMessage(); + + needsWriting = true; + + if (options.millisecondsBeforeSaving > 0) + startTimer (options.millisecondsBeforeSaving); + else if (options.millisecondsBeforeSaving == 0) + saveIfNeeded(); +} diff --git a/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h new file mode 100644 index 0000000..f3d9551 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/app_properties/juce_PropertiesFile.h @@ -0,0 +1,247 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_PROPERTIESFILE_H_INCLUDED +#define JUCE_PROPERTIESFILE_H_INCLUDED + + +//============================================================================== +/** Wrapper on a file that stores a list of key/value data pairs. + + Useful for storing application settings, etc. See the PropertySet class for + the interfaces that read and write values. + + Not designed for very large amounts of data, as it keeps all the values in + memory and writes them out to disk lazily when they are changed. + + Because this class derives from ChangeBroadcaster, ChangeListeners can be registered + with it, and these will be signalled when a value changes. + + @see PropertySet +*/ +class JUCE_API PropertiesFile : public PropertySet, + public ChangeBroadcaster, + private Timer +{ +public: + //============================================================================== + enum StorageFormat + { + storeAsBinary, + storeAsCompressedBinary, + storeAsXML + }; + + //============================================================================== + struct JUCE_API Options + { + /** Creates an empty Options structure. + You'll need to fill-in the data members appropriately before using this structure. + */ + Options(); + + /** The name of your application - this is used to help generate the path and filename + at which the properties file will be stored. */ + String applicationName; + + /** The suffix to use for your properties file. + It doesn't really matter what this is - you may want to use ".settings" or + ".properties" or something. + */ + String filenameSuffix; + + /** The name of a subfolder in which you'd like your properties file to live. + See the getDefaultFile() method for more details about how this is used. + */ + String folderName; + + /** If you're using properties files on a Mac, you must set this value - failure to + do so will cause a runtime assertion. + + The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple + have changed their advice, and now stipulate that settings should go in "Library/Application Support". + + Because older apps would be broken by a silent change in this class's behaviour, you must now + explicitly set the osxLibrarySubFolder value to indicate which path you want to use. + + In newer apps, you should always set this to "Application Support" or + "Application Support/YourSubFolderName". + + If your app needs to load settings files that were created by older versions of juce and + you want to maintain backwards-compatibility, then you can set this to "Preferences". + But.. for better Apple-compliance, the recommended approach would be to write some code that + finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, + and then uses the new path. + */ + String osxLibrarySubFolder; + + /** If true, the file will be created in a location that's shared between users. + The default constructor initialises this value to false. + */ + bool commonToAllUsers; + + /** If true, this means that property names are matched in a case-insensitive manner. + See the PropertySet constructor for more info. + The default constructor initialises this value to false. + */ + bool ignoreCaseOfKeyNames; + + /** If set to true, this prevents the file from being written to disk. */ + bool doNotSave; + + /** If this is zero or greater, then after a value is changed, the object will wait + for this amount of time and then save the file. If this zero, the file will be + written to disk immediately on being changed (which might be slow, as it'll re-write + synchronously each time a value-change method is called). If it is less than zero, + the file won't be saved until save() or saveIfNeeded() are explicitly called. + The default constructor sets this to a reasonable value of a few seconds, so you + only need to change it if you need a special case. + */ + int millisecondsBeforeSaving; + + /** Specifies whether the file should be written as XML, binary, etc. + The default constructor sets this to storeAsXML, so you only need to set it explicitly + if you want to use a different format. + */ + StorageFormat storageFormat; + + /** An optional InterprocessLock object that will be used to prevent multiple threads or + processes from writing to the file at the same time. The PropertiesFile will keep a + pointer to this object but will not take ownership of it - the caller is responsible for + making sure that the lock doesn't get deleted before the PropertiesFile has been deleted. + The default constructor initialises this value to nullptr, so you don't need to touch it + unless you want to use a lock. + */ + InterProcessLock* processLock; + + /** This can be called to suggest a file that should be used, based on the values + in this structure. + + So on a Mac, this will return a file called: + ~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix] + + On Windows it'll return something like: + C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix] + + On Linux it'll return + ~/[folderName]/[applicationName].[filenameSuffix] + + If the folderName variable is empty, it'll use the app name for this (or omit the + folder name on the Mac). + + The paths will also vary depending on whether commonToAllUsers is true. + */ + File getDefaultFile() const; + }; + + //============================================================================== + /** Creates a PropertiesFile object. + The file used will be chosen by calling PropertiesFile::Options::getDefaultFile() + for the options provided. To set the file explicitly, use the other constructor. + */ + explicit PropertiesFile (const Options& options); + + /** Creates a PropertiesFile object. + Unlike the other constructor, this one allows you to explicitly set the file that you + want to be used, rather than using the default one. + */ + PropertiesFile (const File& file, + const Options& options); + + /** Destructor. + When deleted, the file will first call saveIfNeeded() to flush any changes to disk. + */ + ~PropertiesFile(); + + //============================================================================== + /** Returns true if this file was created from a valid (or non-existent) file. + If the file failed to load correctly because it was corrupt or had insufficient + access, this will be false. + */ + bool isValidFile() const noexcept { return loadedOk; } + + //============================================================================== + /** This will flush all the values to disk if they've changed since the last + time they were saved. + + Returns false if it fails to write to the file for some reason (maybe because + it's read-only or the directory doesn't exist or something). + + @see save + */ + bool saveIfNeeded(); + + /** This will force a write-to-disk of the current values, regardless of whether + anything has changed since the last save. + + Returns false if it fails to write to the file for some reason (maybe because + it's read-only or the directory doesn't exist or something). + + @see saveIfNeeded + */ + bool save(); + + /** Returns true if the properties have been altered since the last time they were saved. + The file is flagged as needing to be saved when you change a value, but you can + explicitly set this flag with setNeedsToBeSaved(). + */ + bool needsToBeSaved() const; + + /** Explicitly sets the flag to indicate whether the file needs saving or not. + @see needsToBeSaved + */ + void setNeedsToBeSaved (bool needsToBeSaved); + + /** Attempts to reload the settings from the file. */ + bool reload(); + + //============================================================================== + /** Returns the file that's being used. */ + const File& getFile() const noexcept { return file; } + + +protected: + /** @internal */ + virtual void propertyChanged(); + +private: + //============================================================================== + File file; + Options options; + bool loadedOk, needsWriting; + + typedef const ScopedPointer ProcessScopedLock; + InterProcessLock::ScopedLockType* createProcessLock() const; + + void timerCallback() override; + bool saveAsXml(); + bool saveAsBinary(); + bool loadAsXml(); + bool loadAsBinary(); + bool loadAsBinary (InputStream&); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile) +}; + +#endif // JUCE_PROPERTIESFILE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.cpp b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.cpp new file mode 100644 index 0000000..fd1d12a --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.cpp @@ -0,0 +1,49 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if defined (JUCE_DATA_STRUCTURES_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +// Your project must contain an AppConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "AppConfig.h" + +#include "juce_data_structures.h" + +namespace juce +{ + +#include "values/juce_Value.cpp" +#include "values/juce_ValueTree.cpp" +#include "undomanager/juce_UndoManager.cpp" +#include "app_properties/juce_ApplicationProperties.cpp" +#include "app_properties/juce_PropertiesFile.cpp" + +} diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h new file mode 100644 index 0000000..629b88d --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h @@ -0,0 +1,43 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DATA_STRUCTURES_H_INCLUDED +#define JUCE_DATA_STRUCTURES_H_INCLUDED + +//============================================================================= +#include "../juce_events/juce_events.h" + +namespace juce +{ + +#include "undomanager/juce_UndoableAction.h" +#include "undomanager/juce_UndoManager.h" +#include "values/juce_Value.h" +#include "values/juce_ValueTree.h" +#include "app_properties/juce_PropertiesFile.h" +#include "app_properties/juce_ApplicationProperties.h" + +} + +#endif // JUCE_DATA_STRUCTURES_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.mm b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.mm new file mode 100644 index 0000000..2eb4fb4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.mm @@ -0,0 +1,25 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#include "juce_data_structures.cpp" diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_module_info b/JuceLibraryCode/modules/juce_data_structures/juce_module_info new file mode 100644 index 0000000..51d8fdb --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/juce_module_info @@ -0,0 +1,20 @@ +{ + "id": "juce_data_structures", + "name": "JUCE data model helper classes", + "version": "3.1.1", + "description": "Classes for undo/redo management, and smart data structures.", + "website": "http://www.juce.com/juce", + "license": "GPL/Commercial", + + "dependencies": [ { "id": "juce_core", "version": "matching" }, + { "id": "juce_events", "version": "matching" } ], + + "include": "juce_data_structures.h", + + "compile": [ { "file": "juce_data_structures.cpp", "target": "! xcode" }, + { "file": "juce_data_structures.mm", "target": "xcode" } ], + + "browse": [ "values/*", + "undomanager/*", + "app_properties/*" ] +} diff --git a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp new file mode 100644 index 0000000..07948c6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.cpp @@ -0,0 +1,303 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct UndoManager::ActionSet +{ + ActionSet (const String& transactionName) + : name (transactionName), + time (Time::getCurrentTime()) + {} + + bool perform() const + { + for (int i = 0; i < actions.size(); ++i) + if (! actions.getUnchecked(i)->perform()) + return false; + + return true; + } + + bool undo() const + { + for (int i = actions.size(); --i >= 0;) + if (! actions.getUnchecked(i)->undo()) + return false; + + return true; + } + + int getTotalSize() const + { + int total = 0; + + for (int i = actions.size(); --i >= 0;) + total += actions.getUnchecked(i)->getSizeInUnits(); + + return total; + } + + OwnedArray actions; + String name; + Time time; +}; + +//============================================================================== +UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, + const int minimumTransactions) + : totalUnitsStored (0), + nextIndex (0), + newTransaction (true), + reentrancyCheck (false) +{ + setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, + minimumTransactions); +} + +UndoManager::~UndoManager() +{ +} + +//============================================================================== +void UndoManager::clearUndoHistory() +{ + transactions.clear(); + totalUnitsStored = 0; + nextIndex = 0; + sendChangeMessage(); +} + +int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const +{ + return totalUnitsStored; +} + +void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, + const int minimumTransactions) +{ + maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); + minimumTransactionsToKeep = jmax (1, minimumTransactions); +} + +//============================================================================== +bool UndoManager::perform (UndoableAction* const newAction, const String& actionName) +{ + if (perform (newAction)) + { + if (actionName.isNotEmpty()) + setCurrentTransactionName (actionName); + + return true; + } + + return false; +} + +bool UndoManager::perform (UndoableAction* const newAction) +{ + if (newAction != nullptr) + { + ScopedPointer action (newAction); + + if (reentrancyCheck) + { + jassertfalse; // don't call perform() recursively from the UndoableAction::perform() + // or undo() methods, or else these actions will be discarded! + return false; + } + + if (action->perform()) + { + ActionSet* actionSet = getCurrentSet(); + + if (actionSet != nullptr && ! newTransaction) + { + if (UndoableAction* const lastAction = actionSet->actions.getLast()) + { + if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action)) + { + action = coalescedAction; + totalUnitsStored -= lastAction->getSizeInUnits(); + actionSet->actions.removeLast(); + } + } + } + else + { + actionSet = new ActionSet (newTransactionName); + transactions.insert (nextIndex, actionSet); + ++nextIndex; + } + + totalUnitsStored += action->getSizeInUnits(); + actionSet->actions.add (action.release()); + newTransaction = false; + + clearFutureTransactions(); + sendChangeMessage(); + return true; + } + } + + return false; +} + +void UndoManager::clearFutureTransactions() +{ + while (nextIndex < transactions.size()) + { + totalUnitsStored -= transactions.getLast()->getTotalSize(); + transactions.removeLast(); + } + + while (nextIndex > 0 + && totalUnitsStored > maxNumUnitsToKeep + && transactions.size() > minimumTransactionsToKeep) + { + totalUnitsStored -= transactions.getFirst()->getTotalSize(); + transactions.remove (0); + --nextIndex; + + // if this fails, then some actions may not be returning + // consistent results from their getSizeInUnits() method + jassert (totalUnitsStored >= 0); + } +} + +void UndoManager::beginNewTransaction() noexcept +{ + beginNewTransaction (String()); +} + +void UndoManager::beginNewTransaction (const String& actionName) noexcept +{ + newTransaction = true; + newTransactionName = actionName; +} + +void UndoManager::setCurrentTransactionName (const String& newName) noexcept +{ + if (newTransaction) + newTransactionName = newName; + else if (ActionSet* action = getCurrentSet()) + action->name = newName; +} + +//============================================================================== +UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; } +UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; } + +bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; } +bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; } + +bool UndoManager::undo() +{ + if (const ActionSet* const s = getCurrentSet()) + { + const ScopedValueSetter setter (reentrancyCheck, true); + + if (s->undo()) + --nextIndex; + else + clearUndoHistory(); + + beginNewTransaction(); + sendChangeMessage(); + return true; + } + + return false; +} + +bool UndoManager::redo() +{ + if (const ActionSet* const s = getNextSet()) + { + const ScopedValueSetter setter (reentrancyCheck, true); + + if (s->perform()) + ++nextIndex; + else + clearUndoHistory(); + + beginNewTransaction(); + sendChangeMessage(); + return true; + } + + return false; +} + +String UndoManager::getUndoDescription() const +{ + if (const ActionSet* const s = getCurrentSet()) + return s->name; + + return String(); +} + +String UndoManager::getRedoDescription() const +{ + if (const ActionSet* const s = getNextSet()) + return s->name; + + return String(); +} + +Time UndoManager::getTimeOfUndoTransaction() const +{ + if (const ActionSet* const s = getCurrentSet()) + return s->time; + + return Time(); +} + +Time UndoManager::getTimeOfRedoTransaction() const +{ + if (const ActionSet* const s = getNextSet()) + return s->time; + + return Time::getCurrentTime(); +} + +bool UndoManager::undoCurrentTransactionOnly() +{ + return newTransaction ? false : undo(); +} + +void UndoManager::getActionsInCurrentTransaction (Array& actionsFound) const +{ + if (! newTransaction) + if (const ActionSet* const s = getCurrentSet()) + for (int i = 0; i < s->actions.size(); ++i) + actionsFound.add (s->actions.getUnchecked(i)); +} + +int UndoManager::getNumActionsInCurrentTransaction() const +{ + if (! newTransaction) + if (const ActionSet* const s = getCurrentSet()) + return s->actions.size(); + + return 0; +} diff --git a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h new file mode 100644 index 0000000..a3b691b --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoManager.h @@ -0,0 +1,239 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_UNDOMANAGER_H_INCLUDED +#define JUCE_UNDOMANAGER_H_INCLUDED + + +//============================================================================== +/** + Manages a list of undo/redo commands. + + An UndoManager object keeps a list of past actions and can use these actions + to move backwards and forwards through an undo history. + + To use it, create subclasses of UndoableAction which perform all the + actions you need, then when you need to actually perform an action, create one + and pass it to the UndoManager's perform() method. + + The manager also uses the concept of 'transactions' to group the actions + together - all actions performed between calls to beginNewTransaction() are + grouped together and are all undone/redone as a group. + + The UndoManager is a ChangeBroadcaster, so listeners can register to be told + when actions are performed or undone. + + @see UndoableAction +*/ +class JUCE_API UndoManager : public ChangeBroadcaster +{ +public: + //============================================================================== + /** Creates an UndoManager. + + @param maxNumberOfUnitsToKeep each UndoableAction object returns a value + to indicate how much storage it takes up + (UndoableAction::getSizeInUnits()), so this + lets you specify the maximum total number of + units that the undomanager is allowed to + keep in memory before letting the older actions + drop off the end of the list. + @param minimumTransactionsToKeep this specifies the minimum number of transactions + that will be kept, even if this involves exceeding + the amount of space specified in maxNumberOfUnitsToKeep + */ + UndoManager (int maxNumberOfUnitsToKeep = 30000, + int minimumTransactionsToKeep = 30); + + /** Destructor. */ + ~UndoManager(); + + //============================================================================== + /** Deletes all stored actions in the list. */ + void clearUndoHistory(); + + /** Returns the current amount of space to use for storing UndoableAction objects. + @see setMaxNumberOfStoredUnits + */ + int getNumberOfUnitsTakenUpByStoredCommands() const; + + /** Sets the amount of space that can be used for storing UndoableAction objects. + + @param maxNumberOfUnitsToKeep each UndoableAction object returns a value + to indicate how much storage it takes up + (UndoableAction::getSizeInUnits()), so this + lets you specify the maximum total number of + units that the undomanager is allowed to + keep in memory before letting the older actions + drop off the end of the list. + @param minimumTransactionsToKeep this specifies the minimum number of transactions + that will be kept, even if this involves exceeding + the amount of space specified in maxNumberOfUnitsToKeep + @see getNumberOfUnitsTakenUpByStoredCommands + */ + void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep, + int minimumTransactionsToKeep); + + //============================================================================== + /** Performs an action and adds it to the undo history list. + + @param action the action to perform - this object will be deleted by + the UndoManager when no longer needed + @returns true if the command succeeds - see UndoableAction::perform + @see beginNewTransaction + */ + bool perform (UndoableAction* action); + + /** Performs an action and also gives it a name. + + @param action the action to perform - this object will be deleted by + the UndoManager when no longer needed + @param actionName if this string is non-empty, the current transaction will be + given this name; if it's empty, the current transaction name will + be left unchanged. See setCurrentTransactionName() + @returns true if the command succeeds - see UndoableAction::perform + @see beginNewTransaction + */ + bool perform (UndoableAction* action, const String& actionName); + + /** Starts a new group of actions that together will be treated as a single transaction. + + All actions that are passed to the perform() method between calls to this + method are grouped together and undone/redone together by a single call to + undo() or redo(). + */ + void beginNewTransaction() noexcept; + + /** Starts a new group of actions that together will be treated as a single transaction. + + All actions that are passed to the perform() method between calls to this + method are grouped together and undone/redone together by a single call to + undo() or redo(). + + @param actionName a description of the transaction that is about to be + performed + */ + void beginNewTransaction (const String& actionName) noexcept; + + /** Changes the name stored for the current transaction. + + Each transaction is given a name when the beginNewTransaction() method is + called, but this can be used to change that name without starting a new + transaction. + */ + void setCurrentTransactionName (const String& newName) noexcept; + + //============================================================================== + /** Returns true if there's at least one action in the list to undo. + @see getUndoDescription, undo, canRedo + */ + bool canUndo() const noexcept; + + /** Returns the name of the transaction that will be rolled-back when undo() is called. + @see undo + */ + String getUndoDescription() const; + + /** Tries to roll-back the last transaction. + @returns true if the transaction can be undone, and false if it fails, or + if there aren't any transactions to undo + */ + bool undo(); + + /** Tries to roll-back any actions that were added to the current transaction. + + This will perform an undo() only if there are some actions in the undo list + that were added after the last call to beginNewTransaction(). + + This is useful because it lets you call beginNewTransaction(), then + perform an operation which may or may not actually perform some actions, and + then call this method to get rid of any actions that might have been done + without it rolling back the previous transaction if nothing was actually + done. + + @returns true if any actions were undone. + */ + bool undoCurrentTransactionOnly(); + + /** Returns a list of the UndoableAction objects that have been performed during the + transaction that is currently open. + + Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly() + were to be called now. + + The first item in the list is the earliest action performed. + */ + void getActionsInCurrentTransaction (Array& actionsFound) const; + + /** Returns the number of UndoableAction objects that have been performed during the + transaction that is currently open. + @see getActionsInCurrentTransaction + */ + int getNumActionsInCurrentTransaction() const; + + /** Returns the time to which the state would be restored if undo() was to be called. + If an undo isn't currently possible, it'll return Time(). + */ + Time getTimeOfUndoTransaction() const; + + /** Returns the time to which the state would be restored if redo() was to be called. + If a redo isn't currently possible, it'll return Time::getCurrentTime(). + */ + Time getTimeOfRedoTransaction() const; + + //============================================================================== + /** Returns true if there's at least one action in the list to redo. + @see getRedoDescription, redo, canUndo + */ + bool canRedo() const noexcept; + + /** Returns the name of the transaction that will be redone when redo() is called. + @see redo + */ + String getRedoDescription() const; + + /** Tries to redo the last transaction that was undone. + @returns true if the transaction can be redone, and false if it fails, or + if there aren't any transactions to redo + */ + bool redo(); + + +private: + //============================================================================== + struct ActionSet; + friend struct ContainerDeletePolicy; + OwnedArray transactions; + String newTransactionName; + int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex; + bool newTransaction, reentrancyCheck; + ActionSet* getCurrentSet() const noexcept; + ActionSet* getNextSet() const noexcept; + void clearFutureTransactions(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager) +}; + + +#endif // JUCE_UNDOMANAGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoableAction.h b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoableAction.h new file mode 100644 index 0000000..866d488 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/undomanager/juce_UndoableAction.h @@ -0,0 +1,99 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_UNDOABLEACTION_H_INCLUDED +#define JUCE_UNDOABLEACTION_H_INCLUDED + + +//============================================================================== +/** + Used by the UndoManager class to store an action which can be done + and undone. + + @see UndoManager +*/ +class JUCE_API UndoableAction +{ +protected: + /** Creates an action. */ + UndoableAction() noexcept {} + +public: + /** Destructor. */ + virtual ~UndoableAction() {} + + //============================================================================== + /** Overridden by a subclass to perform the action. + + This method is called by the UndoManager, and shouldn't be used directly by + applications. + + Be careful not to make any calls in a perform() method that could call + recursively back into the UndoManager::perform() method + + @returns true if the action could be performed. + @see UndoManager::perform + */ + virtual bool perform() = 0; + + /** Overridden by a subclass to undo the action. + + This method is called by the UndoManager, and shouldn't be used directly by + applications. + + Be careful not to make any calls in an undo() method that could call + recursively back into the UndoManager::perform() method + + @returns true if the action could be undone without any errors. + @see UndoManager::perform + */ + virtual bool undo() = 0; + + //============================================================================== + /** Returns a value to indicate how much memory this object takes up. + + Because the UndoManager keeps a list of UndoableActions, this is used + to work out how much space each one will take up, so that the UndoManager + can work out how many to keep. + + The default value returned here is 10 - units are arbitrary and + don't have to be accurate. + + @see UndoManager::getNumberOfUnitsTakenUpByStoredCommands, + UndoManager::setMaxNumberOfStoredUnits + */ + virtual int getSizeInUnits() { return 10; } + + /** Allows multiple actions to be coalesced into a single action object, to reduce storage space. + + If possible, this method should create and return a single action that does the same job as + this one followed by the supplied action. + + If it's not possible to merge the two actions, the method should return zero. + */ + virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { (void) nextAction; return nullptr; } +}; + + +#endif // JUCE_UNDOABLEACTION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.cpp b/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.cpp new file mode 100644 index 0000000..b0bb405 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.cpp @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +Value::ValueSource::ValueSource() +{ +} + +Value::ValueSource::~ValueSource() +{ + cancelPendingUpdate(); +} + +void Value::ValueSource::handleAsyncUpdate() +{ + sendChangeMessage (true); +} + +void Value::ValueSource::sendChangeMessage (const bool synchronous) +{ + const int numListeners = valuesWithListeners.size(); + + if (numListeners > 0) + { + if (synchronous) + { + const ReferenceCountedObjectPtr localRef (this); + + cancelPendingUpdate(); + + for (int i = numListeners; --i >= 0;) + if (Value* const v = valuesWithListeners[i]) + v->callListeners(); + } + else + { + triggerAsyncUpdate(); + } + } +} + +//============================================================================== +class SimpleValueSource : public Value::ValueSource +{ +public: + SimpleValueSource() + { + } + + SimpleValueSource (const var& initialValue) + : value (initialValue) + { + } + + var getValue() const + { + return value; + } + + void setValue (const var& newValue) + { + if (! newValue.equalsWithSameType (value)) + { + value = newValue; + sendChangeMessage (false); + } + } + +private: + var value; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource) +}; + + +//============================================================================== +Value::Value() : value (new SimpleValueSource()) +{ +} + +Value::Value (ValueSource* const v) : value (v) +{ + jassert (v != nullptr); +} + +Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue)) +{ +} + +Value::Value (const Value& other) : value (other.value) +{ +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Value::Value (Value&& other) noexcept +{ + // moving a Value with listeners will lose those listeners, which + // probably isn't what you wanted to happen! + jassert (other.listeners.size() == 0); + + other.removeFromListenerList(); + value = static_cast&&> (other.value); +} + +Value& Value::operator= (Value&& other) noexcept +{ + // moving a Value with listeners will lose those listeners, which + // probably isn't what you wanted to happen! + jassert (other.listeners.size() == 0); + + other.removeFromListenerList(); + value = static_cast&&> (other.value); + return *this; +} +#endif + +Value::~Value() +{ + removeFromListenerList(); +} + +void Value::removeFromListenerList() +{ + if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation + value->valuesWithListeners.removeValue (this); +} + +//============================================================================== +var Value::getValue() const +{ + return value->getValue(); +} + +Value::operator var() const +{ + return value->getValue(); +} + +void Value::setValue (const var& newValue) +{ + value->setValue (newValue); +} + +String Value::toString() const +{ + return value->getValue().toString(); +} + +Value& Value::operator= (const var& newValue) +{ + value->setValue (newValue); + return *this; +} + +void Value::referTo (const Value& valueToReferTo) +{ + if (valueToReferTo.value != value) + { + if (listeners.size() > 0) + { + value->valuesWithListeners.removeValue (this); + valueToReferTo.value->valuesWithListeners.add (this); + } + + value = valueToReferTo.value; + callListeners(); + } +} + +bool Value::refersToSameSourceAs (const Value& other) const +{ + return value == other.value; +} + +bool Value::operator== (const Value& other) const +{ + return value == other.value || value->getValue() == other.getValue(); +} + +bool Value::operator!= (const Value& other) const +{ + return value != other.value && value->getValue() != other.getValue(); +} + +//============================================================================== +void Value::addListener (ValueListener* const listener) +{ + if (listener != nullptr) + { + if (listeners.size() == 0) + value->valuesWithListeners.add (this); + + listeners.add (listener); + } +} + +void Value::removeListener (ValueListener* const listener) +{ + listeners.remove (listener); + + if (listeners.size() == 0) + value->valuesWithListeners.removeValue (this); +} + +void Value::callListeners() +{ + if (listeners.size() > 0) + { + Value v (*this); // (create a copy in case this gets deleted by a callback) + listeners.call (&ValueListener::valueChanged, v); + } +} + +OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value) +{ + return stream << value.toString(); +} diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.h b/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.h new file mode 100644 index 0000000..c25d35d --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_Value.h @@ -0,0 +1,237 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VALUE_H_INCLUDED +#define JUCE_VALUE_H_INCLUDED + + +//============================================================================== +/** + Represents a shared variant value. + + A Value object contains a reference to a var object, and can get and set its value. + Listeners can be attached to be told when the value is changed. + + The Value class is a wrapper around a shared, reference-counted underlying data + object - this means that multiple Value objects can all refer to the same piece of + data, allowing all of them to be notified when any of them changes it. + + When you create a Value with its default constructor, it acts as a wrapper around a + simple var object, but by creating a Value that refers to a custom subclass of ValueSource, + you can map the Value onto any kind of underlying data. +*/ +class JUCE_API Value +{ +public: + //============================================================================== + /** Creates an empty Value, containing a void var. */ + Value(); + + /** Creates a Value that refers to the same value as another one. + + Note that this doesn't make a copy of the other value - both this and the other + Value will share the same underlying value, so that when either one alters it, both + will see it change. + */ + Value (const Value& other); + + /** Creates a Value that is set to the specified value. */ + explicit Value (const var& initialValue); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Value (Value&&) noexcept; + Value& operator= (Value&&) noexcept; + #endif + + /** Destructor. */ + ~Value(); + + //============================================================================== + /** Returns the current value. */ + var getValue() const; + + /** Returns the current value. */ + operator var() const; + + /** Returns the value as a string. + This is a shortcut for "myValue.getValue().toString()". + */ + String toString() const; + + /** Sets the current value. + + You can also use operator= to set the value. + + If there are any listeners registered, they will be notified of the + change asynchronously. + */ + void setValue (const var& newValue); + + /** Sets the current value. + + This is the same as calling setValue(). + + If there are any listeners registered, they will be notified of the + change asynchronously. + */ + Value& operator= (const var& newValue); + + /** Makes this object refer to the same underlying ValueSource as another one. + + Once this object has been connected to another one, changing either one + will update the other. + + Existing listeners will still be registered after you call this method, and + they'll continue to receive messages when the new value changes. + */ + void referTo (const Value& valueToReferTo); + + /** Returns true if this value and the other one are references to the same value. + */ + bool refersToSameSourceAs (const Value& other) const; + + /** Compares two values. + This is a compare-by-value comparison, so is effectively the same as + saying (this->getValue() == other.getValue()). + */ + bool operator== (const Value& other) const; + + /** Compares two values. + This is a compare-by-value comparison, so is effectively the same as + saying (this->getValue() != other.getValue()). + */ + bool operator!= (const Value& other) const; + + //============================================================================== + /** Receives callbacks when a Value object changes. + @see Value::addListener + */ + class JUCE_API Listener + { + public: + Listener() {} + virtual ~Listener() {} + + /** Called when a Value object is changed. + + Note that the Value object passed as a parameter may not be exactly the same + object that you registered the listener with - it might be a copy that refers + to the same underlying ValueSource. To find out, you can call Value::refersToSameSourceAs(). + */ + virtual void valueChanged (Value& value) = 0; + }; + + /** Adds a listener to receive callbacks when the value changes. + + The listener is added to this specific Value object, and not to the shared + object that it refers to. When this object is deleted, all the listeners will + be lost, even if other references to the same Value still exist. So when you're + adding a listener, make sure that you add it to a Value instance that will last + for as long as you need the listener. In general, you'd never want to add a listener + to a local stack-based Value, but more likely to one that's a member variable. + + @see removeListener + */ + void addListener (Listener* listener); + + /** Removes a listener that was previously added with addListener(). */ + void removeListener (Listener* listener); + + + //============================================================================== + /** + Used internally by the Value class as the base class for its shared value objects. + + The Value class is essentially a reference-counted pointer to a shared instance + of a ValueSource object. If you're feeling adventurous, you can create your own custom + ValueSource classes to allow Value objects to represent your own custom data items. + */ + class JUCE_API ValueSource : public ReferenceCountedObject, + private AsyncUpdater + { + public: + ValueSource(); + virtual ~ValueSource(); + + /** Returns the current value of this object. */ + virtual var getValue() const = 0; + + /** Changes the current value. + This must also trigger a change message if the value actually changes. + */ + virtual void setValue (const var& newValue) = 0; + + /** Delivers a change message to all the listeners that are registered with + this value. + + If dispatchSynchronously is true, the method will call all the listeners + before returning; otherwise it'll dispatch a message and make the call later. + */ + void sendChangeMessage (bool dispatchSynchronously); + + protected: + //============================================================================== + friend class Value; + SortedSet valuesWithListeners; + + private: + void handleAsyncUpdate() override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource) + }; + + + //============================================================================== + /** Creates a Value object that uses this valueSource object as its underlying data. */ + explicit Value (ValueSource* valueSource); + + /** Returns the ValueSource that this value is referring to. */ + ValueSource& getValueSource() noexcept { return *value; } + + +private: + //============================================================================== + friend class ValueSource; + ReferenceCountedObjectPtr value; + ListenerList listeners; + + void callListeners(); + void removeFromListenerList(); + + // This is disallowed to avoid confusion about whether it should + // do a by-value or by-reference copy. + Value& operator= (const Value&) JUCE_DELETED_FUNCTION; + + // This declaration prevents accidental construction from an integer of 0, + // which is possible in some compilers via an implicit cast to a pointer. + explicit Value (void*) JUCE_DELETED_FUNCTION; +}; + +/** Writes a Value to an OutputStream as a UTF8 string. */ +OutputStream& JUCE_CALLTYPE operator<< (OutputStream&, const Value&); + +/** This typedef is just for compatibility with old code - newer code should use the Value::Listener class directly. */ +typedef Value::Listener ValueListener; + +#endif // JUCE_VALUE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp new file mode 100644 index 0000000..ee13400 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -0,0 +1,1123 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class ValueTree::SharedObject : public ReferenceCountedObject +{ +public: + typedef ReferenceCountedObjectPtr Ptr; + + explicit SharedObject (Identifier t) noexcept + : type (t), parent (nullptr) + { + } + + SharedObject (const SharedObject& other) + : ReferenceCountedObject(), + type (other.type), properties (other.properties), parent (nullptr) + { + for (int i = 0; i < other.children.size(); ++i) + { + SharedObject* const child = new SharedObject (*other.children.getObjectPointerUnchecked(i)); + child->parent = this; + children.add (child); + } + } + + ~SharedObject() + { + jassert (parent == nullptr); // this should never happen unless something isn't obeying the ref-counting! + + for (int i = children.size(); --i >= 0;) + { + const Ptr c (children.getObjectPointerUnchecked(i)); + c->parent = nullptr; + children.remove (i); + c->sendParentChangeMessage(); + } + } + + template + void callListeners (Method method, ValueTree& tree) const + { + const int numListeners = valueTreesWithListeners.size(); + + if (numListeners > 0) + { + if (numListeners == 1) + { + valueTreesWithListeners.getUnchecked(0)->listeners.call (method, tree); + } + else + { + const SortedSet listenersCopy (valueTreesWithListeners); + + for (int i = 0; i < numListeners; ++i) + { + ValueTree* const v = listenersCopy.getUnchecked(i); + + if (i == 0 || valueTreesWithListeners.contains (v)) + v->listeners.call (method, tree); + } + } + } + } + + template + void callListeners (Method method, ValueTree& tree, ParamType& param2) const + { + const int numListeners = valueTreesWithListeners.size(); + + if (numListeners > 0) + { + if (numListeners == 1) + { + valueTreesWithListeners.getUnchecked(0)->listeners.call (method, tree, param2); + } + else + { + const SortedSet listenersCopy (valueTreesWithListeners); + + for (int i = 0; i < numListeners; ++i) + { + ValueTree* const v = listenersCopy.getUnchecked(i); + + if (i == 0 || valueTreesWithListeners.contains (v)) + v->listeners.call (method, tree, param2); + } + } + } + } + + void sendPropertyChangeMessage (const Identifier property) + { + ValueTree tree (this); + + for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) + t->callListeners (&ValueTree::Listener::valueTreePropertyChanged, tree, property); + } + + void sendChildAddedMessage (ValueTree child) + { + ValueTree tree (this); + + for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) + t->callListeners (&ValueTree::Listener::valueTreeChildAdded, tree, child); + } + + void sendChildRemovedMessage (ValueTree child) + { + ValueTree tree (this); + + for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) + t->callListeners (&ValueTree::Listener::valueTreeChildRemoved, tree, child); + } + + void sendChildOrderChangedMessage() + { + ValueTree tree (this); + + for (ValueTree::SharedObject* t = this; t != nullptr; t = t->parent) + t->callListeners (&ValueTree::Listener::valueTreeChildOrderChanged, tree); + } + + void sendParentChangeMessage() + { + ValueTree tree (this); + + for (int j = children.size(); --j >= 0;) + if (SharedObject* const child = children.getObjectPointer (j)) + child->sendParentChangeMessage(); + + callListeners (&ValueTree::Listener::valueTreeParentChanged, tree); + } + + void setProperty (const Identifier name, const var& newValue, UndoManager* const undoManager) + { + if (undoManager == nullptr) + { + if (properties.set (name, newValue)) + sendPropertyChangeMessage (name); + } + else + { + if (const var* const existingValue = properties.getVarPointer (name)) + { + if (*existingValue != newValue) + undoManager->perform (new SetPropertyAction (this, name, newValue, *existingValue, false, false)); + } + else + { + undoManager->perform (new SetPropertyAction (this, name, newValue, var(), true, false)); + } + } + } + + bool hasProperty (const Identifier name) const noexcept + { + return properties.contains (name); + } + + void removeProperty (const Identifier name, UndoManager* const undoManager) + { + if (undoManager == nullptr) + { + if (properties.remove (name)) + sendPropertyChangeMessage (name); + } + else + { + if (properties.contains (name)) + undoManager->perform (new SetPropertyAction (this, name, var(), properties [name], false, true)); + } + } + + void removeAllProperties (UndoManager* const undoManager) + { + if (undoManager == nullptr) + { + while (properties.size() > 0) + { + const Identifier name (properties.getName (properties.size() - 1)); + properties.remove (name); + sendPropertyChangeMessage (name); + } + } + else + { + for (int i = properties.size(); --i >= 0;) + undoManager->perform (new SetPropertyAction (this, properties.getName(i), var(), + properties.getValueAt(i), false, true)); + } + } + + void copyPropertiesFrom (const SharedObject& source, UndoManager* const undoManager) + { + for (int i = properties.size(); --i >= 0;) + if (! source.properties.contains (properties.getName (i))) + removeProperty (properties.getName (i), undoManager); + + for (int i = 0; i < source.properties.size(); ++i) + setProperty (source.properties.getName(i), source.properties.getValueAt(i), undoManager); + } + + ValueTree getChildWithName (const Identifier typeToMatch) const + { + for (int i = 0; i < children.size(); ++i) + { + SharedObject* const s = children.getObjectPointerUnchecked (i); + if (s->type == typeToMatch) + return ValueTree (s); + } + + return ValueTree(); + } + + ValueTree getOrCreateChildWithName (const Identifier typeToMatch, UndoManager* undoManager) + { + for (int i = 0; i < children.size(); ++i) + { + SharedObject* const s = children.getObjectPointerUnchecked (i); + if (s->type == typeToMatch) + return ValueTree (s); + } + + SharedObject* const newObject = new SharedObject (typeToMatch); + addChild (newObject, -1, undoManager); + return ValueTree (newObject); + + } + + ValueTree getChildWithProperty (const Identifier propertyName, const var& propertyValue) const + { + for (int i = 0; i < children.size(); ++i) + { + SharedObject* const s = children.getObjectPointerUnchecked (i); + if (s->properties[propertyName] == propertyValue) + return ValueTree (s); + } + + return ValueTree(); + } + + bool isAChildOf (const SharedObject* const possibleParent) const noexcept + { + for (const SharedObject* p = parent; p != nullptr; p = p->parent) + if (p == possibleParent) + return true; + + return false; + } + + int indexOf (const ValueTree& child) const noexcept + { + return children.indexOf (child.object); + } + + void addChild (SharedObject* child, int index, UndoManager* const undoManager) + { + if (child != nullptr && child->parent != this) + { + if (child != this && ! isAChildOf (child)) + { + // You should always make sure that a child is removed from its previous parent before + // adding it somewhere else - otherwise, it's ambiguous as to whether a different + // undomanager should be used when removing it from its current parent.. + jassert (child->parent == nullptr); + + if (child->parent != nullptr) + { + jassert (child->parent->children.indexOf (child) >= 0); + child->parent->removeChild (child->parent->children.indexOf (child), undoManager); + } + + if (undoManager == nullptr) + { + children.insert (index, child); + child->parent = this; + sendChildAddedMessage (ValueTree (child)); + child->sendParentChangeMessage(); + } + else + { + if (! isPositiveAndBelow (index, children.size())) + index = children.size(); + + undoManager->perform (new AddOrRemoveChildAction (this, index, child)); + } + } + else + { + // You're attempting to create a recursive loop! A node + // can't be a child of one of its own children! + jassertfalse; + } + } + } + + void removeChild (const int childIndex, UndoManager* const undoManager) + { + if (const Ptr child = children.getObjectPointer (childIndex)) + { + if (undoManager == nullptr) + { + children.remove (childIndex); + child->parent = nullptr; + sendChildRemovedMessage (ValueTree (child)); + child->sendParentChangeMessage(); + } + else + { + undoManager->perform (new AddOrRemoveChildAction (this, childIndex, nullptr)); + } + } + } + + void removeAllChildren (UndoManager* const undoManager) + { + while (children.size() > 0) + removeChild (children.size() - 1, undoManager); + } + + void moveChild (int currentIndex, int newIndex, UndoManager* undoManager) + { + // The source index must be a valid index! + jassert (isPositiveAndBelow (currentIndex, children.size())); + + if (currentIndex != newIndex + && isPositiveAndBelow (currentIndex, children.size())) + { + if (undoManager == nullptr) + { + children.move (currentIndex, newIndex); + sendChildOrderChangedMessage(); + } + else + { + if (! isPositiveAndBelow (newIndex, children.size())) + newIndex = children.size() - 1; + + undoManager->perform (new MoveChildAction (this, currentIndex, newIndex)); + } + } + } + + void reorderChildren (const OwnedArray& newOrder, UndoManager* undoManager) + { + jassert (newOrder.size() == children.size()); + + if (undoManager == nullptr) + { + children.clear(); + children.ensureStorageAllocated (newOrder.size()); + + for (int i = 0; i < newOrder.size(); ++i) + children.add (newOrder.getUnchecked(i)->object); + + sendChildOrderChangedMessage(); + } + else + { + for (int i = 0; i < children.size(); ++i) + { + SharedObject* const child = newOrder.getUnchecked(i)->object; + + if (children.getObjectPointerUnchecked (i) != child) + { + const int oldIndex = children.indexOf (child); + jassert (oldIndex >= 0); + moveChild (oldIndex, i, undoManager); + } + } + } + } + + bool isEquivalentTo (const SharedObject& other) const + { + if (type != other.type + || properties.size() != other.properties.size() + || children.size() != other.children.size() + || properties != other.properties) + return false; + + for (int i = 0; i < children.size(); ++i) + if (! children.getObjectPointerUnchecked(i)->isEquivalentTo (*other.children.getObjectPointerUnchecked(i))) + return false; + + return true; + } + + XmlElement* createXml() const + { + XmlElement* const xml = new XmlElement (type); + properties.copyToXmlAttributes (*xml); + + // (NB: it's faster to add nodes to XML elements in reverse order) + for (int i = children.size(); --i >= 0;) + xml->prependChildElement (children.getObjectPointerUnchecked(i)->createXml()); + + return xml; + } + + void writeToStream (OutputStream& output) const + { + output.writeString (type.toString()); + output.writeCompressedInt (properties.size()); + + for (int j = 0; j < properties.size(); ++j) + { + output.writeString (properties.getName (j).toString()); + properties.getValueAt(j).writeToStream (output); + } + + output.writeCompressedInt (children.size()); + + for (int i = 0; i < children.size(); ++i) + writeObjectToStream (output, children.getObjectPointerUnchecked(i)); + } + + static void writeObjectToStream (OutputStream& output, const SharedObject* const object) + { + if (object != nullptr) + { + object->writeToStream (output); + } + else + { + output.writeString (String()); + output.writeCompressedInt (0); + output.writeCompressedInt (0); + } + } + + //============================================================================== + class SetPropertyAction : public UndoableAction + { + public: + SetPropertyAction (SharedObject* const so, const Identifier propertyName, + const var& newVal, const var& oldVal, bool isAdding, bool isDeleting) + : target (so), name (propertyName), newValue (newVal), oldValue (oldVal), + isAddingNewProperty (isAdding), isDeletingProperty (isDeleting) + { + } + + bool perform() + { + jassert (! (isAddingNewProperty && target->hasProperty (name))); + + if (isDeletingProperty) + target->removeProperty (name, nullptr); + else + target->setProperty (name, newValue, nullptr); + + return true; + } + + bool undo() + { + if (isAddingNewProperty) + target->removeProperty (name, nullptr); + else + target->setProperty (name, oldValue, nullptr); + + return true; + } + + int getSizeInUnits() + { + return (int) sizeof (*this); //xxx should be more accurate + } + + UndoableAction* createCoalescedAction (UndoableAction* nextAction) + { + if (! (isAddingNewProperty || isDeletingProperty)) + { + if (SetPropertyAction* const next = dynamic_cast (nextAction)) + if (next->target == target && next->name == name + && ! (next->isAddingNewProperty || next->isDeletingProperty)) + return new SetPropertyAction (target, name, next->newValue, oldValue, false, false); + } + + return nullptr; + } + + private: + const Ptr target; + const Identifier name; + const var newValue; + var oldValue; + const bool isAddingNewProperty : 1, isDeletingProperty : 1; + + JUCE_DECLARE_NON_COPYABLE (SetPropertyAction) + }; + + //============================================================================== + class AddOrRemoveChildAction : public UndoableAction + { + public: + AddOrRemoveChildAction (SharedObject* parentObject, int index, SharedObject* newChild) + : target (parentObject), + child (newChild != nullptr ? newChild : parentObject->children.getObjectPointer (index)), + childIndex (index), + isDeleting (newChild == nullptr) + { + jassert (child != nullptr); + } + + bool perform() + { + if (isDeleting) + target->removeChild (childIndex, nullptr); + else + target->addChild (child, childIndex, nullptr); + + return true; + } + + bool undo() + { + if (isDeleting) + { + target->addChild (child, childIndex, nullptr); + } + else + { + // If you hit this, it seems that your object's state is getting confused - probably + // because you've interleaved some undoable and non-undoable operations? + jassert (childIndex < target->children.size()); + target->removeChild (childIndex, nullptr); + } + + return true; + } + + int getSizeInUnits() + { + return (int) sizeof (*this); //xxx should be more accurate + } + + private: + const Ptr target, child; + const int childIndex; + const bool isDeleting; + + JUCE_DECLARE_NON_COPYABLE (AddOrRemoveChildAction) + }; + + //============================================================================== + class MoveChildAction : public UndoableAction + { + public: + MoveChildAction (SharedObject* parentObject, int fromIndex, int toIndex) noexcept + : parent (parentObject), startIndex (fromIndex), endIndex (toIndex) + { + } + + bool perform() + { + parent->moveChild (startIndex, endIndex, nullptr); + return true; + } + + bool undo() + { + parent->moveChild (endIndex, startIndex, nullptr); + return true; + } + + int getSizeInUnits() + { + return (int) sizeof (*this); //xxx should be more accurate + } + + UndoableAction* createCoalescedAction (UndoableAction* nextAction) + { + if (MoveChildAction* next = dynamic_cast (nextAction)) + if (next->parent == parent && next->startIndex == endIndex) + return new MoveChildAction (parent, startIndex, next->endIndex); + + return nullptr; + } + + private: + const Ptr parent; + const int startIndex, endIndex; + + JUCE_DECLARE_NON_COPYABLE (MoveChildAction) + }; + + //============================================================================== + const Identifier type; + NamedValueSet properties; + ReferenceCountedArray children; + SortedSet valueTreesWithListeners; + SharedObject* parent; + +private: + SharedObject& operator= (const SharedObject&); + JUCE_LEAK_DETECTOR (SharedObject) +}; + +//============================================================================== +ValueTree::ValueTree() noexcept +{ +} + +const ValueTree ValueTree::invalid; + +ValueTree::ValueTree (Identifier type) : object (new ValueTree::SharedObject (type)) +{ + jassert (type.toString().isNotEmpty()); // All objects must be given a sensible type name! +} + +ValueTree::ValueTree (SharedObject* so) : object (so) +{ +} + +ValueTree::ValueTree (const ValueTree& other) : object (other.object) +{ +} + +ValueTree& ValueTree::operator= (const ValueTree& other) +{ + if (object != other.object) + { + if (listeners.isEmpty()) + { + object = other.object; + } + else + { + if (object != nullptr) + object->valueTreesWithListeners.removeValue (this); + + if (other.object != nullptr) + other.object->valueTreesWithListeners.add (this); + + object = other.object; + + listeners.call (&ValueTree::Listener::valueTreeRedirected, *this); + } + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +ValueTree::ValueTree (ValueTree&& other) noexcept + : object (static_cast (other.object)) +{ +} +#endif + +ValueTree::~ValueTree() +{ + if (listeners.size() > 0 && object != nullptr) + object->valueTreesWithListeners.removeValue (this); +} + +bool ValueTree::operator== (const ValueTree& other) const noexcept +{ + return object == other.object; +} + +bool ValueTree::operator!= (const ValueTree& other) const noexcept +{ + return object != other.object; +} + +bool ValueTree::isEquivalentTo (const ValueTree& other) const +{ + return object == other.object + || (object != nullptr && other.object != nullptr + && object->isEquivalentTo (*other.object)); +} + +ValueTree ValueTree::createCopy() const +{ + return ValueTree (createCopyIfNotNull (object.get())); +} + +bool ValueTree::hasType (const Identifier typeName) const +{ + return object != nullptr && object->type == typeName; +} + +Identifier ValueTree::getType() const +{ + return object != nullptr ? object->type : Identifier(); +} + +ValueTree ValueTree::getParent() const +{ + return ValueTree (object != nullptr ? object->parent + : static_cast (nullptr)); +} + +ValueTree ValueTree::getSibling (const int delta) const +{ + if (object == nullptr || object->parent == nullptr) + return invalid; + + const int index = object->parent->indexOf (*this) + delta; + return ValueTree (object->parent->children.getObjectPointer (index)); +} + +const var& ValueTree::operator[] (const Identifier name) const +{ + return object == nullptr ? var::null : object->properties[name]; +} + +const var& ValueTree::getProperty (const Identifier name) const +{ + return object == nullptr ? var::null : object->properties[name]; +} + +var ValueTree::getProperty (const Identifier name, const var& defaultReturnValue) const +{ + return object == nullptr ? defaultReturnValue + : object->properties.getWithDefault (name, defaultReturnValue); +} + +ValueTree& ValueTree::setProperty (const Identifier name, const var& newValue, + UndoManager* const undoManager) +{ + jassert (name.toString().isNotEmpty()); // Must have a valid property name! + jassert (object != nullptr); // Trying to add a property to a null ValueTree will fail! + + if (object != nullptr) + object->setProperty (name, newValue, undoManager); + + return *this; +} + +bool ValueTree::hasProperty (const Identifier name) const +{ + return object != nullptr && object->hasProperty (name); +} + +void ValueTree::removeProperty (const Identifier name, UndoManager* const undoManager) +{ + if (object != nullptr) + object->removeProperty (name, undoManager); +} + +void ValueTree::removeAllProperties (UndoManager* const undoManager) +{ + if (object != nullptr) + object->removeAllProperties (undoManager); +} + +int ValueTree::getNumProperties() const +{ + return object == nullptr ? 0 : object->properties.size(); +} + +Identifier ValueTree::getPropertyName (const int index) const +{ + return object == nullptr ? Identifier() + : object->properties.getName (index); +} + +void ValueTree::copyPropertiesFrom (const ValueTree& source, UndoManager* const undoManager) +{ + jassert (object != nullptr || source.object == nullptr); // Trying to add properties to a null ValueTree will fail! + + if (source.object == nullptr) + removeAllProperties (undoManager); + else if (object != nullptr) + object->copyPropertiesFrom (*(source.object), undoManager); +} + +int ValueTree::getReferenceCount() const noexcept +{ + return object != nullptr ? object->getReferenceCount() : 0; +} + +//============================================================================== +class ValueTreePropertyValueSource : public Value::ValueSource, + private ValueTree::Listener +{ +public: + ValueTreePropertyValueSource (const ValueTree& vt, const Identifier prop, UndoManager* um) + : tree (vt), property (prop), undoManager (um) + { + tree.addListener (this); + } + + ~ValueTreePropertyValueSource() + { + tree.removeListener (this); + } + + var getValue() const { return tree [property]; } + void setValue (const var& newValue) { tree.setProperty (property, newValue, undoManager); } + +private: + ValueTree tree; + const Identifier property; + UndoManager* const undoManager; + + void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override + { + if (tree == changedTree && property == changedProperty) + sendChangeMessage (false); + } + + void valueTreeChildAdded (ValueTree&, ValueTree&) override {} + void valueTreeChildRemoved (ValueTree&, ValueTree&) override {} + void valueTreeChildOrderChanged (ValueTree&) override {} + void valueTreeParentChanged (ValueTree&) override {} + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreePropertyValueSource) +}; + +Value ValueTree::getPropertyAsValue (const Identifier name, UndoManager* const undoManager) +{ + return Value (new ValueTreePropertyValueSource (*this, name, undoManager)); +} + +//============================================================================== +int ValueTree::getNumChildren() const +{ + return object == nullptr ? 0 : object->children.size(); +} + +ValueTree ValueTree::getChild (int index) const +{ + return ValueTree (object != nullptr ? object->children.getObjectPointer (index) + : static_cast (nullptr)); +} + +ValueTree ValueTree::getChildWithName (const Identifier type) const +{ + return object != nullptr ? object->getChildWithName (type) : ValueTree(); +} + +ValueTree ValueTree::getOrCreateChildWithName (const Identifier type, UndoManager* undoManager) +{ + return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree(); +} + +ValueTree ValueTree::getChildWithProperty (const Identifier propertyName, const var& propertyValue) const +{ + return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree(); +} + +bool ValueTree::isAChildOf (const ValueTree& possibleParent) const +{ + return object != nullptr && object->isAChildOf (possibleParent.object); +} + +int ValueTree::indexOf (const ValueTree& child) const +{ + return object != nullptr ? object->indexOf (child) : -1; +} + +void ValueTree::addChild (const ValueTree& child, int index, UndoManager* const undoManager) +{ + jassert (object != nullptr); // Trying to add a child to a null ValueTree! + + if (object != nullptr) + object->addChild (child.object, index, undoManager); +} + +void ValueTree::removeChild (const int childIndex, UndoManager* const undoManager) +{ + if (object != nullptr) + object->removeChild (childIndex, undoManager); +} + +void ValueTree::removeChild (const ValueTree& child, UndoManager* const undoManager) +{ + if (object != nullptr) + object->removeChild (object->children.indexOf (child.object), undoManager); +} + +void ValueTree::removeAllChildren (UndoManager* const undoManager) +{ + if (object != nullptr) + object->removeAllChildren (undoManager); +} + +void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) +{ + if (object != nullptr) + object->moveChild (currentIndex, newIndex, undoManager); +} + +//============================================================================== +void ValueTree::createListOfChildren (OwnedArray& list) const +{ + jassert (object != nullptr); + + for (int i = 0; i < object->children.size(); ++i) + list.add (new ValueTree (object->children.getObjectPointerUnchecked(i))); +} + +void ValueTree::reorderChildren (const OwnedArray& newOrder, UndoManager* undoManager) +{ + jassert (object != nullptr); + object->reorderChildren (newOrder, undoManager); +} + +//============================================================================== +void ValueTree::addListener (Listener* listener) +{ + if (listener != nullptr) + { + if (listeners.isEmpty() && object != nullptr) + object->valueTreesWithListeners.add (this); + + listeners.add (listener); + } +} + +void ValueTree::removeListener (Listener* listener) +{ + listeners.remove (listener); + + if (listeners.isEmpty() && object != nullptr) + object->valueTreesWithListeners.removeValue (this); +} + +void ValueTree::sendPropertyChangeMessage (const Identifier property) +{ + if (object != nullptr) + object->sendPropertyChangeMessage (property); +} + +//============================================================================== +XmlElement* ValueTree::createXml() const +{ + return object != nullptr ? object->createXml() : nullptr; +} + +ValueTree ValueTree::fromXml (const XmlElement& xml) +{ + // ValueTrees don't have any equivalent to XML text elements! + jassert (! xml.isTextElement()); + + ValueTree v (xml.getTagName()); + v.object->properties.setFromXmlAttributes (xml); + + forEachXmlChildElement (xml, e) + v.addChild (fromXml (*e), -1, nullptr); + + return v; +} + +String ValueTree::toXmlString() const +{ + const ScopedPointer xml (createXml()); + return xml != nullptr ? xml->createDocument ("") : String(); +} + +//============================================================================== +void ValueTree::writeToStream (OutputStream& output) const +{ + SharedObject::writeObjectToStream (output, object); +} + +ValueTree ValueTree::readFromStream (InputStream& input) +{ + const String type (input.readString()); + + if (type.isEmpty()) + return ValueTree(); + + ValueTree v (type); + + const int numProps = input.readCompressedInt(); + + if (numProps < 0) + { + jassertfalse; // trying to read corrupted data! + return v; + } + + for (int i = 0; i < numProps; ++i) + { + const String name (input.readString()); + jassert (name.isNotEmpty()); + const var value (var::readFromStream (input)); + v.object->properties.set (name, value); + } + + const int numChildren = input.readCompressedInt(); + v.object->children.ensureStorageAllocated (numChildren); + + for (int i = 0; i < numChildren; ++i) + { + ValueTree child (readFromStream (input)); + + v.object->children.add (child.object); + child.object->parent = v.object; + } + + return v; +} + +ValueTree ValueTree::readFromData (const void* const data, const size_t numBytes) +{ + MemoryInputStream in (data, numBytes, false); + return readFromStream (in); +} + +ValueTree ValueTree::readFromGZIPData (const void* const data, const size_t numBytes) +{ + MemoryInputStream in (data, numBytes, false); + GZIPDecompressorInputStream gzipStream (in); + return readFromStream (gzipStream); +} + +void ValueTree::Listener::valueTreeRedirected (ValueTree&) {} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class ValueTreeTests : public UnitTest +{ +public: + ValueTreeTests() : UnitTest ("ValueTrees") {} + + static String createRandomIdentifier (Random& r) + { + char buffer[50] = { 0 }; + const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"; + + for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;) + buffer[i] = chars [r.nextInt (sizeof (chars) - 1)]; + + return CharPointer_ASCII (buffer); + } + + static String createRandomWideCharString (Random& r) + { + juce_wchar buffer[50] = { 0 }; + + for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;) + { + if (r.nextBool()) + { + do + { + buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); + } + while (! CharPointer_UTF16::canRepresent (buffer[i])); + } + else + buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e)); + } + + return CharPointer_UTF32 (buffer); + } + + static ValueTree createRandomTree (UndoManager* undoManager, int depth, Random& r) + { + ValueTree v (createRandomIdentifier (r)); + + for (int i = r.nextInt (10); --i >= 0;) + { + switch (r.nextInt (5)) + { + case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager); break; + case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager); break; + case 2: if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager); break; + case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager); break; + case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager); break; + default: break; + } + } + + return v; + } + + void runTest() + { + beginTest ("ValueTree"); + Random r = getRandom(); + + for (int i = 10; --i >= 0;) + { + MemoryOutputStream mo; + ValueTree v1 (createRandomTree (nullptr, 0, r)); + v1.writeToStream (mo); + + MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); + ValueTree v2 = ValueTree::readFromStream (mi); + expect (v1.isEquivalentTo (v2)); + + ScopedPointer xml1 (v1.createXml()); + ScopedPointer xml2 (v2.createCopy().createXml()); + expect (xml1->isEquivalentTo (xml2, false)); + + ValueTree v4 = v2.createCopy(); + expect (v1.isEquivalentTo (v4)); + } + } +}; + +static ValueTreeTests valueTreeTests; + +#endif diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h new file mode 100644 index 0000000..42dd778 --- /dev/null +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.h @@ -0,0 +1,531 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_VALUETREE_H_INCLUDED +#define JUCE_VALUETREE_H_INCLUDED + + +//============================================================================== +/** + A powerful tree structure that can be used to hold free-form data, and which can + handle its own undo and redo behaviour. + + A ValueTree contains a list of named properties as var objects, and also holds + any number of sub-trees. + + Create ValueTree objects on the stack, and don't be afraid to copy them around, as + they're simply a lightweight reference to a shared data container. Creating a copy + of another ValueTree simply creates a new reference to the same underlying object - to + make a separate, deep copy of a tree you should explicitly call createCopy(). + + Each ValueTree has a type name, in much the same way as an XmlElement has a tag name, + and much of the structure of a ValueTree is similar to an XmlElement tree. + You can convert a ValueTree to and from an XmlElement, and as long as the XML doesn't + contain text elements, the conversion works well and makes a good serialisation + format. They can also be serialised to a binary format, which is very fast and compact. + + All the methods that change data take an optional UndoManager, which will be used + to track any changes to the object. For this to work, you have to be careful to + consistently always use the same UndoManager for all operations to any node inside + the tree. + + A ValueTree can only be a child of one parent at a time, so if you're moving one from + one tree to another, be careful to always remove it first, before adding it. This + could also mess up your undo/redo chain, so be wary! In a debug build you should hit + assertions if you try to do anything dangerous, but there are still plenty of ways it + could go wrong. + + Listeners can be added to a ValueTree to be told when properies change and when + nodes are added or removed. + + @see var, XmlElement +*/ +class JUCE_API ValueTree +{ +public: + //============================================================================== + /** Creates an empty, invalid ValueTree. + + A ValueTree that is created with this constructor can't actually be used for anything, + it's just a default 'null' ValueTree that can be returned to indicate some sort of failure. + To create a real one, use the constructor that takes a string. + + @see ValueTree::invalid + */ + ValueTree() noexcept; + + /** Creates an empty ValueTree with the given type name. + Like an XmlElement, each ValueTree node has a type, which you can access with + getType() and hasType(). + */ + explicit ValueTree (Identifier type); + + /** Creates a reference to another ValueTree. */ + ValueTree (const ValueTree&); + + /** Makes this object reference another node. */ + ValueTree& operator= (const ValueTree&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + ValueTree (ValueTree&&) noexcept; + #endif + + /** Destructor. */ + ~ValueTree(); + + /** Returns true if both this and the other tree node refer to the same underlying structure. + Note that this isn't a value comparison - two independently-created trees which + contain identical data are not considered equal. + */ + bool operator== (const ValueTree&) const noexcept; + + /** Returns true if this and the other node refer to different underlying structures. + Note that this isn't a value comparison - two independently-created trees which + contain identical data are not considered equal. + */ + bool operator!= (const ValueTree&) const noexcept; + + /** Performs a deep comparison between the properties and children of two trees. + If all the properties and children of the two trees are the same (recursively), this + returns true. + The normal operator==() only checks whether two trees refer to the same shared data + structure, so use this method if you need to do a proper value comparison. + */ + bool isEquivalentTo (const ValueTree&) const; + + //============================================================================== + /** Returns true if this node refers to some valid data. + It's hard to create an invalid node, but you might get one returned, e.g. by an out-of-range + call to getChild(). + */ + bool isValid() const { return object != nullptr; } + + /** Returns a deep copy of this tree and all its sub-nodes. */ + ValueTree createCopy() const; + + //============================================================================== + /** Returns the type of this node. + The type is specified when the ValueTree is created. + @see hasType + */ + Identifier getType() const; + + /** Returns true if the node has this type. + The comparison is case-sensitive. + */ + bool hasType (const Identifier typeName) const; + + //============================================================================== + /** Returns the value of a named property. + If no such property has been set, this will return a void variant. + You can also use operator[] to get a property. + @see var, setProperty, hasProperty + */ + const var& getProperty (const Identifier name) const; + + /** Returns the value of a named property, or a user-specified default if the property doesn't exist. + If no such property has been set, this will return the value of defaultReturnValue. + You can also use operator[] and getProperty to get a property. + @see var, getProperty, setProperty, hasProperty + */ + var getProperty (const Identifier name, const var& defaultReturnValue) const; + + /** Returns the value of a named property. + If no such property has been set, this will return a void variant. This is the same as + calling getProperty(). + @see getProperty + */ + const var& operator[] (const Identifier name) const; + + /** Changes a named property of the node. + The name identifier must not be an empty string. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + @see var, getProperty, removeProperty + @returns a reference to the value tree, so that you can daisy-chain calls to this method. + */ + ValueTree& setProperty (const Identifier name, const var& newValue, UndoManager* undoManager); + + /** Returns true if the node contains a named property. */ + bool hasProperty (const Identifier name) const; + + /** Removes a property from the node. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void removeProperty (const Identifier name, UndoManager* undoManager); + + /** Removes all properties from the node. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void removeAllProperties (UndoManager* undoManager); + + /** Returns the total number of properties that the node contains. + @see getProperty. + */ + int getNumProperties() const; + + /** Returns the identifier of the property with a given index. + @see getNumProperties + */ + Identifier getPropertyName (int index) const; + + /** Returns a Value object that can be used to control and respond to one of the tree's properties. + + The Value object will maintain a reference to this tree, and will use the undo manager when + it needs to change the value. Attaching a Value::Listener to the value object will provide + callbacks whenever the property changes. + */ + Value getPropertyAsValue (const Identifier name, UndoManager* undoManager); + + /** Overwrites all the properties in this tree with the properties of the source tree. + Any properties that already exist will be updated; and new ones will be added, and + any that are not present in the source tree will be removed. + */ + void copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager); + + //============================================================================== + /** Returns the number of child nodes belonging to this one. + @see getChild + */ + int getNumChildren() const; + + /** Returns one of this node's child nodes. + If the index is out of range, it'll return an invalid node. (See isValid() to find out + whether a node is valid). + */ + ValueTree getChild (int index) const; + + /** Returns the first child node with the speficied type name. + If no such node is found, it'll return an invalid node. (See isValid() to find out + whether a node is valid). + @see getOrCreateChildWithName + */ + ValueTree getChildWithName (const Identifier type) const; + + /** Returns the first child node with the speficied type name, creating and adding + a child with this name if there wasn't already one there. + + The only time this will return an invalid object is when the object that you're calling + the method on is itself invalid. + @see getChildWithName + */ + ValueTree getOrCreateChildWithName (const Identifier type, UndoManager* undoManager); + + /** Looks for the first child node that has the speficied property value. + + This will scan the child nodes in order, until it finds one that has property that matches + the specified value. + + If no such node is found, it'll return an invalid node. (See isValid() to find out + whether a node is valid). + */ + ValueTree getChildWithProperty (const Identifier propertyName, const var& propertyValue) const; + + /** Adds a child to this node. + + Make sure that the child is removed from any former parent node before calling this, or + you'll hit an assertion. + + If the index is < 0 or greater than the current number of child nodes, the new node will + be added at the end of the list. + + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void addChild (const ValueTree& child, int index, UndoManager* undoManager); + + /** Removes the specified child from this node's child-list. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void removeChild (const ValueTree& child, UndoManager* undoManager); + + /** Removes a child from this node's child-list. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void removeChild (int childIndex, UndoManager* undoManager); + + /** Removes all child-nodes from this node. + If the undoManager parameter is non-null, its UndoManager::perform() method will be used, + so that this change can be undone. + */ + void removeAllChildren (UndoManager* undoManager); + + /** Moves one of the children to a different index. + + This will move the child to a specified index, shuffling along any intervening + items as required. So for example, if you have a list of { 0, 1, 2, 3, 4, 5 }, then + calling move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. + + @param currentIndex the index of the item to be moved. If this isn't a + valid index, then nothing will be done + @param newIndex the index at which you'd like this item to end up. If this + is less than zero, the value will be moved to the end + of the list + @param undoManager the optional UndoManager to use to store this transaction + */ + void moveChild (int currentIndex, int newIndex, UndoManager* undoManager); + + /** Returns true if this node is anywhere below the specified parent node. + This returns true if the node is a child-of-a-child, as well as a direct child. + */ + bool isAChildOf (const ValueTree& possibleParent) const; + + /** Returns the index of a child item in this parent. + If the child isn't found, this returns -1. + */ + int indexOf (const ValueTree& child) const; + + /** Returns the parent node that contains this one. + If the node has no parent, this will return an invalid node. (See isValid() to find out + whether a node is valid). + */ + ValueTree getParent() const; + + /** Returns one of this node's siblings in its parent's child list. + + The delta specifies how far to move through the list, so a value of 1 would return the node + that follows this one, -1 would return the node before it, 0 will return this node itself, etc. + If the requested position is beyond the range of available nodes, this will return ValueTree::invalid. + */ + ValueTree getSibling (int delta) const; + + //============================================================================== + /** Creates an XmlElement that holds a complete image of this node and all its children. + + If this node is invalid, this may return nullptr. Otherwise, the XML that is produced can + be used to recreate a similar node by calling fromXml(). + + The caller must delete the object that is returned. + + @see fromXml + */ + XmlElement* createXml() const; + + /** Tries to recreate a node from its XML representation. + + This isn't designed to cope with random XML data - for a sensible result, it should only + be fed XML that was created by the createXml() method. + */ + static ValueTree fromXml (const XmlElement& xml); + + /** This returns a string containing an XML representation of the tree. + This is quite handy for debugging purposes, as it provides a quick way to view a tree. + */ + String toXmlString() const; + + //============================================================================== + /** Stores this tree (and all its children) in a binary format. + + Once written, the data can be read back with readFromStream(). + + It's much faster to load/save your tree in binary form than as XML, but + obviously isn't human-readable. + */ + void writeToStream (OutputStream& output) const; + + /** Reloads a tree from a stream that was written with writeToStream(). */ + static ValueTree readFromStream (InputStream& input); + + /** Reloads a tree from a data block that was written with writeToStream(). */ + static ValueTree readFromData (const void* data, size_t numBytes); + + /** Reloads a tree from a data block that was written with writeToStream() and + then zipped using GZIPCompressorOutputStream. + */ + static ValueTree readFromGZIPData (const void* data, size_t numBytes); + + //============================================================================== + /** Listener class for events that happen to a ValueTree. + + To get events from a ValueTree, make your class implement this interface, and use + ValueTree::addListener() and ValueTree::removeListener() to register it. + */ + class JUCE_API Listener + { + public: + /** Destructor. */ + virtual ~Listener() {} + + /** This method is called when a property of this node (or of one of its sub-nodes) has + changed. + + The tree parameter indicates which tree has had its property changed, and the property + parameter indicates the property. + + Note that when you register a listener to a tree, it will receive this callback for + property changes in that tree, and also for any of its children, (recursively, at any depth). + If your tree has sub-trees but you only want to know about changes to the top level tree, + simply check the tree parameter in this callback to make sure it's the tree you're interested in. + */ + virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, + const Identifier& property) = 0; + + /** This method is called when a child sub-tree is added. + + Note that when you register a listener to a tree, it will receive this callback for + child changes in both that tree and any of its children, (recursively, at any depth). + If your tree has sub-trees but you only want to know about changes to the top level tree, + just check the parentTree parameter to make sure it's the one that you're interested in. + */ + virtual void valueTreeChildAdded (ValueTree& parentTree, + ValueTree& childWhichHasBeenAdded) = 0; + + /** This method is called when a child sub-tree is removed. + + Note that when you register a listener to a tree, it will receive this callback for + child changes in both that tree and any of its children, (recursively, at any depth). + If your tree has sub-trees but you only want to know about changes to the top level tree, + just check the parentTree parameter to make sure it's the one that you're interested in. + */ + virtual void valueTreeChildRemoved (ValueTree& parentTree, + ValueTree& childWhichHasBeenRemoved) = 0; + + /** This method is called when a tree's children have been re-shuffled. + + Note that when you register a listener to a tree, it will receive this callback for + child changes in both that tree and any of its children, (recursively, at any depth). + If your tree has sub-trees but you only want to know about changes to the top level tree, + just check the parameter to make sure it's the tree that you're interested in. + */ + virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved) = 0; + + /** This method is called when a tree has been added or removed from a parent node. + + This callback happens when the tree to which the listener was registered is added or + removed from a parent. Unlike the other callbacks, it applies only to the tree to which + the listener is registered, and not to any of its children. + */ + virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) = 0; + + /** This method is called when a tree is made to point to a different internal shared object. + When operator= is used to make a ValueTree refer to a different object, this callback + will be made. + */ + virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged); + }; + + /** Adds a listener to receive callbacks when this node is changed. + + The listener is added to this specific ValueTree object, and not to the shared + object that it refers to. When this object is deleted, all the listeners will + be lost, even if other references to the same ValueTree still exist. And if you + use the operator= to make this refer to a different ValueTree, any listeners will + begin listening to changes to the new tree instead of the old one. + + When you're adding a listener, make sure that you add it to a ValueTree instance that + will last for as long as you need the listener. In general, you'd never want to add a + listener to a local stack-based ValueTree, and would usually add one to a member variable. + + @see removeListener + */ + void addListener (Listener* listener); + + /** Removes a listener that was previously added with addListener(). */ + void removeListener (Listener* listener); + + /** Causes a property-change callback to be triggered for the specified property, + calling any listeners that are registered. + */ + void sendPropertyChangeMessage (const Identifier property); + + //============================================================================== + /** This method uses a comparator object to sort the tree's children into order. + + The object provided must have a method of the form: + @code + int compareElements (const ValueTree& first, const ValueTree& second); + @endcode + + ..and this method must return: + - a value of < 0 if the first comes before the second + - a value of 0 if the two objects are equivalent + - a value of > 0 if the second comes before the first + + To improve performance, the compareElements() method can be declared as static or const. + + @param comparator the comparator to use for comparing elements. + @param undoManager optional UndoManager for storing the changes + @param retainOrderOfEquivalentItems if this is true, then items which the comparator says are + equivalent will be kept in the order in which they currently appear in the array. + This is slower to perform, but may be important in some cases. If it's false, a + faster algorithm is used, but equivalent elements may be rearranged. + */ + template + void sort (ElementComparator& comparator, UndoManager* undoManager, bool retainOrderOfEquivalentItems) + { + if (object != nullptr) + { + OwnedArray sortedList; + createListOfChildren (sortedList); + ComparatorAdapter adapter (comparator); + sortedList.sort (adapter, retainOrderOfEquivalentItems); + reorderChildren (sortedList, undoManager); + } + } + + /** An invalid ValueTree that can be used if you need to return one as an error condition, etc. + This invalid object is equivalent to ValueTree created with its default constructor. + */ + static const ValueTree invalid; + + /** Returns the total number of references to the shared underlying data structure that this + ValueTree is using. + */ + int getReferenceCount() const noexcept; + +private: + //============================================================================== + JUCE_PUBLIC_IN_DLL_BUILD (class SharedObject) + friend class SharedObject; + + ReferenceCountedObjectPtr object; + ListenerList listeners; + + template + struct ComparatorAdapter + { + ComparatorAdapter (ElementComparator& comp) noexcept : comparator (comp) {} + + int compareElements (const ValueTree* const first, const ValueTree* const second) + { + return comparator.compareElements (*first, *second); + } + + private: + ElementComparator& comparator; + JUCE_DECLARE_NON_COPYABLE (ComparatorAdapter) + }; + + void createListOfChildren (OwnedArray&) const; + void reorderChildren (const OwnedArray&, UndoManager*); + + explicit ValueTree (SharedObject*); +}; + + +#endif // JUCE_VALUETREE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.cpp b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.cpp new file mode 100644 index 0000000..33f232d --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.cpp @@ -0,0 +1,91 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class ActionBroadcaster::ActionMessage : public MessageManager::MessageBase +{ +public: + ActionMessage (const ActionBroadcaster* ab, + const String& messageText, ActionListener* l) noexcept + : broadcaster (const_cast (ab)), + message (messageText), + listener (l) + {} + + void messageCallback() override + { + if (const ActionBroadcaster* const b = broadcaster) + if (b->actionListeners.contains (listener)) + listener->actionListenerCallback (message); + } + +private: + WeakReference broadcaster; + const String message; + ActionListener* const listener; + + JUCE_DECLARE_NON_COPYABLE (ActionMessage) +}; + +//============================================================================== +ActionBroadcaster::ActionBroadcaster() +{ + // are you trying to create this object before or after juce has been intialised?? + jassert (MessageManager::getInstanceWithoutCreating() != nullptr); +} + +ActionBroadcaster::~ActionBroadcaster() +{ + // all event-based objects must be deleted BEFORE juce is shut down! + jassert (MessageManager::getInstanceWithoutCreating() != nullptr); + + masterReference.clear(); +} + +void ActionBroadcaster::addActionListener (ActionListener* const listener) +{ + const ScopedLock sl (actionListenerLock); + + if (listener != nullptr) + actionListeners.add (listener); +} + +void ActionBroadcaster::removeActionListener (ActionListener* const listener) +{ + const ScopedLock sl (actionListenerLock); + actionListeners.removeValue (listener); +} + +void ActionBroadcaster::removeAllActionListeners() +{ + const ScopedLock sl (actionListenerLock); + actionListeners.clear(); +} + +void ActionBroadcaster::sendActionMessage (const String& message) const +{ + const ScopedLock sl (actionListenerLock); + + for (int i = actionListeners.size(); --i >= 0;) + (new ActionMessage (this, message, actionListeners.getUnchecked(i)))->post(); +} diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.h new file mode 100644 index 0000000..8052bed --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionBroadcaster.h @@ -0,0 +1,83 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ACTIONBROADCASTER_H_INCLUDED +#define JUCE_ACTIONBROADCASTER_H_INCLUDED + + +//============================================================================== +/** Manages a list of ActionListeners, and can send them messages. + + To quickly add methods to your class that can add/remove action + listeners and broadcast to them, you can derive from this. + + @see ActionListener, ChangeListener +*/ +class JUCE_API ActionBroadcaster +{ +public: + //============================================================================== + /** Creates an ActionBroadcaster. */ + ActionBroadcaster(); + + /** Destructor. */ + virtual ~ActionBroadcaster(); + + //============================================================================== + /** Adds a listener to the list. + Trying to add a listener that's already on the list will have no effect. + */ + void addActionListener (ActionListener* listener); + + /** Removes a listener from the list. + If the listener isn't on the list, this won't have any effect. + */ + void removeActionListener (ActionListener* listener); + + /** Removes all listeners from the list. */ + void removeAllActionListeners(); + + //============================================================================== + /** Broadcasts a message to all the registered listeners. + @see ActionListener::actionListenerCallback + */ + void sendActionMessage (const String& message) const; + + +private: + //============================================================================== + friend class WeakReference; + WeakReference::Master masterReference; + + class ActionMessage; + friend class ActionMessage; + + SortedSet actionListeners; + CriticalSection actionListenerLock; + + JUCE_DECLARE_NON_COPYABLE (ActionBroadcaster) +}; + + +#endif // JUCE_ACTIONBROADCASTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionListener.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionListener.h new file mode 100644 index 0000000..12257f9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ActionListener.h @@ -0,0 +1,50 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ACTIONLISTENER_H_INCLUDED +#define JUCE_ACTIONLISTENER_H_INCLUDED + + +//============================================================================== +/** + Interface class for delivery of events that are sent by an ActionBroadcaster. + + @see ActionBroadcaster, ChangeListener +*/ +class JUCE_API ActionListener +{ +public: + /** Destructor. */ + virtual ~ActionListener() {} + + /** Overridden by your subclass to receive the callback. + + @param message the string that was specified when the event was triggered + by a call to ActionBroadcaster::sendActionMessage() + */ + virtual void actionListenerCallback (const String& message) = 0; +}; + + +#endif // JUCE_ACTIONLISTENER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp b/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp new file mode 100644 index 0000000..980eb41 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.cpp @@ -0,0 +1,86 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class AsyncUpdater::AsyncUpdaterMessage : public CallbackMessage +{ +public: + AsyncUpdaterMessage (AsyncUpdater& au) : owner (au) {} + + void messageCallback() override + { + if (shouldDeliver.compareAndSetBool (0, 1)) + owner.handleAsyncUpdate(); + } + + Atomic shouldDeliver; + +private: + AsyncUpdater& owner; + + JUCE_DECLARE_NON_COPYABLE (AsyncUpdaterMessage) +}; + +//============================================================================== +AsyncUpdater::AsyncUpdater() +{ + activeMessage = new AsyncUpdaterMessage (*this); +} + +AsyncUpdater::~AsyncUpdater() +{ + // You're deleting this object with a background thread while there's an update + // pending on the main event thread - that's pretty dodgy threading, as the callback could + // happen after this destructor has finished. You should either use a MessageManagerLock while + // deleting this object, or find some other way to avoid such a race condition. + jassert ((! isUpdatePending()) || MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + activeMessage->shouldDeliver.set (0); +} + +void AsyncUpdater::triggerAsyncUpdate() +{ + if (activeMessage->shouldDeliver.compareAndSetBool (1, 0)) + if (! activeMessage->post()) + cancelPendingUpdate(); // if the message queue fails, this avoids getting + // trapped waiting for the message to arrive +} + +void AsyncUpdater::cancelPendingUpdate() noexcept +{ + activeMessage->shouldDeliver.set (0); +} + +void AsyncUpdater::handleUpdateNowIfNeeded() +{ + // This can only be called by the event thread. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + if (activeMessage->shouldDeliver.exchange (0) != 0) + handleAsyncUpdate(); +} + +bool AsyncUpdater::isUpdatePending() const noexcept +{ + return activeMessage->shouldDeliver.value != 0; +} diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.h new file mode 100644 index 0000000..79ab6e1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_AsyncUpdater.h @@ -0,0 +1,109 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ASYNCUPDATER_H_INCLUDED +#define JUCE_ASYNCUPDATER_H_INCLUDED + + +//============================================================================== +/** + Has a callback method that is triggered asynchronously. + + This object allows an asynchronous callback function to be triggered, for + tasks such as coalescing multiple updates into a single callback later on. + + Basically, one or more calls to the triggerAsyncUpdate() will result in the + message thread calling handleAsyncUpdate() as soon as it can. +*/ +class JUCE_API AsyncUpdater +{ +public: + //============================================================================== + /** Creates an AsyncUpdater object. */ + AsyncUpdater(); + + /** Destructor. + If there are any pending callbacks when the object is deleted, these are lost. + */ + virtual ~AsyncUpdater(); + + //============================================================================== + /** Causes the callback to be triggered at a later time. + + This method returns immediately, having made sure that a callback + to the handleAsyncUpdate() method will occur as soon as possible. + + If an update callback is already pending but hasn't happened yet, calls + to this method will be ignored. + + It's thread-safe to call this method from any number of threads without + needing to worry about locking. + */ + void triggerAsyncUpdate(); + + /** This will stop any pending updates from happening. + + If called after triggerAsyncUpdate() and before the handleAsyncUpdate() + callback happens, this will cancel the handleAsyncUpdate() callback. + + Note that this method simply cancels the next callback - if a callback is already + in progress on a different thread, this won't block until the callback finishes, so + there's no guarantee that the callback isn't still running when the method returns. + */ + void cancelPendingUpdate() noexcept; + + /** If an update has been triggered and is pending, this will invoke it + synchronously. + + Use this as a kind of "flush" operation - if an update is pending, the + handleAsyncUpdate() method will be called immediately; if no update is + pending, then nothing will be done. + + Because this may invoke the callback, this method must only be called on + the main event thread. + */ + void handleUpdateNowIfNeeded(); + + /** Returns true if there's an update callback in the pipeline. */ + bool isUpdatePending() const noexcept; + + //============================================================================== + /** Called back to do whatever your class needs to do. + + This method is called by the message thread at the next convenient time + after the triggerAsyncUpdate() method has been called. + */ + virtual void handleAsyncUpdate() = 0; + +private: + //============================================================================== + class AsyncUpdaterMessage; + friend class ReferenceCountedObjectPtr; + ReferenceCountedObjectPtr activeMessage; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncUpdater) +}; + + +#endif // JUCE_ASYNCUPDATER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp new file mode 100644 index 0000000..bff1f1a --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp @@ -0,0 +1,96 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ChangeBroadcaster::ChangeBroadcaster() noexcept +{ + callback.owner = this; +} + +ChangeBroadcaster::~ChangeBroadcaster() +{ +} + +void ChangeBroadcaster::addChangeListener (ChangeListener* const listener) +{ + // Listeners can only be safely added when the event thread is locked + // You can use a MessageManagerLock if you need to call this from another thread. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + changeListeners.add (listener); +} + +void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener) +{ + // Listeners can only be safely added when the event thread is locked + // You can use a MessageManagerLock if you need to call this from another thread. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + changeListeners.remove (listener); +} + +void ChangeBroadcaster::removeAllChangeListeners() +{ + // Listeners can only be safely added when the event thread is locked + // You can use a MessageManagerLock if you need to call this from another thread. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + changeListeners.clear(); +} + +void ChangeBroadcaster::sendChangeMessage() +{ + if (changeListeners.size() > 0) + callback.triggerAsyncUpdate(); +} + +void ChangeBroadcaster::sendSynchronousChangeMessage() +{ + // This can only be called by the event thread. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + callback.cancelPendingUpdate(); + callListeners(); +} + +void ChangeBroadcaster::dispatchPendingMessages() +{ + callback.handleUpdateNowIfNeeded(); +} + +void ChangeBroadcaster::callListeners() +{ + changeListeners.call (&ChangeListener::changeListenerCallback, this); +} + +//============================================================================== +ChangeBroadcaster::ChangeBroadcasterCallback::ChangeBroadcasterCallback() + : owner (nullptr) +{ +} + +void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate() +{ + jassert (owner != nullptr); + owner->callListeners(); +} diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.h new file mode 100644 index 0000000..8d9aca9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeBroadcaster.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CHANGEBROADCASTER_H_INCLUDED +#define JUCE_CHANGEBROADCASTER_H_INCLUDED + + +//============================================================================== +/** + Holds a list of ChangeListeners, and sends messages to them when instructed. + + @see ChangeListener +*/ +class JUCE_API ChangeBroadcaster +{ +public: + //============================================================================== + /** Creates an ChangeBroadcaster. */ + ChangeBroadcaster() noexcept; + + /** Destructor. */ + virtual ~ChangeBroadcaster(); + + //============================================================================== + /** Registers a listener to receive change callbacks from this broadcaster. + Trying to add a listener that's already on the list will have no effect. + */ + void addChangeListener (ChangeListener* listener); + + /** Unregisters a listener from the list. + If the listener isn't on the list, this won't have any effect. + */ + void removeChangeListener (ChangeListener* listener); + + /** Removes all listeners from the list. */ + void removeAllChangeListeners(); + + //============================================================================== + /** Causes an asynchronous change message to be sent to all the registered listeners. + + The message will be delivered asynchronously by the main message thread, so this + method will return immediately. To call the listeners synchronously use + sendSynchronousChangeMessage(). + */ + void sendChangeMessage(); + + /** Sends a synchronous change message to all the registered listeners. + + This will immediately call all the listeners that are registered. For thread-safety + reasons, you must only call this method on the main message thread. + + @see dispatchPendingMessages + */ + void sendSynchronousChangeMessage(); + + /** If a change message has been sent but not yet dispatched, this will call + sendSynchronousChangeMessage() to make the callback immediately. + + For thread-safety reasons, you must only call this method on the main message thread. + */ + void dispatchPendingMessages(); + +private: + //============================================================================== + class ChangeBroadcasterCallback : public AsyncUpdater + { + public: + ChangeBroadcasterCallback(); + void handleAsyncUpdate() override; + + ChangeBroadcaster* owner; + }; + + friend class ChangeBroadcasterCallback; + ChangeBroadcasterCallback callback; + ListenerList changeListeners; + + void callListeners(); + + JUCE_DECLARE_NON_COPYABLE (ChangeBroadcaster) +}; + + +#endif // JUCE_CHANGEBROADCASTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeListener.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeListener.h new file mode 100644 index 0000000..59e3025 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ChangeListener.h @@ -0,0 +1,64 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CHANGELISTENER_H_INCLUDED +#define JUCE_CHANGELISTENER_H_INCLUDED + +class ChangeBroadcaster; + +//============================================================================== +/** + Receives change event callbacks that are sent out by a ChangeBroadcaster. + + A ChangeBroadcaster keeps a set of listeners to which it broadcasts a message when + the ChangeBroadcaster::sendChangeMessage() method is called. A subclass of + ChangeListener is used to receive these callbacks. + + Note that the major difference between an ActionListener and a ChangeListener + is that for a ChangeListener, multiple changes will be coalesced into fewer + callbacks, but ActionListeners perform one callback for every event posted. + + @see ChangeBroadcaster, ActionListener +*/ +class JUCE_API ChangeListener +{ +public: + /** Destructor. */ + virtual ~ChangeListener() {} + + /** Your subclass should implement this method to receive the callback. + @param source the ChangeBroadcaster that triggered the callback. + */ + virtual void changeListenerCallback (ChangeBroadcaster* source) = 0; + + + //============================================================================== + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // This method's signature has changed to take a ChangeBroadcaster parameter - please update your code! + private: virtual int changeListenerCallback (void*) { return 0; } + #endif +}; + + +#endif // JUCE_CHANGELISTENER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_ListenerList.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ListenerList.h new file mode 100644 index 0000000..853e252 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_ListenerList.h @@ -0,0 +1,359 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_LISTENERLIST_H_INCLUDED +#define JUCE_LISTENERLIST_H_INCLUDED + + +//============================================================================== +/** + Holds a set of objects and can invoke a member function callback on each object + in the set with a single call. + + Use a ListenerList to manage a set of objects which need a callback, and you + can invoke a member function by simply calling call() or callChecked(). + + E.g. + @code + class MyListenerType + { + public: + void myCallbackMethod (int foo, bool bar); + }; + + ListenerList listeners; + listeners.add (someCallbackObjects...); + + // This will invoke myCallbackMethod (1234, true) on each of the objects + // in the list... + listeners.call (&MyListenerType::myCallbackMethod, 1234, true); + @endcode + + If you add or remove listeners from the list during one of the callbacks - i.e. while + it's in the middle of iterating the listeners, then it's guaranteed that no listeners + will be mistakenly called after they've been removed, but it may mean that some of the + listeners could be called more than once, or not at all, depending on the list's order. + + Sometimes, there's a chance that invoking one of the callbacks might result in the + list itself being deleted while it's still iterating - to survive this situation, you can + use callChecked() instead of call(), passing it a local object to act as a "BailOutChecker". + The BailOutChecker must implement a method of the form "bool shouldBailOut()", and + the list will check this after each callback to determine whether it should abort the + operation. For an example of a bail-out checker, see the Component::BailOutChecker class, + which can be used to check when a Component has been deleted. See also + ListenerList::DummyBailOutChecker, which is a dummy checker that always returns false. +*/ +template > +class ListenerList +{ + // Horrible macros required to support VC7.. + #ifndef DOXYGEN + #if JUCE_VC8_OR_EARLIER + #define LL_TEMPLATE(a) typename P##a, typename Q##a + #define LL_PARAM(a) Q##a& param##a + #else + #define LL_TEMPLATE(a) typename P##a + #define LL_PARAM(a) PARAMETER_TYPE(P##a) param##a + #endif + #endif + +public: + //============================================================================== + /** Creates an empty list. */ + ListenerList() + { + } + + /** Destructor. */ + ~ListenerList() + { + } + + //============================================================================== + /** Adds a listener to the list. + A listener can only be added once, so if the listener is already in the list, + this method has no effect. + @see remove + */ + void add (ListenerClass* const listenerToAdd) + { + // Listeners can't be null pointers! + jassert (listenerToAdd != nullptr); + + if (listenerToAdd != nullptr) + listeners.addIfNotAlreadyThere (listenerToAdd); + } + + /** Removes a listener from the list. + If the listener wasn't in the list, this has no effect. + */ + void remove (ListenerClass* const listenerToRemove) + { + // Listeners can't be null pointers! + jassert (listenerToRemove != nullptr); + + listeners.removeFirstMatchingValue (listenerToRemove); + } + + /** Returns the number of registered listeners. */ + int size() const noexcept + { + return listeners.size(); + } + + /** Returns true if any listeners are registered. */ + bool isEmpty() const noexcept + { + return listeners.size() == 0; + } + + /** Clears the list. */ + void clear() + { + listeners.clear(); + } + + /** Returns true if the specified listener has been added to the list. */ + bool contains (ListenerClass* const listener) const noexcept + { + return listeners.contains (listener); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with no parameters. */ + void call (void (ListenerClass::*callbackFunction) ()) + { + callChecked (static_cast (DummyBailOutChecker()), callbackFunction); + } + + /** Calls a member function on each listener in the list, with no parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) ()) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 1 parameter. */ + template + void call (void (ListenerClass::*callbackFunction) (P1), LL_PARAM(1)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1); + } + + /** Calls a member function on each listener in the list, with one parameter and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1), + LL_PARAM(1)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 2 parameters. */ + template + void call (void (ListenerClass::*callbackFunction) (P1, P2), + LL_PARAM(1), LL_PARAM(2)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1, param2); + } + + /** Calls a member function on each listener in the list, with 2 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2), + LL_PARAM(1), LL_PARAM(2)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1, param2); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 3 parameters. */ + template + void call (void (ListenerClass::*callbackFunction) (P1, P2, P3), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1, param2, param3); + } + + /** Calls a member function on each listener in the list, with 3 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1, param2, param3); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 4 parameters. */ + template + void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); + } + + /** Calls a member function on each listener in the list, with 4 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 5 parameters. */ + template + void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); + } + + /** Calls a member function on each listener in the list, with 5 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5); + } + + //============================================================================== + /** Calls a member function on each listener in the list, with 5 parameters. */ + template + void call (void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) + { + for (Iterator iter (*this); iter.next();) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); + } + + /** Calls a member function on each listener in the list, with 5 parameters and a bail-out-checker. + See the class description for info about writing a bail-out checker. */ + template + void callChecked (const BailOutCheckerType& bailOutChecker, + void (ListenerClass::*callbackFunction) (P1, P2, P3, P4, P5, P6), + LL_PARAM(1), LL_PARAM(2), LL_PARAM(3), LL_PARAM(4), LL_PARAM(5), LL_PARAM(6)) + { + for (Iterator iter (*this); iter.next (bailOutChecker);) + (iter.getListener()->*callbackFunction) (param1, param2, param3, param4, param5, param6); + } + + + //============================================================================== + /** A dummy bail-out checker that always returns false. + See the ListenerList notes for more info about bail-out checkers. + */ + class DummyBailOutChecker + { + public: + inline bool shouldBailOut() const noexcept { return false; } + }; + + //============================================================================== + /** Iterates the listeners in a ListenerList. */ + template + class Iterator + { + public: + //============================================================================== + Iterator (const ListType& listToIterate) noexcept + : list (listToIterate), index (listToIterate.size()) + {} + + ~Iterator() noexcept {} + + //============================================================================== + bool next() noexcept + { + if (index <= 0) + return false; + + const int listSize = list.size(); + + if (--index < listSize) + return true; + + index = listSize - 1; + return index >= 0; + } + + bool next (const BailOutCheckerType& bailOutChecker) noexcept + { + return (! bailOutChecker.shouldBailOut()) && next(); + } + + typename ListType::ListenerType* getListener() const noexcept + { + return list.getListeners().getUnchecked (index); + } + + //============================================================================== + private: + const ListType& list; + int index; + + JUCE_DECLARE_NON_COPYABLE (Iterator) + }; + + typedef ListenerList ThisType; + typedef ListenerClass ListenerType; + + const ArrayType& getListeners() const noexcept { return listeners; } + +private: + //============================================================================== + ArrayType listeners; + + JUCE_DECLARE_NON_COPYABLE (ListenerList) + + #undef LL_TEMPLATE + #undef LL_PARAM +}; + + +#endif // JUCE_LISTENERLIST_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp new file mode 100644 index 0000000..2c36c2c --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp @@ -0,0 +1,264 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +enum { magicMastSlaveConnectionHeader = 0x712baf04 }; + +static const char* startMessage = "__ipc_st"; +static const char* killMessage = "__ipc_k_"; +static const char* pingMessage = "__ipc_p_"; +enum { specialMessageSize = 8, defaultTimeoutMs = 8000 }; + +static String getCommandLinePrefix (const String& commandLineUniqueID) +{ + return "--" + commandLineUniqueID + ":"; +} + +//============================================================================== +// This thread sends and receives ping messages every second, so that it +// can find out if the other process has stopped running. +struct ChildProcessPingThread : public Thread, + private AsyncUpdater +{ + ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout) + { + pingReceived(); + } + + static bool isPingMessage (const MemoryBlock& m) noexcept + { + return memcmp (m.getData(), pingMessage, specialMessageSize) == 0; + } + + void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } + void triggerConnectionLostMessage() { triggerAsyncUpdate(); } + + virtual bool sendPingMessage (const MemoryBlock&) = 0; + virtual void pingFailed() = 0; + + int timeoutMs; + +private: + Atomic countdown; + + void handleAsyncUpdate() override { pingFailed(); } + + void run() override + { + while (! threadShouldExit()) + { + if (--countdown <= 0 || ! sendPingMessage (MemoryBlock (pingMessage, specialMessageSize))) + { + triggerConnectionLostMessage(); + break; + } + + wait (1000); + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread) +}; + +//============================================================================== +struct ChildProcessMaster::Connection : public InterprocessConnection, + private ChildProcessPingThread +{ + Connection (ChildProcessMaster& m, const String& pipeName, int timeout) + : InterprocessConnection (false, magicMastSlaveConnectionHeader), + ChildProcessPingThread (timeout), + owner (m) + { + if (createPipe (pipeName, timeoutMs)) + startThread (4); + } + + ~Connection() + { + stopThread (10000); + } + +private: + void connectionMade() override {} + void connectionLost() override { owner.handleConnectionLost(); } + + bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); } + void pingFailed() override { connectionLost(); } + + void messageReceived (const MemoryBlock& m) override + { + pingReceived(); + + if (m.getSize() != specialMessageSize || ! isPingMessage (m)) + owner.handleMessageFromSlave (m); + } + + ChildProcessMaster& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) +}; + +//============================================================================== +ChildProcessMaster::ChildProcessMaster() {} + +ChildProcessMaster::~ChildProcessMaster() +{ + if (connection != nullptr) + { + sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize)); + connection->disconnect(); + connection = nullptr; + } +} + +void ChildProcessMaster::handleConnectionLost() {} + +bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb) +{ + if (connection != nullptr) + return connection->sendMessage (mb); + + jassertfalse; // this can only be used when the connection is active! + return false; +} + +bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID, int timeoutMs) +{ + connection = nullptr; + jassert (childProcess.kill()); + + const String pipeName ("p" + String::toHexString (Random().nextInt64())); + + StringArray args; + args.add (executable.getFullPathName()); + args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName); + + if (childProcess.start (args)) + { + connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); + + if (connection->isConnected()) + { + sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize)); + return true; + } + + connection = nullptr; + } + + return false; +} + +//============================================================================== +struct ChildProcessSlave::Connection : public InterprocessConnection, + private ChildProcessPingThread +{ + Connection (ChildProcessSlave& p, const String& pipeName, int timeout) + : InterprocessConnection (false, magicMastSlaveConnectionHeader), + ChildProcessPingThread (timeout), + owner (p) + { + connectToPipe (pipeName, timeoutMs); + startThread (4); + } + + ~Connection() + { + stopThread (10000); + } + +private: + ChildProcessSlave& owner; + + void connectionMade() override {} + void connectionLost() override { owner.handleConnectionLost(); } + + bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); } + void pingFailed() override { connectionLost(); } + + void messageReceived (const MemoryBlock& m) override + { + pingReceived(); + + if (m.getSize() == specialMessageSize) + { + if (isPingMessage (m)) + return; + + if (memcmp (m.getData(), killMessage, specialMessageSize) == 0) + { + triggerConnectionLostMessage(); + return; + } + + if (memcmp (m.getData(), startMessage, specialMessageSize) == 0) + { + owner.handleConnectionMade(); + return; + } + } + + owner.handleMessageFromMaster (m); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) +}; + +//============================================================================== +ChildProcessSlave::ChildProcessSlave() {} +ChildProcessSlave::~ChildProcessSlave() {} + +void ChildProcessSlave::handleConnectionMade() {} +void ChildProcessSlave::handleConnectionLost() {} + +bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb) +{ + if (connection != nullptr) + return connection->sendMessage (mb); + + jassertfalse; // this can only be used when the connection is active! + return false; +} + +bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine, + const String& commandLineUniqueID, + int timeoutMs) +{ + String prefix (getCommandLinePrefix (commandLineUniqueID)); + + if (commandLine.trim().startsWith (prefix)) + { + String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false) + .upToFirstOccurrenceOf (" ", false, false).trim()); + + if (pipeName.isNotEmpty()) + { + connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); + + if (! connection->isConnected()) + connection = nullptr; + } + } + + return connection != nullptr; +} diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.h b/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.h new file mode 100644 index 0000000..60e3b71 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_ConnectedChildProcess.h @@ -0,0 +1,191 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED +#define JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED + +//============================================================================== +/** + Acts as the slave end of a master/slave pair of connected processes. + + The ChildProcessSlave and ChildProcessMaster classes make it easy for an app + to spawn a child process, and to manage a 2-way messaging connection to control it. + + To use the system, you need to create subclasses of both ChildProcessSlave and + ChildProcessMaster. To instantiate the ChildProcessSlave object, you must + add some code to your main() or JUCEApplication::initialise() function that + calls the initialiseFromCommandLine() method to check the app's command-line + parameters to see whether it's being launched as a child process. If this returns + true then the slave process can be allowed to run, and its handleMessageFromMaster() + method will be called whenever a message arrives. + + The juce demo app has a good example of this class in action. + + @see ChildProcessMaster, InterprocessConnection, ChildProcess +*/ +class JUCE_API ChildProcessSlave +{ +public: + /** Creates a non-connected slave process. + Use initialiseFromCommandLine to connect to a master process. + */ + ChildProcessSlave(); + + /** Destructor. */ + virtual ~ChildProcessSlave(); + + /** This checks some command-line parameters to see whether they were generated by + ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process. + + In an exe that can be used as a child process, you should add some code to your + main() or JUCEApplication::initialise() that calls this method. + + The commandLineUniqueID should be a short alphanumeric identifier (no spaces!) + that matches the string passed to ChildProcessMaster::launchSlaveProcess(). + + The timeoutMs parameter lets you specify how long the child process is allowed + to run without receiving a ping from the master before the master is considered to + have died, and handleConnectionLost() will be called. Passing <= 0 for this timeout + makes it use a default value. + + Returns true if the command-line matches and the connection is made successfully. + */ + bool initialiseFromCommandLine (const String& commandLine, + const String& commandLineUniqueID, + int timeoutMs = 0); + + //============================================================================== + /** This will be called to deliver messages from the master process. + The call will probably be made on a background thread, so be careful with your + thread-safety! You may want to respond by sending back a message with + sendMessageToMaster() + */ + virtual void handleMessageFromMaster (const MemoryBlock&) = 0; + + /** This will be called when the master process finishes connecting to this slave. + The call will probably be made on a background thread, so be careful with your thread-safety! + */ + virtual void handleConnectionMade(); + + /** This will be called when the connection to the master process is lost. + The call may be made from any thread (including the message thread). + Typically, if your process only exists to act as a slave, you should probably exit + when this happens. + */ + virtual void handleConnectionLost(); + + /** Tries to send a message to the master process. + This returns true if the message was sent, but doesn't check that it actually gets + delivered at the other end. If successful, the data will emerge in a call to your + ChildProcessMaster::handleMessageFromSlave(). + */ + bool sendMessageToMaster (const MemoryBlock&); + +private: + struct Connection; + friend struct Connection; + friend struct ContainerDeletePolicy; + ScopedPointer connection; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave) +}; + +//============================================================================== +/** + Acts as the master in a master/slave pair of connected processes. + + The ChildProcessSlave and ChildProcessMaster classes make it easy for an app + to spawn a child process, and to manage a 2-way messaging connection to control it. + + To use the system, you need to create subclasses of both ChildProcessSlave and + ChildProcessMaster. When you want your master process to launch the slave, you + just call launchSlaveProcess(), and it'll attempt to launch the executable that + you specify (which may be the same exe), and assuming it has been set-up to + correctly parse the command-line parameters (see ChildProcessSlave) then a + two-way connection will be created. + + The juce demo app has a good example of this class in action. + + @see ChildProcessSlave, InterprocessConnection, ChildProcess +*/ +class JUCE_API ChildProcessMaster +{ +public: + /** Creates an uninitialised master process object. + Use launchSlaveProcess to launch and connect to a child process. + */ + ChildProcessMaster(); + + /** Destructor. */ + virtual ~ChildProcessMaster(); + + /** Attempts to launch and connect to a slave process. + This will start the given executable, passing it a special command-line + parameter based around the commandLineUniqueID string, which must be a + short alphanumeric string (no spaces!) that identifies your app. The exe + that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine() + in its startup code, and must use a matching ID to commandLineUniqueID. + + The timeoutMs parameter lets you specify how long the child process is allowed + to go without sending a ping before it is considered to have died and + handleConnectionLost() will be called. Passing <= 0 for this timeout makes + it use a default value. + + If this all works, the method returns true, and you can begin sending and + receiving messages with the slave process. + */ + bool launchSlaveProcess (const File& executableToLaunch, + const String& commandLineUniqueID, + int timeoutMs = 0); + + /** This will be called to deliver a message from the slave process. + The call will probably be made on a background thread, so be careful with your thread-safety! + */ + virtual void handleMessageFromSlave (const MemoryBlock&) = 0; + + /** This will be called when the slave process dies or is somehow disconnected. + The call will probably be made on a background thread, so be careful with your thread-safety! + */ + virtual void handleConnectionLost(); + + /** Attempts to send a message to the slave process. + This returns true if the message was dispatched, but doesn't check that it actually + gets delivered at the other end. If successful, the data will emerge in a call to + your ChildProcessSlave::handleMessageFromMaster(). + */ + bool sendMessageToSlave (const MemoryBlock&); + +private: + ChildProcess childProcess; + + struct Connection; + friend struct Connection; + friend struct ContainerDeletePolicy; + ScopedPointer connection; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster) +}; + + +#endif // JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp new file mode 100644 index 0000000..f008cd1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp @@ -0,0 +1,365 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct InterprocessConnection::ConnectionThread : public Thread +{ + ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {} + + void run() override { owner.runThread(); } + +private: + InterprocessConnection& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread) +}; + +//============================================================================== +InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread, + const uint32 magicMessageHeaderNumber) + : callbackConnectionState (false), + useMessageThread (callbacksOnMessageThread), + magicMessageHeader (magicMessageHeaderNumber), + pipeReceiveMessageTimeout (-1) +{ + thread = new ConnectionThread (*this); +} + +InterprocessConnection::~InterprocessConnection() +{ + callbackConnectionState = false; + disconnect(); + masterReference.clear(); + thread = nullptr; +} + +//============================================================================== +bool InterprocessConnection::connectToSocket (const String& hostName, + const int portNumber, + const int timeOutMillisecs) +{ + disconnect(); + + const ScopedLock sl (pipeAndSocketLock); + socket = new StreamingSocket(); + + if (socket->connect (hostName, portNumber, timeOutMillisecs)) + { + connectionMadeInt(); + thread->startThread(); + return true; + } + + socket = nullptr; + return false; +} + +bool InterprocessConnection::connectToPipe (const String& pipeName, const int timeoutMs) +{ + disconnect(); + + ScopedPointer newPipe (new NamedPipe()); + + if (newPipe->openExisting (pipeName)) + { + const ScopedLock sl (pipeAndSocketLock); + pipeReceiveMessageTimeout = timeoutMs; + initialiseWithPipe (newPipe.release()); + return true; + } + + return false; +} + +bool InterprocessConnection::createPipe (const String& pipeName, const int timeoutMs) +{ + disconnect(); + + ScopedPointer newPipe (new NamedPipe()); + + if (newPipe->createNewPipe (pipeName)) + { + const ScopedLock sl (pipeAndSocketLock); + pipeReceiveMessageTimeout = timeoutMs; + initialiseWithPipe (newPipe.release()); + return true; + } + + return false; +} + +void InterprocessConnection::disconnect() +{ + thread->signalThreadShouldExit(); + + { + const ScopedLock sl (pipeAndSocketLock); + if (socket != nullptr) socket->close(); + if (pipe != nullptr) pipe->close(); + } + + thread->stopThread (4000); + deletePipeAndSocket(); + connectionLostInt(); +} + +void InterprocessConnection::deletePipeAndSocket() +{ + const ScopedLock sl (pipeAndSocketLock); + socket = nullptr; + pipe = nullptr; +} + +bool InterprocessConnection::isConnected() const +{ + const ScopedLock sl (pipeAndSocketLock); + + return ((socket != nullptr && socket->isConnected()) + || (pipe != nullptr && pipe->isOpen())) + && thread->isThreadRunning(); +} + +String InterprocessConnection::getConnectedHostName() const +{ + { + const ScopedLock sl (pipeAndSocketLock); + + if (pipe == nullptr && socket == nullptr) + return String(); + + if (socket != nullptr && ! socket->isLocal()) + return socket->getHostName(); + } + + return IPAddress::local().toString(); +} + +//============================================================================== +bool InterprocessConnection::sendMessage (const MemoryBlock& message) +{ + uint32 messageHeader[2] = { ByteOrder::swapIfBigEndian (magicMessageHeader), + ByteOrder::swapIfBigEndian ((uint32) message.getSize()) }; + + MemoryBlock messageData (sizeof (messageHeader) + message.getSize()); + messageData.copyFrom (messageHeader, 0, sizeof (messageHeader)); + messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize()); + + return writeData (messageData.getData(), (int) messageData.getSize()) == (int) messageData.getSize(); +} + +int InterprocessConnection::writeData (void* data, int dataSize) +{ + const ScopedLock sl (pipeAndSocketLock); + + if (socket != nullptr) + return socket->write (data, dataSize); + + if (pipe != nullptr) + return pipe->write (data, dataSize, pipeReceiveMessageTimeout); + + return 0; +} + +//============================================================================== +void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) +{ + jassert (socket == nullptr && pipe == nullptr); + socket = newSocket; + connectionMadeInt(); + thread->startThread(); +} + +void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) +{ + jassert (socket == nullptr && pipe == nullptr); + pipe = newPipe; + connectionMadeInt(); + thread->startThread(); +} + +//============================================================================== +struct ConnectionStateMessage : public MessageManager::MessageBase +{ + ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept + : owner (ipc), connectionMade (connected) + {} + + void messageCallback() override + { + if (InterprocessConnection* const ipc = owner) + { + if (connectionMade) + ipc->connectionMade(); + else + ipc->connectionLost(); + } + } + + WeakReference owner; + bool connectionMade; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage) +}; + +void InterprocessConnection::connectionMadeInt() +{ + if (! callbackConnectionState) + { + callbackConnectionState = true; + + if (useMessageThread) + (new ConnectionStateMessage (this, true))->post(); + else + connectionMade(); + } +} + +void InterprocessConnection::connectionLostInt() +{ + if (callbackConnectionState) + { + callbackConnectionState = false; + + if (useMessageThread) + (new ConnectionStateMessage (this, false))->post(); + else + connectionLost(); + } +} + +struct DataDeliveryMessage : public Message +{ + DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d) + : owner (ipc), data (d) + {} + + void messageCallback() override + { + if (InterprocessConnection* const ipc = owner) + ipc->messageReceived (data); + } + + WeakReference owner; + MemoryBlock data; +}; + +void InterprocessConnection::deliverDataInt (const MemoryBlock& data) +{ + jassert (callbackConnectionState); + + if (useMessageThread) + (new DataDeliveryMessage (this, data))->post(); + else + messageReceived (data); +} + +//============================================================================== +bool InterprocessConnection::readNextMessageInt() +{ + uint32 messageHeader[2]; + const int bytes = socket != nullptr ? socket->read (messageHeader, sizeof (messageHeader), true) + : pipe ->read (messageHeader, sizeof (messageHeader), -1); + + if (bytes == sizeof (messageHeader) + && ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader) + { + int bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]); + + if (bytesInMessage > 0) + { + MemoryBlock messageData ((size_t) bytesInMessage, true); + int bytesRead = 0; + + while (bytesInMessage > 0) + { + if (thread->threadShouldExit()) + return false; + + const int numThisTime = jmin (bytesInMessage, 65536); + void* const data = addBytesToPointer (messageData.getData(), bytesRead); + + const int bytesIn = socket != nullptr ? socket->read (data, numThisTime, true) + : pipe ->read (data, numThisTime, -1); + + if (bytesIn <= 0) + break; + + bytesRead += bytesIn; + bytesInMessage -= bytesIn; + } + + if (bytesRead >= 0) + deliverDataInt (messageData); + } + } + else if (bytes < 0) + { + if (socket != nullptr) + deletePipeAndSocket(); + + connectionLostInt(); + return false; + } + + return true; +} + +void InterprocessConnection::runThread() +{ + while (! thread->threadShouldExit()) + { + if (socket != nullptr) + { + const int ready = socket->waitUntilReady (true, 0); + + if (ready < 0) + { + deletePipeAndSocket(); + connectionLostInt(); + break; + } + + if (ready == 0) + { + thread->wait (1); + continue; + } + } + else if (pipe != nullptr) + { + if (! pipe->isOpen()) + { + deletePipeAndSocket(); + connectionLostInt(); + break; + } + } + else + { + break; + } + + if (thread->threadShouldExit() || ! readNextMessageInt()) + break; + } +} diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h new file mode 100644 index 0000000..a88e8fd --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h @@ -0,0 +1,209 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_INTERPROCESSCONNECTION_H_INCLUDED +#define JUCE_INTERPROCESSCONNECTION_H_INCLUDED + +class InterprocessConnectionServer; +class MemoryBlock; + + +//============================================================================== +/** + Manages a simple two-way messaging connection to another process, using either + a socket or a named pipe as the transport medium. + + To connect to a waiting socket or an open pipe, use the connectToSocket() or + connectToPipe() methods. If this succeeds, messages can be sent to the other end, + and incoming messages will result in a callback via the messageReceived() + method. + + To open a pipe and wait for another client to connect to it, use the createPipe() + method. + + To act as a socket server and create connections for one or more client, see the + InterprocessConnectionServer class. + + @see InterprocessConnectionServer, Socket, NamedPipe +*/ +class JUCE_API InterprocessConnection +{ +public: + //============================================================================== + /** Creates a connection. + + Connections are created manually, connecting them with the connectToSocket() + or connectToPipe() methods, or they are created automatically by a InterprocessConnectionServer + when a client wants to connect. + + @param callbacksOnMessageThread if true, callbacks to the connectionMade(), + connectionLost() and messageReceived() methods will + always be made using the message thread; if false, + these will be called immediately on the connection's + own thread. + @param magicMessageHeaderNumber a magic number to use in the header to check the + validity of the data blocks being sent and received. This + can be any number, but the sender and receiver must obviously + use matching values or they won't recognise each other. + */ + InterprocessConnection (bool callbacksOnMessageThread = true, + uint32 magicMessageHeaderNumber = 0xf2b49e2c); + + /** Destructor. */ + virtual ~InterprocessConnection(); + + //============================================================================== + /** Tries to connect this object to a socket. + + For this to work, the machine on the other end needs to have a InterprocessConnectionServer + object waiting to receive client connections on this port number. + + @param hostName the host computer, either a network address or name + @param portNumber the socket port number to try to connect to + @param timeOutMillisecs how long to keep trying before giving up + @returns true if the connection is established successfully + @see Socket + */ + bool connectToSocket (const String& hostName, + int portNumber, + int timeOutMillisecs); + + /** Tries to connect the object to an existing named pipe. + + For this to work, another process on the same computer must already have opened + an InterprocessConnection object and used createPipe() to create a pipe for this + to connect to. + + @param pipeName the name to use for the pipe - this should be unique to your app + @param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing + to the pipe, or -1 for an infinite timeout. + @returns true if it connects successfully. + @see createPipe, NamedPipe + */ + bool connectToPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs); + + /** Tries to create a new pipe for other processes to connect to. + + This creates a pipe with the given name, so that other processes can use + connectToPipe() to connect to the other end. + + @param pipeName the name to use for the pipe - this should be unique to your app + @param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing + to the pipe, or -1 for an infinite timeout. + @returns true if the pipe was created, or false if it fails (e.g. if another process is + already using using the pipe). + */ + bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs); + + /** Disconnects and closes any currently-open sockets or pipes. */ + void disconnect(); + + /** True if a socket or pipe is currently active. */ + bool isConnected() const; + + /** Returns the socket that this connection is using (or nullptr if it uses a pipe). */ + StreamingSocket* getSocket() const noexcept { return socket; } + + /** Returns the pipe that this connection is using (or nullptr if it uses a socket). */ + NamedPipe* getPipe() const noexcept { return pipe; } + + /** Returns the name of the machine at the other end of this connection. + This may return an empty string if the name is unknown. + */ + String getConnectedHostName() const; + + //============================================================================== + /** Tries to send a message to the other end of this connection. + + This will fail if it's not connected, or if there's some kind of write error. If + it succeeds, the connection object at the other end will receive the message by + a callback to its messageReceived() method. + + @see messageReceived + */ + bool sendMessage (const MemoryBlock& message); + + //============================================================================== + /** Called when the connection is first connected. + + If the connection was created with the callbacksOnMessageThread flag set, then + this will be called on the message thread; otherwise it will be called on a server + thread. + */ + virtual void connectionMade() = 0; + + /** Called when the connection is broken. + + If the connection was created with the callbacksOnMessageThread flag set, then + this will be called on the message thread; otherwise it will be called on a server + thread. + */ + virtual void connectionLost() = 0; + + /** Called when a message arrives. + + When the object at the other end of this connection sends us a message with sendMessage(), + this callback is used to deliver it to us. + + If the connection was created with the callbacksOnMessageThread flag set, then + this will be called on the message thread; otherwise it will be called on a server + thread. + + @see sendMessage + */ + virtual void messageReceived (const MemoryBlock& message) = 0; + + +private: + //============================================================================== + WeakReference::Master masterReference; + friend class WeakReference; + CriticalSection pipeAndSocketLock; + ScopedPointer socket; + ScopedPointer pipe; + bool callbackConnectionState; + const bool useMessageThread; + const uint32 magicMessageHeader; + int pipeReceiveMessageTimeout; + + friend class InterprocessConnectionServer; + void initialiseWithSocket (StreamingSocket*); + void initialiseWithPipe (NamedPipe*); + void deletePipeAndSocket(); + void connectionMadeInt(); + void connectionLostInt(); + void deliverDataInt (const MemoryBlock&); + bool readNextMessageInt(); + + struct ConnectionThread; + friend struct ConnectionThread; + friend struct ContainerDeletePolicy; + ScopedPointer thread; + void runThread(); + int writeData (void*, int); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) +}; + +#endif // JUCE_INTERPROCESSCONNECTION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp new file mode 100644 index 0000000..e9b5426 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp @@ -0,0 +1,73 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +InterprocessConnectionServer::InterprocessConnectionServer() + : Thread ("Juce IPC server") +{ +} + +InterprocessConnectionServer::~InterprocessConnectionServer() +{ + stop(); +} + +//============================================================================== +bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber) +{ + stop(); + + socket = new StreamingSocket(); + + if (socket->createListener (portNumber)) + { + startThread(); + return true; + } + + socket = nullptr; + return false; +} + +void InterprocessConnectionServer::stop() +{ + signalThreadShouldExit(); + + if (socket != nullptr) + socket->close(); + + stopThread (4000); + socket = nullptr; +} + +void InterprocessConnectionServer::run() +{ + while ((! threadShouldExit()) && socket != nullptr) + { + ScopedPointer clientSocket (socket->waitForNextConnection()); + + if (clientSocket != nullptr) + if (InterprocessConnection* newConnection = createConnectionObject()) + newConnection->initialiseWithSocket (clientSocket.release()); + } +} diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h new file mode 100644 index 0000000..8de7b8f --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.h @@ -0,0 +1,93 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED +#define JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED + + +//============================================================================== +/** + An object that waits for client sockets to connect to a port on this host, and + creates InterprocessConnection objects for each one. + + To use this, create a class derived from it which implements the createConnectionObject() + method, so that it creates suitable connection objects for each client that tries + to connect. + + @see InterprocessConnection +*/ +class JUCE_API InterprocessConnectionServer : private Thread +{ +public: + //============================================================================== + /** Creates an uninitialised server object. + */ + InterprocessConnectionServer(); + + /** Destructor. */ + ~InterprocessConnectionServer(); + + //============================================================================== + /** Starts an internal thread which listens on the given port number. + + While this is running, in another process tries to connect with the + InterprocessConnection::connectToSocket() method, this object will call + createConnectionObject() to create a connection to that client. + + Use stop() to stop the thread running. + + @see createConnectionObject, stop + */ + bool beginWaitingForSocket (int portNumber); + + /** Terminates the listener thread, if it's active. + + @see beginWaitingForSocket + */ + void stop(); + +protected: + /** Creates a suitable connection object for a client process that wants to + connect to this one. + + This will be called by the listener thread when a client process tries + to connect, and must return a new InterprocessConnection object that will + then run as this end of the connection. + + @see InterprocessConnection + */ + virtual InterprocessConnection* createConnectionObject() = 0; + + +private: + //============================================================================== + ScopedPointer socket; + + void run() override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer) +}; + + +#endif // JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/juce_events.cpp b/JuceLibraryCode/modules/juce_events/juce_events.cpp new file mode 100644 index 0000000..2110d0a --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/juce_events.cpp @@ -0,0 +1,103 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if defined (JUCE_EVENTS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +// Your project must contain an AppConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "AppConfig.h" + +#include "../juce_core/native/juce_BasicNativeHeaders.h" +#include "juce_events.h" + +#if JUCE_CATCH_UNHANDLED_EXCEPTIONS && JUCE_MODULE_AVAILABLE_juce_gui_basics + #include "../juce_gui_basics/juce_gui_basics.h" +#endif + +//============================================================================== +#if JUCE_MAC + #import + #import + #import + #import + #import + +#elif JUCE_LINUX + #include + #include + #include + #undef KeyPress + #include +#endif + +//============================================================================== +namespace juce +{ + +#include "messages/juce_ApplicationBase.cpp" +#include "messages/juce_DeletedAtShutdown.cpp" +#include "messages/juce_MessageListener.cpp" +#include "messages/juce_MessageManager.cpp" +#include "broadcasters/juce_ActionBroadcaster.cpp" +#include "broadcasters/juce_AsyncUpdater.cpp" +#include "broadcasters/juce_ChangeBroadcaster.cpp" +#include "timers/juce_MultiTimer.cpp" +#include "timers/juce_Timer.cpp" +#include "interprocess/juce_InterprocessConnection.cpp" +#include "interprocess/juce_InterprocessConnectionServer.cpp" +#include "interprocess/juce_ConnectedChildProcess.cpp" + +//============================================================================== +#if JUCE_MAC + #include "../juce_core/native/juce_osx_ObjCHelpers.h" + #include "native/juce_osx_MessageQueue.h" + #include "native/juce_mac_MessageManager.mm" + +#elif JUCE_IOS + #include "../juce_core/native/juce_osx_ObjCHelpers.h" + #include "native/juce_osx_MessageQueue.h" + #include "native/juce_ios_MessageManager.mm" + +#elif JUCE_WINDOWS + #include "native/juce_win32_HiddenMessageWindow.h" + #include "native/juce_win32_Messaging.cpp" + +#elif JUCE_LINUX + #include "native/juce_ScopedXLock.h" + #include "native/juce_linux_Messaging.cpp" + +#elif JUCE_ANDROID + #include "../juce_core/native/juce_android_JNIHelpers.h" + #include "native/juce_android_Messaging.cpp" + +#endif + +} diff --git a/JuceLibraryCode/modules/juce_events/juce_events.h b/JuceLibraryCode/modules/juce_events/juce_events.h new file mode 100644 index 0000000..7af9c9c --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/juce_events.h @@ -0,0 +1,58 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_EVENTS_H_INCLUDED +#define JUCE_EVENTS_H_INCLUDED + +//============================================================================= +#include "../juce_core/juce_core.h" + +namespace juce +{ + +#include "messages/juce_MessageManager.h" +#include "messages/juce_Message.h" +#include "messages/juce_MessageListener.h" +#include "messages/juce_CallbackMessage.h" +#include "messages/juce_DeletedAtShutdown.h" +#include "messages/juce_NotificationType.h" +#include "messages/juce_ApplicationBase.h" +#include "messages/juce_Initialisation.h" +#include "messages/juce_MountedVolumeListChangeDetector.h" +#include "broadcasters/juce_ListenerList.h" +#include "broadcasters/juce_ActionBroadcaster.h" +#include "broadcasters/juce_ActionListener.h" +#include "broadcasters/juce_AsyncUpdater.h" +#include "broadcasters/juce_ChangeListener.h" +#include "broadcasters/juce_ChangeBroadcaster.h" +#include "timers/juce_Timer.h" +#include "timers/juce_MultiTimer.h" +#include "interprocess/juce_InterprocessConnection.h" +#include "interprocess/juce_InterprocessConnectionServer.h" +#include "interprocess/juce_ConnectedChildProcess.h" +#include "native/juce_ScopedXLock.h" + +} + +#endif // JUCE_EVENTS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/juce_events.mm b/JuceLibraryCode/modules/juce_events/juce_events.mm new file mode 100644 index 0000000..45263f1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/juce_events.mm @@ -0,0 +1,25 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#include "juce_events.cpp" diff --git a/JuceLibraryCode/modules/juce_events/juce_module_info b/JuceLibraryCode/modules/juce_events/juce_module_info new file mode 100644 index 0000000..47c8ea8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/juce_module_info @@ -0,0 +1,23 @@ +{ + "id": "juce_events", + "name": "JUCE message and event handling classes", + "version": "3.1.1", + "description": "Classes for running an application's main event loop and sending/receiving messages, timers, etc.", + "website": "http://www.juce.com/juce", + "license": "GPL/Commercial", + + "dependencies": [ { "id": "juce_core", "version": "matching" } ], + + "include": "juce_events.h", + + "compile": [ { "file": "juce_events.cpp", "target": "! xcode" }, + { "file": "juce_events.mm", "target": "xcode" } ], + + "browse": [ "messages/*", + "timers/*", + "broadcasters/*", + "interprocess/*", + "native/*" ], + + "LinuxLibs": "X11" +} diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp new file mode 100644 index 0000000..dafc27e --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp @@ -0,0 +1,294 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +JUCEApplicationBase::CreateInstanceFunction JUCEApplicationBase::createInstance = 0; +JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr; + +JUCEApplicationBase::JUCEApplicationBase() + : appReturnValue (0), + stillInitialising (true) +{ + jassert (isStandaloneApp() && appInstance == nullptr); + appInstance = this; +} + +JUCEApplicationBase::~JUCEApplicationBase() +{ + jassert (appInstance == this); + appInstance = nullptr; +} + +void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept +{ + appReturnValue = newReturnValue; +} + +// This is called on the Mac and iOS where the OS doesn't allow the stack to unwind on shutdown.. +void JUCEApplicationBase::appWillTerminateByForce() +{ + JUCE_AUTORELEASEPOOL + { + { + const ScopedPointer app (appInstance); + + if (app != nullptr) + app->shutdownApp(); + } + + DeletedAtShutdown::deleteAll(); + MessageManager::deleteInstance(); + } +} + +void JUCEApplicationBase::quit() +{ + MessageManager::getInstance()->stopDispatchLoop(); +} + +void JUCEApplicationBase::sendUnhandledException (const std::exception* const e, + const char* const sourceFile, + const int lineNumber) +{ + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + app->unhandledException (e, sourceFile, lineNumber); +} + +//============================================================================== +#if ! (JUCE_IOS || JUCE_ANDROID) + #define JUCE_HANDLE_MULTIPLE_INSTANCES 1 +#endif + +#if JUCE_HANDLE_MULTIPLE_INSTANCES +struct JUCEApplicationBase::MultipleInstanceHandler : public ActionListener +{ +public: + MultipleInstanceHandler (const String& appName) + : appLock ("juceAppLock_" + appName) + { + } + + bool sendCommandLineToPreexistingInstance() + { + if (appLock.enter (0)) + return false; + + JUCEApplicationBase* const app = JUCEApplicationBase::getInstance(); + jassert (app != nullptr); + + MessageManager::broadcastMessage (app->getApplicationName() + + "/" + app->getCommandLineParameters()); + return true; + } + + void actionListenerCallback (const String& message) override + { + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + { + const String appName (app->getApplicationName()); + + if (message.startsWith (appName + "/")) + app->anotherInstanceStarted (message.substring (appName.length() + 1)); + } + } + +private: + InterProcessLock appLock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultipleInstanceHandler) +}; + +bool JUCEApplicationBase::sendCommandLineToPreexistingInstance() +{ + jassert (multipleInstanceHandler == nullptr); // this must only be called once! + + multipleInstanceHandler = new MultipleInstanceHandler (getApplicationName()); + return multipleInstanceHandler->sendCommandLineToPreexistingInstance(); +} + +#else +struct JUCEApplicationBase::MultipleInstanceHandler {}; +#endif + +//============================================================================== +#if JUCE_ANDROID + +StringArray JUCEApplicationBase::getCommandLineParameterArray() { return StringArray(); } +String JUCEApplicationBase::getCommandLineParameters() { return String(); } + +#else + +#if JUCE_WINDOWS && ! defined (_CONSOLE) + +String JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameters() +{ + return CharacterFunctions::findEndOfToken (CharPointer_UTF16 (GetCommandLineW()), + CharPointer_UTF16 (L" "), + CharPointer_UTF16 (L"\"")).findEndOfWhitespace(); +} + +StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray() +{ + StringArray s; + + int argc = 0; + if (LPWSTR* const argv = CommandLineToArgvW (GetCommandLineW(), &argc)) + { + s = StringArray (argv + 1, argc - 1); + LocalFree (argv); + } + + return s; +} + +#else + +#if JUCE_IOS + extern int juce_iOSMain (int argc, const char* argv[]); +#endif + +#if JUCE_MAC + extern void initialiseNSApplication(); +#endif + +#if JUCE_WINDOWS + const char* const* juce_argv = nullptr; + int juce_argc = 0; +#else + extern const char* const* juce_argv; // declared in juce_core + extern int juce_argc; +#endif + +String JUCEApplicationBase::getCommandLineParameters() +{ + String argString; + + for (int i = 1; i < juce_argc; ++i) + { + String arg (juce_argv[i]); + + if (arg.containsChar (' ') && ! arg.isQuotedString()) + arg = arg.quoted ('"'); + + argString << arg << ' '; + } + + return argString.trim(); +} + +StringArray JUCEApplicationBase::getCommandLineParameterArray() +{ + return StringArray (juce_argv + 1, juce_argc - 1); +} + +int JUCEApplicationBase::main (int argc, const char* argv[]) +{ + JUCE_AUTORELEASEPOOL + { + juce_argc = argc; + juce_argv = argv; + + #if JUCE_MAC + initialiseNSApplication(); + #endif + + #if JUCE_IOS + return juce_iOSMain (argc, argv); + #else + return JUCEApplicationBase::main(); + #endif + } +} + +#endif + +//============================================================================== +int JUCEApplicationBase::main() +{ + ScopedJuceInitialiser_GUI libraryInitialiser; + jassert (createInstance != nullptr); + + const ScopedPointer app (createInstance()); + jassert (app != nullptr); + + if (! app->initialiseApp()) + return app->getApplicationReturnValue(); + + JUCE_TRY + { + // loop until a quit message is received.. + MessageManager::getInstance()->runDispatchLoop(); + } + JUCE_CATCH_EXCEPTION + + return app->shutdownApp(); +} + +#endif + +//============================================================================== +bool JUCEApplicationBase::initialiseApp() +{ + #if JUCE_HANDLE_MULTIPLE_INSTANCES + if ((! moreThanOneInstanceAllowed()) && sendCommandLineToPreexistingInstance()) + { + DBG ("Another instance is running - quitting..."); + return false; + } + #endif + + // let the app do its setting-up.. + initialise (getCommandLineParameters()); + + stillInitialising = false; + + if (MessageManager::getInstance()->hasStopMessageBeenSent()) + return false; + + #if JUCE_HANDLE_MULTIPLE_INSTANCES + if (multipleInstanceHandler != nullptr) + MessageManager::getInstance()->registerBroadcastListener (multipleInstanceHandler); + #endif + + return true; +} + +int JUCEApplicationBase::shutdownApp() +{ + jassert (JUCEApplicationBase::getInstance() == this); + + #if JUCE_HANDLE_MULTIPLE_INSTANCES + if (multipleInstanceHandler != nullptr) + MessageManager::getInstance()->deregisterBroadcastListener (multipleInstanceHandler); + #endif + + JUCE_TRY + { + // give the app a chance to clean up.. + shutdown(); + } + JUCE_CATCH_EXCEPTION + + multipleInstanceHandler = nullptr; + return getApplicationReturnValue(); +} diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.h b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.h new file mode 100644 index 0000000..073e5d6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.h @@ -0,0 +1,283 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONBASE_H_INCLUDED +#define JUCE_APPLICATIONBASE_H_INCLUDED + + +//============================================================================== +/** + Abstract base class for application classes. + + Note that in the juce_gui_basics module, there's a utility class JUCEApplication + which derives from JUCEApplicationBase, and takes care of a few chores. Most + of the time you'll want to derive your class from JUCEApplication rather than + using JUCEApplicationBase directly, but if you're not using the juce_gui_basics + module then you might need to go straight to this base class. + + Any application that wants to run an event loop must declare a subclass of + JUCEApplicationBase, and implement its various pure virtual methods. + + It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file + to declare an instance of this class and generate suitable platform-specific + boilerplate code to launch the app. + + e.g. @code + class MyJUCEApp : public JUCEApplication + { + public: + MyJUCEApp() {} + ~MyJUCEApp() {} + + void initialise (const String& commandLine) override + { + myMainWindow = new MyApplicationWindow(); + myMainWindow->setBounds (100, 100, 400, 500); + myMainWindow->setVisible (true); + } + + void shutdown() override + { + myMainWindow = nullptr; + } + + const String getApplicationName() override + { + return "Super JUCE-o-matic"; + } + + const String getApplicationVersion() override + { + return "1.0"; + } + + private: + ScopedPointer myMainWindow; + }; + + // this generates boilerplate code to launch our app class: + START_JUCE_APPLICATION (MyJUCEApp) + @endcode + + @see JUCEApplication, START_JUCE_APPLICATION +*/ +class JUCE_API JUCEApplicationBase +{ +protected: + //============================================================================== + JUCEApplicationBase(); + +public: + /** Destructor. */ + virtual ~JUCEApplicationBase(); + + //============================================================================== + /** Returns the global instance of the application object that's running. */ + static JUCEApplicationBase* getInstance() noexcept { return appInstance; } + + //============================================================================== + /** Returns the application's name. */ + virtual const String getApplicationName() = 0; + + /** Returns the application's version number. */ + virtual const String getApplicationVersion() = 0; + + /** Checks whether multiple instances of the app are allowed. + + If you application class returns true for this, more than one instance is + permitted to run (except on the Mac where this isn't possible). + + If it's false, the second instance won't start, but it you will still get a + callback to anotherInstanceStarted() to tell you about this - which + gives you a chance to react to what the user was trying to do. + */ + virtual bool moreThanOneInstanceAllowed() = 0; + + /** Called when the application starts. + + This will be called once to let the application do whatever initialisation + it needs, create its windows, etc. + + After the method returns, the normal event-dispatch loop will be run, + until the quit() method is called, at which point the shutdown() + method will be called to let the application clear up anything it needs + to delete. + + If during the initialise() method, the application decides not to start-up + after all, it can just call the quit() method and the event loop won't be run. + + @param commandLineParameters the line passed in does not include the name of + the executable, just the parameter list. To get the + parameters as an array, you can call + JUCEApplication::getCommandLineParameters() + @see shutdown, quit + */ + virtual void initialise (const String& commandLineParameters) = 0; + + /* Called to allow the application to clear up before exiting. + + After JUCEApplication::quit() has been called, the event-dispatch loop will + terminate, and this method will get called to allow the app to sort itself + out. + + Be careful that nothing happens in this method that might rely on messages + being sent, or any kind of window activity, because the message loop is no + longer running at this point. + + @see DeletedAtShutdown + */ + virtual void shutdown() = 0; + + /** Indicates that the user has tried to start up another instance of the app. + + This will get called even if moreThanOneInstanceAllowed() is false. + */ + virtual void anotherInstanceStarted (const String& commandLine) = 0; + + /** Called when the operating system is trying to close the application. + + The default implementation of this method is to call quit(), but it may + be overloaded to ignore the request or do some other special behaviour + instead. For example, you might want to offer the user the chance to save + their changes before quitting, and give them the chance to cancel. + + If you want to send a quit signal to your app, this is the correct method + to call, because it means that requests that come from the system get handled + in the same way as those from your own application code. So e.g. you'd + call this method from a "quit" item on a menu bar. + */ + virtual void systemRequestedQuit() = 0; + + /** This method is called when the application is being put into background mode + by the operating system. + */ + virtual void suspended() = 0; + + /** This method is called when the application is being woken from background mode + by the operating system. + */ + virtual void resumed() = 0; + + /** If any unhandled exceptions make it through to the message dispatch loop, this + callback will be triggered, in case you want to log them or do some other + type of error-handling. + + If the type of exception is derived from the std::exception class, the pointer + passed-in will be valid. If the exception is of unknown type, this pointer + will be null. + */ + virtual void unhandledException (const std::exception*, + const String& sourceFilename, + int lineNumber) = 0; + + //============================================================================== + /** Signals that the main message loop should stop and the application should terminate. + + This isn't synchronous, it just posts a quit message to the main queue, and + when this message arrives, the message loop will stop, the shutdown() method + will be called, and the app will exit. + + Note that this will cause an unconditional quit to happen, so if you need an + extra level before this, e.g. to give the user the chance to save their work + and maybe cancel the quit, you'll need to handle this in the systemRequestedQuit() + method - see that method's help for more info. + + @see MessageManager + */ + static void quit(); + + //============================================================================== + /** Returns the application's command line parameters as a set of strings. + @see getCommandLineParameters + */ + static StringArray JUCE_CALLTYPE getCommandLineParameterArray(); + + /** Returns the application's command line parameters as a single string. + @see getCommandLineParameterArray + */ + static String JUCE_CALLTYPE getCommandLineParameters(); + + //============================================================================== + /** Sets the value that should be returned as the application's exit code when the + app quits. + + This is the value that's returned by the main() function. Normally you'd leave this + as 0 unless you want to indicate an error code. + + @see getApplicationReturnValue + */ + void setApplicationReturnValue (int newReturnValue) noexcept; + + /** Returns the value that has been set as the application's exit code. + @see setApplicationReturnValue + */ + int getApplicationReturnValue() const noexcept { return appReturnValue; } + + //============================================================================== + /** Returns true if this executable is running as an app (as opposed to being a plugin + or other kind of shared library. */ + static bool isStandaloneApp() noexcept { return createInstance != nullptr; } + + /** Returns true if the application hasn't yet completed its initialise() method + and entered the main event loop. + + This is handy for things like splash screens to know when the app's up-and-running + properly. + */ + bool isInitialising() const noexcept { return stillInitialising; } + + + //============================================================================== + #ifndef DOXYGEN + // The following methods are for internal use only... + static int main(); + static int main (int argc, const char* argv[]); + + static void appWillTerminateByForce(); + typedef JUCEApplicationBase* (*CreateInstanceFunction)(); + static CreateInstanceFunction createInstance; + + virtual bool initialiseApp(); + static void JUCE_CALLTYPE sendUnhandledException (const std::exception*, const char* sourceFile, int lineNumber); + bool sendCommandLineToPreexistingInstance(); + #endif + +private: + //============================================================================== + static JUCEApplicationBase* appInstance; + int appReturnValue; + bool stillInitialising; + + struct MultipleInstanceHandler; + friend struct MultipleInstanceHandler; + friend struct ContainerDeletePolicy; + ScopedPointer multipleInstanceHandler; + + int shutdownApp(); + + JUCE_DECLARE_NON_COPYABLE (JUCEApplicationBase) +}; + + +#endif // JUCE_APPLICATIONBASE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_CallbackMessage.h b/JuceLibraryCode/modules/juce_events/messages/juce_CallbackMessage.h new file mode 100644 index 0000000..799defd --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_CallbackMessage.h @@ -0,0 +1,73 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CALLBACKMESSAGE_H_INCLUDED +#define JUCE_CALLBACKMESSAGE_H_INCLUDED + + +//============================================================================== +/** + A message that invokes a callback method when it gets delivered. + + You can use this class to fire off actions that you want to be performed later + on the message thread. + + To use it, create a subclass of CallbackMessage which implements the messageCallback() + method, then call post() to dispatch it. The event thread will then invoke your + messageCallback() method later on, and will automatically delete the message object + afterwards. + + Always create a new instance of a CallbackMessage on the heap, as it will be + deleted automatically after the message has been delivered. + + @see MessageManager, MessageListener, ActionListener, ChangeListener +*/ +class JUCE_API CallbackMessage : public MessageManager::MessageBase +{ +public: + //============================================================================== + CallbackMessage() noexcept {} + + /** Destructor. */ + ~CallbackMessage() {} + + //============================================================================== + /** Called when the message is delivered. + + You should implement this method and make it do whatever action you want + to perform. + + Note that like all other messages, this object will be deleted immediately + after this method has been invoked. + */ + virtual void messageCallback() = 0; + +private: + // Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered + // messages still in the system event queue. These aren't harmful, but can cause annoying assertions. + JUCE_DECLARE_NON_COPYABLE (CallbackMessage) +}; + + +#endif // JUCE_CALLBACKMESSAGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.cpp new file mode 100644 index 0000000..d3c6e97 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.cpp @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +static SpinLock deletedAtShutdownLock; + +DeletedAtShutdown::DeletedAtShutdown() +{ + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); + getObjects().add (this); +} + +DeletedAtShutdown::~DeletedAtShutdown() +{ + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); + getObjects().removeFirstMatchingValue (this); +} + +void DeletedAtShutdown::deleteAll() +{ + // make a local copy of the array, so it can't get into a loop if something + // creates another DeletedAtShutdown object during its destructor. + Array localCopy; + + { + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); + localCopy = getObjects(); + } + + for (int i = localCopy.size(); --i >= 0;) + { + JUCE_TRY + { + DeletedAtShutdown* deletee = localCopy.getUnchecked(i); + + // double-check that it's not already been deleted during another object's destructor. + { + const SpinLock::ScopedLockType sl (deletedAtShutdownLock); + if (! getObjects().contains (deletee)) + deletee = nullptr; + } + + delete deletee; + } + JUCE_CATCH_EXCEPTION + } + + // if no objects got re-created during shutdown, this should have been emptied by their + // destructors + jassert (getObjects().size() == 0); + + getObjects().clear(); // just to make sure the array doesn't have any memory still allocated +} + +Array & DeletedAtShutdown::getObjects() +{ + static Array objects; + return objects; +} diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.h b/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.h new file mode 100644 index 0000000..ce1f10e --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_DeletedAtShutdown.h @@ -0,0 +1,68 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DELETEDATSHUTDOWN_H_INCLUDED +#define JUCE_DELETEDATSHUTDOWN_H_INCLUDED + + +//============================================================================== +/** + Classes derived from this will be automatically deleted when the application exits. + + After JUCEApplicationBase::shutdown() has been called, any objects derived from + DeletedAtShutdown which are still in existence will be deleted in the reverse + order to that in which they were created. + + So if you've got a singleton and don't want to have to explicitly delete it, just + inherit from this and it'll be taken care of. +*/ +class JUCE_API DeletedAtShutdown +{ +protected: + /** Creates a DeletedAtShutdown object. */ + DeletedAtShutdown(); + + /** Destructor. + + It's ok to delete these objects explicitly - it's only the ones left + dangling at the end that will be deleted automatically. + */ + virtual ~DeletedAtShutdown(); + + +public: + /** Deletes all extant objects. + + This shouldn't be used by applications, as it's called automatically + in the shutdown code of the JUCEApplicationBase class. + */ + static void deleteAll(); + +private: + static Array & getObjects(); + + JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown) +}; + +#endif // JUCE_DELETEDATSHUTDOWN_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_Initialisation.h b/JuceLibraryCode/modules/juce_events/messages/juce_Initialisation.h new file mode 100644 index 0000000..d3302dd --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_Initialisation.h @@ -0,0 +1,112 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_INITIALISATION_H_INCLUDED +#define JUCE_INITIALISATION_H_INCLUDED + + +//============================================================================== +/** Initialises Juce's GUI classes. + + If you're embedding Juce into an application that uses its own event-loop rather + than using the START_JUCE_APPLICATION macro, call this function before making any + Juce calls, to make sure things are initialised correctly. + + Note that if you're creating a Juce DLL for Windows, you may also need to call the + Process::setCurrentModuleInstanceHandle() method. + + @see shutdownJuce_GUI() +*/ +JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); + +/** Clears up any static data being used by Juce's GUI classes. + + If you're embedding Juce into an application that uses its own event-loop rather + than using the START_JUCE_APPLICATION macro, call this function in your shutdown + code to clean up any juce objects that might be lying around. + + @see initialiseJuce_GUI() +*/ +JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); + + +//============================================================================== +/** A utility object that helps you initialise and shutdown Juce correctly + using an RAII pattern. + + When the first instance of this class is created, it calls initialiseJuce_GUI(), + and when the last instance is deleted, it calls shutdownJuce_GUI(), so that you + can easily be sure that as long as at least one instance of the class exists, the + library will be initialised. + + This class is particularly handy to use at the beginning of a console app's + main() function, because it'll take care of shutting down whenever you return + from the main() call. + + Be careful with your threading though - to be safe, you should always make sure + that these objects are created and deleted on the message thread. +*/ +class JUCE_API ScopedJuceInitialiser_GUI +{ +public: + /** The constructor simply calls initialiseJuce_GUI(). */ + ScopedJuceInitialiser_GUI(); + + /** The destructor simply calls shutdownJuce_GUI(). */ + ~ScopedJuceInitialiser_GUI(); +}; + + +//============================================================================== +/** + To start a JUCE app, use this macro: START_JUCE_APPLICATION (AppSubClass) where + AppSubClass is the name of a class derived from JUCEApplication or JUCEApplicationBase. + + See the JUCEApplication and JUCEApplicationBase class documentation for more details. +*/ +#ifdef DOXYGEN + #define START_JUCE_APPLICATION(AppClass) +#elif JUCE_ANDROID + #define START_JUCE_APPLICATION(AppClass) \ + juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } + +#else + #if JUCE_WINDOWS && ! defined (_CONSOLE) + #define JUCE_MAIN_FUNCTION int __stdcall WinMain (struct HINSTANCE__*, struct HINSTANCE__*, char*, int) + #define JUCE_MAIN_FUNCTION_ARGS + #else + #define JUCE_MAIN_FUNCTION int main (int argc, char* argv[]) + #define JUCE_MAIN_FUNCTION_ARGS argc, (const char**) argv + #endif + + #define START_JUCE_APPLICATION(AppClass) \ + static juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \ + extern "C" JUCE_MAIN_FUNCTION \ + { \ + juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \ + return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \ + } +#endif + +#endif // JUCE_INITIALISATION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_Message.h b/JuceLibraryCode/modules/juce_events/messages/juce_Message.h new file mode 100644 index 0000000..4ae55f6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_Message.h @@ -0,0 +1,65 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MESSAGE_H_INCLUDED +#define JUCE_MESSAGE_H_INCLUDED + +class MessageListener; + + +//============================================================================== +/** The base class for objects that can be sent to a MessageListener. + + If you want to send a message that carries some kind of custom data, just + create a subclass of Message with some appropriate member variables to hold + your data. + + Always create a new instance of a Message object on the heap, as it will be + deleted automatically after the message has been delivered. + + @see MessageListener, MessageManager, ActionListener, ChangeListener +*/ +class JUCE_API Message : public MessageManager::MessageBase +{ +public: + //============================================================================== + /** Creates an uninitialised message. */ + Message() noexcept; + ~Message(); + + typedef ReferenceCountedObjectPtr Ptr; + + //============================================================================== +private: + friend class MessageListener; + WeakReference recipient; + void messageCallback() override; + + // Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered + // messages still in the system event queue. These aren't harmful, but can cause annoying assertions. + JUCE_DECLARE_NON_COPYABLE (Message) +}; + + +#endif // JUCE_MESSAGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.cpp new file mode 100644 index 0000000..b398ffa --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.cpp @@ -0,0 +1,49 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +Message::Message() noexcept {} +Message::~Message() {} + +void Message::messageCallback() +{ + if (MessageListener* const r = recipient) + r->handleMessage (*this); +} + +MessageListener::MessageListener() noexcept +{ + // Are you trying to create a messagelistener before or after juce has been intialised?? + jassert (MessageManager::getInstanceWithoutCreating() != nullptr); +} + +MessageListener::~MessageListener() +{ + masterReference.clear(); +} + +void MessageListener::postMessage (Message* const message) const +{ + message->recipient = const_cast (this); + message->post(); +} diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.h b/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.h new file mode 100644 index 0000000..ddd51e2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageListener.h @@ -0,0 +1,72 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MESSAGELISTENER_H_INCLUDED +#define JUCE_MESSAGELISTENER_H_INCLUDED + + +//============================================================================== +/** + MessageListener subclasses can post and receive Message objects. + + @see Message, MessageManager, ActionListener, ChangeListener +*/ +class JUCE_API MessageListener +{ +public: + //============================================================================== + MessageListener() noexcept; + + /** Destructor. */ + virtual ~MessageListener(); + + //============================================================================== + /** This is the callback method that receives incoming messages. + + This is called by the MessageManager from its dispatch loop. + + @see postMessage + */ + virtual void handleMessage (const Message& message) = 0; + + //============================================================================== + /** Sends a message to the message queue, for asynchronous delivery to this listener + later on. + + This method can be called safely by any thread. + + @param message the message object to send - this will be deleted + automatically by the message queue, so make sure it's + allocated on the heap, not the stack! + @see handleMessage + */ + void postMessage (Message* message) const; + +private: + WeakReference::Master masterReference; + friend class WeakReference; +}; + + +#endif // JUCE_MESSAGELISTENER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp new file mode 100644 index 0000000..44c2adc --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp @@ -0,0 +1,375 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +MessageManager::MessageManager() noexcept + : quitMessagePosted (false), + quitMessageReceived (false), + messageThreadId (Thread::getCurrentThreadId()), + threadWithLock (0) +{ + if (JUCEApplicationBase::isStandaloneApp()) + Thread::setCurrentThreadName ("Juce Message Thread"); +} + +MessageManager::~MessageManager() noexcept +{ + broadcaster = nullptr; + + doPlatformSpecificShutdown(); + + jassert (instance == this); + instance = nullptr; // do this last in case this instance is still needed by doPlatformSpecificShutdown() +} + +MessageManager* MessageManager::instance = nullptr; + +MessageManager* MessageManager::getInstance() +{ + if (instance == nullptr) + { + instance = new MessageManager(); + doPlatformSpecificInitialisation(); + } + + return instance; +} + +MessageManager* MessageManager::getInstanceWithoutCreating() noexcept +{ + return instance; +} + +void MessageManager::deleteInstance() +{ + deleteAndZero (instance); +} + +//============================================================================== +bool MessageManager::MessageBase::post() +{ + MessageManager* const mm = MessageManager::instance; + + if (mm == nullptr || mm->quitMessagePosted || ! postMessageToSystemQueue (this)) + { + Ptr deleter (this); // (this will delete messages that were just created with a 0 ref count) + return false; + } + + return true; +} + +//============================================================================== +#if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS) +void MessageManager::runDispatchLoop() +{ + runDispatchLoopUntil (-1); +} + +bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) +{ + jassert (isThisTheMessageThread()); // must only be called by the message thread + + const int64 endTime = Time::currentTimeMillis() + millisecondsToRunFor; + + while (! quitMessageReceived) + { + JUCE_TRY + { + if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) + Thread::sleep (1); + } + JUCE_CATCH_EXCEPTION + + if (millisecondsToRunFor >= 0 && Time::currentTimeMillis() >= endTime) + break; + } + + return ! quitMessageReceived; +} + +class MessageManager::QuitMessage : public MessageManager::MessageBase +{ +public: + QuitMessage() {} + + void messageCallback() override + { + if (MessageManager* const mm = MessageManager::instance) + mm->quitMessageReceived = true; + } + + JUCE_DECLARE_NON_COPYABLE (QuitMessage) +}; + +void MessageManager::stopDispatchLoop() +{ + (new QuitMessage())->post(); + quitMessagePosted = true; +} + +#endif + +//============================================================================== +#if JUCE_COMPILER_SUPPORTS_LAMBDAS +struct AsyncFunction : private MessageManager::MessageBase +{ + AsyncFunction (std::function f) : fn (f) { post(); } + +private: + std::function fn; + void messageCallback() override { fn(); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncFunction) +}; + +void MessageManager::callAsync (std::function f) +{ + new AsyncFunction (f); +} +#endif + +//============================================================================== +class AsyncFunctionCallback : public MessageManager::MessageBase +{ +public: + AsyncFunctionCallback (MessageCallbackFunction* const f, void* const param) + : result (nullptr), func (f), parameter (param) + {} + + void messageCallback() override + { + result = (*func) (parameter); + finished.signal(); + } + + WaitableEvent finished; + void* volatile result; + +private: + MessageCallbackFunction* const func; + void* const parameter; + + JUCE_DECLARE_NON_COPYABLE (AsyncFunctionCallback) +}; + +void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* const func, void* const parameter) +{ + if (isThisTheMessageThread()) + return func (parameter); + + // If this thread has the message manager locked, then this will deadlock! + jassert (! currentThreadHasLockedMessageManager()); + + const ReferenceCountedObjectPtr message (new AsyncFunctionCallback (func, parameter)); + + if (message->post()) + { + message->finished.wait(); + return message->result; + } + + jassertfalse; // the OS message queue failed to send the message! + return nullptr; +} + +//============================================================================== +void MessageManager::deliverBroadcastMessage (const String& value) +{ + if (broadcaster != nullptr) + broadcaster->sendActionMessage (value); +} + +void MessageManager::registerBroadcastListener (ActionListener* const listener) +{ + if (broadcaster == nullptr) + broadcaster = new ActionBroadcaster(); + + broadcaster->addActionListener (listener); +} + +void MessageManager::deregisterBroadcastListener (ActionListener* const listener) +{ + if (broadcaster != nullptr) + broadcaster->removeActionListener (listener); +} + +//============================================================================== +bool MessageManager::isThisTheMessageThread() const noexcept +{ + return Thread::getCurrentThreadId() == messageThreadId; +} + +void MessageManager::setCurrentThreadAsMessageThread() +{ + const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); + + if (messageThreadId != thisThread) + { + messageThreadId = thisThread; + + // This is needed on windows to make sure the message window is created by this thread + doPlatformSpecificShutdown(); + doPlatformSpecificInitialisation(); + } +} + +bool MessageManager::currentThreadHasLockedMessageManager() const noexcept +{ + const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); + return thisThread == messageThreadId || thisThread == threadWithLock; +} + +//============================================================================== +//============================================================================== +/* The only safe way to lock the message thread while another thread does + some work is by posting a special message, whose purpose is to tie up the event + loop until the other thread has finished its business. + + Any other approach can get horribly deadlocked if the OS uses its own hidden locks which + get locked before making an event callback, because if the same OS lock gets indirectly + accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens + in Cocoa). +*/ +class MessageManagerLock::BlockingMessage : public MessageManager::MessageBase +{ +public: + BlockingMessage() noexcept {} + + void messageCallback() override + { + lockedEvent.signal(); + releaseEvent.wait(); + } + + WaitableEvent lockedEvent, releaseEvent; + + JUCE_DECLARE_NON_COPYABLE (BlockingMessage) +}; + +//============================================================================== +MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) + : blockingMessage(), locked (attemptLock (threadToCheck, nullptr)) +{ +} + +MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) + : blockingMessage(), locked (attemptLock (nullptr, jobToCheckForExitSignal)) +{ +} + +bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob* const job) +{ + MessageManager* const mm = MessageManager::instance; + + if (mm == nullptr) + return false; + + if (mm->currentThreadHasLockedMessageManager()) + return true; + + if (threadToCheck == nullptr && job == nullptr) + { + mm->lockingLock.enter(); + } + else + { + while (! mm->lockingLock.tryEnter()) + { + if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) + || (job != nullptr && job->shouldExit())) + return false; + + Thread::yield(); + } + } + + blockingMessage = new BlockingMessage(); + + if (! blockingMessage->post()) + { + blockingMessage = nullptr; + return false; + } + + while (! blockingMessage->lockedEvent.wait (20)) + { + if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) + || (job != nullptr && job->shouldExit())) + { + blockingMessage->releaseEvent.signal(); + blockingMessage = nullptr; + mm->lockingLock.exit(); + return false; + } + } + + jassert (mm->threadWithLock == 0); + + mm->threadWithLock = Thread::getCurrentThreadId(); + return true; +} + +MessageManagerLock::~MessageManagerLock() noexcept +{ + if (blockingMessage != nullptr) + { + MessageManager* const mm = MessageManager::instance; + + jassert (mm == nullptr || mm->currentThreadHasLockedMessageManager()); + + blockingMessage->releaseEvent.signal(); + blockingMessage = nullptr; + + if (mm != nullptr) + { + mm->threadWithLock = 0; + mm->lockingLock.exit(); + } + } +} + +//============================================================================== +JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); +JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() +{ + JUCE_AUTORELEASEPOOL + { + MessageManager::getInstance(); + } +} + +JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); +JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI() +{ + JUCE_AUTORELEASEPOOL + { + DeletedAtShutdown::deleteAll(); + MessageManager::deleteInstance(); + } +} + +static int numScopedInitInstances = 0; + +ScopedJuceInitialiser_GUI::ScopedJuceInitialiser_GUI() { if (numScopedInitInstances++ == 0) initialiseJuce_GUI(); } +ScopedJuceInitialiser_GUI::~ScopedJuceInitialiser_GUI() { if (--numScopedInitInstances == 0) shutdownJuce_GUI(); } diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h new file mode 100644 index 0000000..0901b29 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h @@ -0,0 +1,335 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MESSAGEMANAGER_H_INCLUDED +#define JUCE_MESSAGEMANAGER_H_INCLUDED + +class MessageManagerLock; +class ThreadPoolJob; +class ActionListener; +class ActionBroadcaster; + + +//============================================================================== +/** See MessageManager::callFunctionOnMessageThread() for use of this function type +*/ +typedef void* (MessageCallbackFunction) (void* userData); + + +//============================================================================== +/** + This class is in charge of the application's event-dispatch loop. + + @see Message, CallbackMessage, MessageManagerLock, JUCEApplication, JUCEApplicationBase +*/ +class JUCE_API MessageManager +{ +public: + //============================================================================== + /** Returns the global instance of the MessageManager. */ + static MessageManager* getInstance(); + + /** Returns the global instance of the MessageManager, or nullptr if it doesn't exist. */ + static MessageManager* getInstanceWithoutCreating() noexcept; + + /** Deletes the global MessageManager instance. + Does nothing if no instance had been created. + */ + static void deleteInstance(); + + //============================================================================== + /** Runs the event dispatch loop until a stop message is posted. + + This method is only intended to be run by the application's startup routine, + as it blocks, and will only return after the stopDispatchLoop() method has been used. + + @see stopDispatchLoop + */ + void runDispatchLoop(); + + /** Sends a signal that the dispatch loop should terminate. + + After this is called, the runDispatchLoop() or runDispatchLoopUntil() methods + will be interrupted and will return. + + @see runDispatchLoop + */ + void stopDispatchLoop(); + + /** Returns true if the stopDispatchLoop() method has been called. + */ + bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted; } + + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + /** Synchronously dispatches messages until a given time has elapsed. + + Returns false if a quit message has been posted by a call to stopDispatchLoop(), + otherwise returns true. + */ + bool runDispatchLoopUntil (int millisecondsToRunFor); + #endif + + //============================================================================== + #if JUCE_COMPILER_SUPPORTS_LAMBDAS + /** Asynchronously invokes a function or C++11 lambda on the message thread. + Internally this uses the CallbackMessage class to invoke the callback. + */ + static void callAsync (std::function); + #endif + + /** Calls a function using the message-thread. + + This can be used by any thread to cause this function to be called-back + by the message thread. If it's the message-thread that's calling this method, + then the function will just be called; if another thread is calling, a message + will be posted to the queue, and this method will block until that message + is delivered, the function is called, and the result is returned. + + Be careful not to cause any deadlocks with this! It's easy to do - e.g. if the caller + thread has a critical section locked, which an unrelated message callback then tries to lock + before the message thread gets round to processing this callback. + + @param callback the function to call - its signature must be @code + void* myCallbackFunction (void*) @endcode + @param userData a user-defined pointer that will be passed to the function that gets called + @returns the value that the callback function returns. + @see MessageManagerLock + */ + void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData); + + /** Returns true if the caller-thread is the message thread. */ + bool isThisTheMessageThread() const noexcept; + + /** Called to tell the manager that the current thread is the one that's running the dispatch loop. + + (Best to ignore this method unless you really know what you're doing..) + @see getCurrentMessageThread + */ + void setCurrentThreadAsMessageThread(); + + /** Returns the ID of the current message thread, as set by setCurrentThreadAsMessageThread(). + + (Best to ignore this method unless you really know what you're doing..) + @see setCurrentThreadAsMessageThread + */ + Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; } + + /** Returns true if the caller thread has currently got the message manager locked. + + see the MessageManagerLock class for more info about this. + + This will be true if the caller is the message thread, because that automatically + gains a lock while a message is being dispatched. + */ + bool currentThreadHasLockedMessageManager() const noexcept; + + //============================================================================== + /** Sends a message to all other JUCE applications that are running. + + @param messageText the string that will be passed to the actionListenerCallback() + method of the broadcast listeners in the other app. + @see registerBroadcastListener, ActionListener + */ + static void broadcastMessage (const String& messageText); + + /** Registers a listener to get told about broadcast messages. + + The actionListenerCallback() callback's string parameter + is the message passed into broadcastMessage(). + + @see broadcastMessage + */ + void registerBroadcastListener (ActionListener* listener); + + /** Deregisters a broadcast listener. */ + void deregisterBroadcastListener (ActionListener* listener); + + //============================================================================== + /** Internal class used as the base class for all message objects. + You shouldn't need to use this directly - see the CallbackMessage or Message + classes instead. + */ + class JUCE_API MessageBase : public ReferenceCountedObject + { + public: + MessageBase() noexcept {} + virtual ~MessageBase() {} + + virtual void messageCallback() = 0; + bool post(); + + typedef ReferenceCountedObjectPtr Ptr; + + JUCE_DECLARE_NON_COPYABLE (MessageBase) + }; + + //============================================================================== + #ifndef DOXYGEN + // Internal methods - do not use! + void deliverBroadcastMessage (const String&); + ~MessageManager() noexcept; + #endif + +private: + //============================================================================== + MessageManager() noexcept; + + static MessageManager* instance; + + friend class MessageBase; + class QuitMessage; + friend class QuitMessage; + friend class MessageManagerLock; + + ScopedPointer broadcaster; + bool quitMessagePosted, quitMessageReceived; + Thread::ThreadID messageThreadId; + Thread::ThreadID volatile threadWithLock; + CriticalSection lockingLock; + + static bool postMessageToSystemQueue (MessageBase*); + static void* exitModalLoopCallback (void*); + static void doPlatformSpecificInitialisation(); + static void doPlatformSpecificShutdown(); + static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageManager) +}; + + +//============================================================================== +/** Used to make sure that the calling thread has exclusive access to the message loop. + + Because it's not thread-safe to call any of the Component or other UI classes + from threads other than the message thread, one of these objects can be used to + lock the message loop and allow this to be done. The message thread will be + suspended for the lifetime of the MessageManagerLock object, so create one on + the stack like this: @code + void MyThread::run() + { + someData = 1234; + + const MessageManagerLock mmLock; + // the event loop will now be locked so it's safe to make a few calls.. + + myComponent->setBounds (newBounds); + myComponent->repaint(); + + // ..the event loop will now be unlocked as the MessageManagerLock goes out of scope + } + @endcode + + Obviously be careful not to create one of these and leave it lying around, or + your app will grind to a halt! + + Another caveat is that using this in conjunction with other CriticalSections + can create lots of interesting ways of producing a deadlock! In particular, if + your message thread calls stopThread() for a thread that uses these locks, + you'll get an (occasional) deadlock.. + + @see MessageManager, MessageManager::currentThreadHasLockedMessageManager +*/ +class JUCE_API MessageManagerLock +{ +public: + //============================================================================== + /** Tries to acquire a lock on the message manager. + + The constructor attempts to gain a lock on the message loop, and the lock will be + kept for the lifetime of this object. + + Optionally, you can pass a thread object here, and while waiting to obtain the lock, + this method will keep checking whether the thread has been given the + Thread::signalThreadShouldExit() signal. If this happens, then it will return + without gaining the lock. If you pass a thread, you must check whether the lock was + successful by calling lockWasGained(). If this is false, your thread is being told to + die, so you should take evasive action. + + If you pass nullptr for the thread object, it will wait indefinitely for the lock - be + careful when doing this, because it's very easy to deadlock if your message thread + attempts to call stopThread() on a thread just as that thread attempts to get the + message lock. + + If the calling thread already has the lock, nothing will be done, so it's safe and + quick to use these locks recursively. + + E.g. + @code + void run() + { + ... + + while (! threadShouldExit()) + { + MessageManagerLock mml (Thread::getCurrentThread()); + + if (! mml.lockWasGained()) + return; // another thread is trying to kill us! + + ..do some locked stuff here.. + } + + ..and now the MM is now unlocked.. + } + @endcode + + */ + MessageManagerLock (Thread* threadToCheckForExitSignal = nullptr); + + //============================================================================== + /** This has the same behaviour as the other constructor, but takes a ThreadPoolJob + instead of a thread. + + See the MessageManagerLock (Thread*) constructor for details on how this works. + */ + MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal); + + + //============================================================================== + /** Releases the current thread's lock on the message manager. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + ~MessageManagerLock() noexcept; + + //============================================================================== + /** Returns true if the lock was successfully acquired. + (See the constructor that takes a Thread for more info). + */ + bool lockWasGained() const noexcept { return locked; } + +private: + class BlockingMessage; + friend class ReferenceCountedObjectPtr; + ReferenceCountedObjectPtr blockingMessage; + bool locked; + + bool attemptLock (Thread*, ThreadPoolJob*); + + JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) +}; + + +#endif // JUCE_MESSAGEMANAGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MountedVolumeListChangeDetector.h b/JuceLibraryCode/modules/juce_events/messages/juce_MountedVolumeListChangeDetector.h new file mode 100644 index 0000000..fe62875 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MountedVolumeListChangeDetector.h @@ -0,0 +1,59 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MOUNTEDVOLUMELISTCHANGEDETECTOR_H_INCLUDED +#define JUCE_MOUNTEDVOLUMELISTCHANGEDETECTOR_H_INCLUDED + +#if JUCE_MAC || JUCE_WINDOWS || defined (DOXYGEN) + +//============================================================================== +/** + An instance of this class will provide callbacks when drives are + mounted or unmounted on the system. + + Just inherit from this class and implement the pure virtual method + to get the callbacks, there's no need to do anything else. + + @see File::findFileSystemRoots() +*/ +class JUCE_API MountedVolumeListChangeDetector +{ +public: + MountedVolumeListChangeDetector(); + virtual ~MountedVolumeListChangeDetector(); + + /** This method is called when a volume is mounted or unmounted. */ + virtual void mountedVolumeListChanged() = 0; + +private: + JUCE_PUBLIC_IN_DLL_BUILD (struct Pimpl) + friend struct ContainerDeletePolicy; + ScopedPointer pimpl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MountedVolumeListChangeDetector) +}; + +#endif + +#endif // JUCE_MOUNTEDVOLUMELISTCHANGEDETECTOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_NotificationType.h b/JuceLibraryCode/modules/juce_events/messages/juce_NotificationType.h new file mode 100644 index 0000000..c3ecccc --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/messages/juce_NotificationType.h @@ -0,0 +1,42 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_NOTIFICATIONTYPE_H_INCLUDED +#define JUCE_NOTIFICATIONTYPE_H_INCLUDED + +//============================================================================== +/** + These enums are used in various classes to indicate whether a notification + event should be sent out. +*/ +enum NotificationType +{ + dontSendNotification = 0, /**< No notification message should be sent. */ + sendNotification = 1, /**< Requests a notification message, either synchronous or not. */ + sendNotificationSync, /**< Requests a synchronous notification. */ + sendNotificationAsync, /**< Requests an asynchronous notification. */ +}; + + +#endif // JUCE_NOTIFICATIONTYPE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/native/juce_ScopedXLock.h b/JuceLibraryCode/modules/juce_events/native/juce_ScopedXLock.h new file mode 100644 index 0000000..d2136c7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_ScopedXLock.h @@ -0,0 +1,50 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_SCOPEDXLOCK_H_INCLUDED +#define JUCE_SCOPEDXLOCK_H_INCLUDED + + +//============================================================================== +#if JUCE_LINUX || DOXYGEN + +/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server + using RAII (Only available in Linux!). +*/ +class ScopedXLock +{ +public: + /** Creating a ScopedXLock object locks the X display. + This uses XLockDisplay() to grab the display that Juce is using. + */ + ScopedXLock(); + + /** Deleting a ScopedXLock object unlocks the X display. + This calls XUnlockDisplay() to release the lock. + */ + ~ScopedXLock(); +}; + +#endif +#endif // JUCE_SCOPEDXLOCK_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/native/juce_android_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_android_Messaging.cpp new file mode 100644 index 0000000..144a326 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_android_Messaging.cpp @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +void MessageManager::doPlatformSpecificInitialisation() {} +void MessageManager::doPlatformSpecificShutdown() {} + +//============================================================================== +bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPendingMessages) +{ + Logger::outputDebugString ("*** Modal loops are not possible in Android!! Exiting..."); + exit (1); + + return true; +} + +//============================================================================== +bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) +{ + message->incReferenceCount(); + android.activity.callVoidMethod (JuceAppActivity.postMessage, (jlong) (pointer_sized_uint) message); + return true; +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (JNIEnv* env, jobject activity, jlong value)) +{ + JUCE_TRY + { + MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; + message->messageCallback(); + message->decReferenceCount(); + } + JUCE_CATCH_EXCEPTION +} + +//============================================================================== +void MessageManager::broadcastMessage (const String&) +{ +} + +void MessageManager::runDispatchLoop() +{ +} + +void MessageManager::stopDispatchLoop() +{ + struct QuitCallback : public CallbackMessage + { + QuitCallback() {} + + void messageCallback() override + { + android.activity.callVoidMethod (JuceAppActivity.finish); + } + }; + + (new QuitCallback())->post(); + quitMessagePosted = true; +} diff --git a/JuceLibraryCode/modules/juce_events/native/juce_ios_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_ios_MessageManager.mm new file mode 100644 index 0000000..bdfed65 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_ios_MessageManager.mm @@ -0,0 +1,88 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +void MessageManager::runDispatchLoop() +{ + jassert (isThisTheMessageThread()); // must only be called by the message thread + runDispatchLoopUntil (-1); +} + +void MessageManager::stopDispatchLoop() +{ + [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; + exit (0); // iOS apps get no mercy.. +} + +bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) +{ + JUCE_AUTORELEASEPOOL + { + jassert (isThisTheMessageThread()); // must only be called by the message thread + + uint32 startTime = Time::getMillisecondCounter(); + NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; + + while (! quitMessagePosted) + { + JUCE_AUTORELEASEPOOL + { + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: endDate]; + + if (millisecondsToRunFor >= 0 + && Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor) + break; + } + } + + return ! quitMessagePosted; + } +} + +//============================================================================== +static ScopedPointer messageQueue; + +void MessageManager::doPlatformSpecificInitialisation() +{ + if (messageQueue == nullptr) + messageQueue = new MessageQueue(); +} + +void MessageManager::doPlatformSpecificShutdown() +{ + messageQueue = nullptr; +} + +bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) +{ + if (messageQueue != nullptr) + messageQueue->post (message); + + return true; +} + +void MessageManager::broadcastMessage (const String&) +{ + // N/A on current iOS +} diff --git a/JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp new file mode 100644 index 0000000..046c462 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp @@ -0,0 +1,398 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_DEBUG && ! defined (JUCE_DEBUG_XERRORS) + #define JUCE_DEBUG_XERRORS 1 +#endif + +Display* display = nullptr; +Window juce_messageWindowHandle = None; +XContext windowHandleXContext; // This is referenced from Windowing.cpp + +typedef bool (*WindowMessageReceiveCallback) (XEvent&); +WindowMessageReceiveCallback dispatchWindowMessage = nullptr; + +typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&); +SelectionRequestCallback handleSelectionRequest = nullptr; + +//============================================================================== +ScopedXLock::ScopedXLock() { XLockDisplay (display); } +ScopedXLock::~ScopedXLock() { XUnlockDisplay (display); } + +//============================================================================== +class InternalMessageQueue +{ +public: + InternalMessageQueue() + : bytesInSocket (0), + totalEventCount (0) + { + int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); + (void) ret; jassert (ret == 0); + } + + ~InternalMessageQueue() + { + close (fd[0]); + close (fd[1]); + + clearSingletonInstance(); + } + + //============================================================================== + void postMessage (MessageManager::MessageBase* const msg) + { + const int maxBytesInSocketQueue = 128; + + ScopedLock sl (lock); + queue.add (msg); + + if (bytesInSocket < maxBytesInSocketQueue) + { + ++bytesInSocket; + + ScopedUnlock ul (lock); + const unsigned char x = 0xff; + size_t bytesWritten = write (fd[0], &x, 1); + (void) bytesWritten; + } + } + + bool isEmpty() const + { + ScopedLock sl (lock); + return queue.size() == 0; + } + + bool dispatchNextEvent() + { + // This alternates between giving priority to XEvents or internal messages, + // to keep everything running smoothly.. + if ((++totalEventCount & 1) != 0) + return dispatchNextXEvent() || dispatchNextInternalMessage(); + + return dispatchNextInternalMessage() || dispatchNextXEvent(); + } + + // Wait for an event (either XEvent, or an internal Message) + bool sleepUntilEvent (const int timeoutMs) + { + if (! isEmpty()) + return true; + + if (display != 0) + { + ScopedXLock xlock; + if (XPending (display)) + return true; + } + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = timeoutMs * 1000; + int fd0 = getWaitHandle(); + int fdmax = fd0; + + fd_set readset; + FD_ZERO (&readset); + FD_SET (fd0, &readset); + + if (display != 0) + { + ScopedXLock xlock; + int fd1 = XConnectionNumber (display); + FD_SET (fd1, &readset); + fdmax = jmax (fd0, fd1); + } + + const int ret = select (fdmax + 1, &readset, 0, 0, &tv); + return (ret > 0); // ret <= 0 if error or timeout + } + + //============================================================================== + juce_DeclareSingleton_SingleThreaded_Minimal (InternalMessageQueue) + +private: + CriticalSection lock; + ReferenceCountedArray queue; + int fd[2]; + int bytesInSocket; + int totalEventCount; + + int getWaitHandle() const noexcept { return fd[1]; } + + static bool setNonBlocking (int handle) + { + int socketFlags = fcntl (handle, F_GETFL, 0); + if (socketFlags == -1) + return false; + + socketFlags |= O_NONBLOCK; + return fcntl (handle, F_SETFL, socketFlags) == 0; + } + + static bool dispatchNextXEvent() + { + if (display == 0) + return false; + + XEvent evt; + + { + ScopedXLock xlock; + if (! XPending (display)) + return false; + + XNextEvent (display, &evt); + } + + if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle + && handleSelectionRequest != nullptr) + handleSelectionRequest (evt.xselectionrequest); + else if (evt.xany.window != juce_messageWindowHandle && dispatchWindowMessage != nullptr) + dispatchWindowMessage (evt); + + return true; + } + + MessageManager::MessageBase::Ptr popNextMessage() + { + const ScopedLock sl (lock); + + if (bytesInSocket > 0) + { + --bytesInSocket; + + const ScopedUnlock ul (lock); + unsigned char x; + size_t numBytes = read (fd[1], &x, 1); + (void) numBytes; + } + + return queue.removeAndReturn (0); + } + + bool dispatchNextInternalMessage() + { + if (const MessageManager::MessageBase::Ptr msg = popNextMessage()) + { + JUCE_TRY + { + msg->messageCallback(); + return true; + } + JUCE_CATCH_EXCEPTION + } + + return false; + } +}; + +juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) + + +//============================================================================== +namespace LinuxErrorHandling +{ + static bool errorOccurred = false; + static bool keyboardBreakOccurred = false; + static XErrorHandler oldErrorHandler = (XErrorHandler) 0; + static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0; + + //============================================================================== + // Usually happens when client-server connection is broken + int ioErrorHandler (Display*) + { + DBG ("ERROR: connection to X server broken.. terminating."); + + if (JUCEApplicationBase::isStandaloneApp()) + MessageManager::getInstance()->stopDispatchLoop(); + + errorOccurred = true; + return 0; + } + + int errorHandler (Display* display, XErrorEvent* event) + { + #if JUCE_DEBUG_XERRORS + char errorStr[64] = { 0 }; + char requestStr[64] = { 0 }; + + XGetErrorText (display, event->error_code, errorStr, 64); + XGetErrorDatabaseText (display, "XRequest", String (event->request_code).toUTF8(), "Unknown", requestStr, 64); + DBG ("ERROR: X returned " << errorStr << " for operation " << requestStr); + #endif + + return 0; + } + + void installXErrorHandlers() + { + oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler); + oldErrorHandler = XSetErrorHandler (errorHandler); + } + + void removeXErrorHandlers() + { + if (JUCEApplicationBase::isStandaloneApp()) + { + XSetIOErrorHandler (oldIOErrorHandler); + oldIOErrorHandler = 0; + + XSetErrorHandler (oldErrorHandler); + oldErrorHandler = 0; + } + } + + //============================================================================== + void keyboardBreakSignalHandler (int sig) + { + if (sig == SIGINT) + keyboardBreakOccurred = true; + } + + void installKeyboardBreakHandler() + { + struct sigaction saction; + sigset_t maskSet; + sigemptyset (&maskSet); + saction.sa_handler = keyboardBreakSignalHandler; + saction.sa_mask = maskSet; + saction.sa_flags = 0; + sigaction (SIGINT, &saction, 0); + } +} + +//============================================================================== +void MessageManager::doPlatformSpecificInitialisation() +{ + if (JUCEApplicationBase::isStandaloneApp()) + { + // Initialise xlib for multiple thread support + static bool initThreadCalled = false; + + if (! initThreadCalled) + { + if (! XInitThreads()) + { + // This is fatal! Print error and closedown + Logger::outputDebugString ("Failed to initialise xlib thread support."); + Process::terminate(); + return; + } + + initThreadCalled = true; + } + + LinuxErrorHandling::installXErrorHandlers(); + LinuxErrorHandling::installKeyboardBreakHandler(); + } + + // Create the internal message queue + InternalMessageQueue::getInstance(); + + // Try to connect to a display + String displayName (getenv ("DISPLAY")); + if (displayName.isEmpty()) + displayName = ":0.0"; + + display = XOpenDisplay (displayName.toUTF8()); + + if (display != 0) // This is not fatal! we can run headless. + { + // Create a context to store user data associated with Windows we create + windowHandleXContext = XUniqueContext(); + + // We're only interested in client messages for this window, which are always sent + XSetWindowAttributes swa; + swa.event_mask = NoEventMask; + + // Create our message window (this will never be mapped) + const int screen = DefaultScreen (display); + juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen), + 0, 0, 1, 1, 0, 0, InputOnly, + DefaultVisual (display, screen), + CWEventMask, &swa); + } +} + +void MessageManager::doPlatformSpecificShutdown() +{ + InternalMessageQueue::deleteInstance(); + + if (display != 0 && ! LinuxErrorHandling::errorOccurred) + { + XDestroyWindow (display, juce_messageWindowHandle); + XCloseDisplay (display); + + juce_messageWindowHandle = 0; + display = nullptr; + + LinuxErrorHandling::removeXErrorHandlers(); + } +} + +bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) +{ + if (LinuxErrorHandling::errorOccurred) + return false; + + InternalMessageQueue::getInstanceWithoutCreating()->postMessage (message); + return true; +} + +void MessageManager::broadcastMessage (const String& /* value */) +{ + /* TODO */ +} + +// this function expects that it will NEVER be called simultaneously for two concurrent threads +bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) +{ + while (! LinuxErrorHandling::errorOccurred) + { + if (LinuxErrorHandling::keyboardBreakOccurred) + { + LinuxErrorHandling::errorOccurred = true; + + if (JUCEApplicationBase::isStandaloneApp()) + Process::terminate(); + + break; + } + + InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating(); + jassert (queue != nullptr); + + if (queue->dispatchNextEvent()) + return true; + + if (returnIfNoPendingMessages) + break; + + queue->sleepUntilEvent (2000); + } + + return false; +} diff --git a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm new file mode 100644 index 0000000..7b2068e --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm @@ -0,0 +1,418 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +typedef void (*AppFocusChangeCallback)(); +AppFocusChangeCallback appFocusChangeCallback = nullptr; + +typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); +CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; + +typedef void (*MenuTrackingChangedCallback)(bool); +MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; + +//============================================================================== +struct AppDelegate +{ +public: + AppDelegate() + { + static AppDelegateClass cls; + delegate = [cls.createInstance() init]; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + + [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) + name: NSMenuDidBeginTrackingNotification object: nil]; + [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) + name: NSMenuDidEndTrackingNotification object: nil]; + + if (JUCEApplicationBase::isStandaloneApp()) + { + [NSApp setDelegate: delegate]; + + [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate + selector: @selector (broadcastMessageCallback:) + name: getBroacastEventName() + object: nil]; + } + else + { + [center addObserver: delegate selector: @selector (applicationDidResignActive:) + name: NSApplicationDidResignActiveNotification object: NSApp]; + + [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) + name: NSApplicationDidBecomeActiveNotification object: NSApp]; + + [center addObserver: delegate selector: @selector (applicationWillUnhide:) + name: NSApplicationWillUnhideNotification object: NSApp]; + } + } + + ~AppDelegate() + { + [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; + [[NSNotificationCenter defaultCenter] removeObserver: delegate]; + + if (JUCEApplicationBase::isStandaloneApp()) + { + [NSApp setDelegate: nil]; + + [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate + name: getBroacastEventName() + object: nil]; + } + + [delegate release]; + } + + static NSString* getBroacastEventName() + { + return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); + } + + MessageQueue messageQueue; + id delegate; + +private: + //============================================================================== + struct AppDelegateClass : public ObjCClass + { + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") + { + addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); + addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); + addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); + addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); + addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); + addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); + addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); + addMethod (@selector (dummyMethod), dummyMethod, "v@:"); + + registerClass(); + } + + private: + static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) + { + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + { + app->systemRequestedQuit(); + + if (! MessageManager::getInstance()->hasStopMessageBeenSent()) + return NSTerminateCancel; + } + + return NSTerminateNow; + } + + static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) + { + JUCEApplicationBase::appWillTerminateByForce(); + } + + static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) + { + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + { + app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); + return YES; + } + + return NO; + } + + static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) + { + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + { + StringArray files; + + for (NSString* f in filenames) + files.add (quotedIfContainsSpaces (f)); + + if (files.size() > 0) + app->anotherInstanceStarted (files.joinIntoString (" ")); + } + } + + static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } + + static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) + { + NSDictionary* dict = (NSDictionary*) [n userInfo]; + const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")])); + MessageManager::getInstance()->deliverBroadcastMessage (messageString); + } + + static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (true); + } + + static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (false); + } + + static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) + + private: + static void focusChanged() + { + if (appFocusChangeCallback != nullptr) + (*appFocusChangeCallback)(); + } + + static String quotedIfContainsSpaces (NSString* file) + { + String s (nsStringToJuce (file)); + if (s.containsChar (' ')) + s = s.quoted ('"'); + + return s; + } + }; +}; + +//============================================================================== +void MessageManager::runDispatchLoop() +{ + if (! quitMessagePosted) // check that the quit message wasn't already posted.. + { + JUCE_AUTORELEASEPOOL + { + // must only be called by the message thread! + jassert (isThisTheMessageThread()); + + #if JUCE_PROJUCER_LIVE_BUILD + runDispatchLoopUntil (std::numeric_limits::max()); + #else + #if JUCE_CATCH_UNHANDLED_EXCEPTIONS + @try + { + [NSApp run]; + } + @catch (NSException* e) + { + // An AppKit exception will kill the app, but at least this provides a chance to log it., + std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]); + JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__); + } + @finally + { + } + #else + [NSApp run]; + #endif + #endif + } + } +} + +static void shutdownNSApp() +{ + [NSApp stop: nil]; + [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated) + [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; +} + +void MessageManager::stopDispatchLoop() +{ + quitMessagePosted = true; + + #if ! JUCE_PROJUCER_LIVE_BUILD + if (isThisTheMessageThread()) + { + shutdownNSApp(); + } + else + { + struct QuitCallback : public CallbackMessage + { + QuitCallback() {} + void messageCallback() override { shutdownNSApp(); } + }; + + (new QuitCallback())->post(); + } + #endif +} + +#if JUCE_MODAL_LOOPS_PERMITTED +bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) +{ + jassert (millisecondsToRunFor >= 0); + jassert (isThisTheMessageThread()); // must only be called by the message thread + + uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor; + + while (! quitMessagePosted) + { + JUCE_AUTORELEASEPOOL + { + CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true); + + NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask + untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + + if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e))) + [NSApp sendEvent: e]; + + if (Time::getMillisecondCounter() >= endTime) + break; + } + } + + return ! quitMessagePosted; +} +#endif + +//============================================================================== +void initialiseNSApplication(); +void initialiseNSApplication() +{ + JUCE_AUTORELEASEPOOL + { + [NSApplication sharedApplication]; + } +} + +static AppDelegate* appDelegate = nullptr; + +void MessageManager::doPlatformSpecificInitialisation() +{ + if (appDelegate == nil) + appDelegate = new AppDelegate(); + + #if ! (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + // This launches a dummy thread, which forces Cocoa to initialise NSThreads correctly (needed prior to 10.5) + if (! [NSThread isMultiThreaded]) + [NSThread detachNewThreadSelector: @selector (dummyMethod) + toTarget: appDelegate->delegate + withObject: nil]; + #endif +} + +void MessageManager::doPlatformSpecificShutdown() +{ + delete appDelegate; + appDelegate = nullptr; +} + +bool MessageManager::postMessageToSystemQueue (MessageBase* message) +{ + jassert (appDelegate != nil); + appDelegate->messageQueue.post (message); + return true; +} + +void MessageManager::broadcastMessage (const String& message) +{ + NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message) + forKey: nsStringLiteral ("message")]; + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroacastEventName() + object: nil + userInfo: info]; +} + +// Special function used by some plugin classes to re-post carbon events +void repostCurrentNSEvent(); +void repostCurrentNSEvent() +{ + struct EventReposter : public CallbackMessage + { + EventReposter() : e ([[NSApp currentEvent] retain]) {} + ~EventReposter() { [e release]; } + + void messageCallback() override + { + [NSApp postEvent: e atStart: YES]; + } + + NSEvent* e; + }; + + (new EventReposter())->post(); +} + + +//============================================================================== +#if JUCE_MAC +struct MountedVolumeListChangeDetector::Pimpl +{ + Pimpl (MountedVolumeListChangeDetector& d) : owner (d) + { + static ObserverClass cls; + delegate = [cls.createInstance() init]; + ObserverClass::setOwner (delegate, this); + + NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter]; + + [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil]; + [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil]; + } + + ~Pimpl() + { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate]; + [delegate release]; + } + +private: + MountedVolumeListChangeDetector& owner; + id delegate; + + struct ObserverClass : public ObjCClass + { + ObserverClass() : ObjCClass ("JUCEDriveObserver_") + { + addIvar ("owner"); + addMethod (@selector (changed:), changed, "v@:@"); + addProtocol (@protocol (NSTextInput)); + registerClass(); + } + + static Pimpl* getOwner (id self) { return getIvar (self, "owner"); } + static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } + + static void changed (id self, SEL, NSNotification*) + { + getOwner (self)->owner.mountedVolumeListChanged(); + } + }; +}; + +MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } +MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} +#endif diff --git a/JuceLibraryCode/modules/juce_events/native/juce_osx_MessageQueue.h b/JuceLibraryCode/modules/juce_events/native/juce_osx_MessageQueue.h new file mode 100644 index 0000000..6ffbefe --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_osx_MessageQueue.h @@ -0,0 +1,103 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_OSX_MESSAGEQUEUE_H_INCLUDED +#define JUCE_OSX_MESSAGEQUEUE_H_INCLUDED + +//============================================================================== +/* An internal message pump class used in OSX and iOS. */ +class MessageQueue +{ +public: + MessageQueue() + { + #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4 && ! JUCE_IOS + runLoop = CFRunLoopGetMain(); + #else + runLoop = CFRunLoopGetCurrent(); + #endif + + CFRunLoopSourceContext sourceContext; + zerostruct (sourceContext); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) + sourceContext.info = this; + sourceContext.perform = runLoopSourceCallback; + runLoopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 1, &sourceContext); + CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes); + } + + ~MessageQueue() + { + CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes); + CFRunLoopSourceInvalidate (runLoopSource); + CFRelease (runLoopSource); + } + + void post (MessageManager::MessageBase* const message) + { + messages.add (message); + CFRunLoopSourceSignal (runLoopSource); + CFRunLoopWakeUp (runLoop); + } + +private: + ReferenceCountedArray messages; + CFRunLoopRef runLoop; + CFRunLoopSourceRef runLoopSource; + + bool deliverNextMessage() + { + const MessageManager::MessageBase::Ptr nextMessage (messages.removeAndReturn (0)); + + if (nextMessage == nullptr) + return false; + + JUCE_AUTORELEASEPOOL + { + JUCE_TRY + { + nextMessage->messageCallback(); + } + JUCE_CATCH_EXCEPTION + } + + return true; + } + + void runLoopCallback() + { + for (int i = 4; --i >= 0;) + if (! deliverNextMessage()) + return; + + CFRunLoopSourceSignal (runLoopSource); + CFRunLoopWakeUp (runLoop); + } + + static void runLoopSourceCallback (void* info) + { + static_cast (info)->runLoopCallback(); + } +}; + +#endif // JUCE_OSX_MESSAGEQUEUE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h b/JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h new file mode 100644 index 0000000..e0df59a --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h @@ -0,0 +1,137 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_WIN32_HIDDENMESSAGEWINDOW_H_INCLUDED +#define JUCE_WIN32_HIDDENMESSAGEWINDOW_H_INCLUDED + +//============================================================================== +class HiddenMessageWindow +{ +public: + HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc) + { + String className ("JUCE_"); + className << String::toHexString (Time::getHighResolutionTicks()); + + HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle(); + + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof (wc); + wc.lpfnWndProc = wndProc; + wc.cbWndExtra = 4; + wc.hInstance = moduleHandle; + wc.lpszClassName = className.toWideCharPointer(); + + atom = RegisterClassEx (&wc); + jassert (atom != 0); + + hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName, + 0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); + jassert (hwnd != 0); + } + + ~HiddenMessageWindow() + { + DestroyWindow (hwnd); + UnregisterClass (getClassNameFromAtom(), 0); + } + + inline HWND getHWND() const noexcept { return hwnd; } + +private: + ATOM atom; + HWND hwnd; + + LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) MAKELONG (atom, 0); } +}; + +//============================================================================== +class JuceWindowIdentifier +{ +public: + static bool isJUCEWindow (HWND hwnd) noexcept + { + return GetWindowLongPtr (hwnd, GWLP_USERDATA) == getImprobableWindowNumber(); + } + + static void setAsJUCEWindow (HWND hwnd, bool isJuceWindow) noexcept + { + SetWindowLongPtr (hwnd, GWLP_USERDATA, isJuceWindow ? getImprobableWindowNumber() : 0); + } + +private: + static LONG_PTR getImprobableWindowNumber() noexcept + { + static LONG_PTR number = (LONG_PTR) Random::getSystemRandom().nextInt64(); + return number; + } +}; + +//============================================================================== +class DeviceChangeDetector : private Timer +{ +public: + DeviceChangeDetector (const wchar_t* const name) + : messageWindow (name, (WNDPROC) deviceChangeEventCallback) + { + SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this); + } + + virtual ~DeviceChangeDetector() {} + + virtual void systemDeviceChanged() = 0; + + void triggerAsyncDeviceChangeCallback() + { + // We'll pause before sending a message, because on device removal, the OS hasn't always updated + // its device lists correctly at this point. This also helps avoid repeated callbacks. + startTimer (500); + } + +private: + HiddenMessageWindow messageWindow; + + static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, + const WPARAM wParam, const LPARAM lParam) + { + if (message == WM_DEVICECHANGE + && (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/ + || wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/ + || wParam == 0x0007 /*DBT_DEVNODES_CHANGED*/)) + { + ((DeviceChangeDetector*) GetWindowLongPtr (h, GWLP_USERDATA)) + ->triggerAsyncDeviceChangeCallback(); + } + + return DefWindowProc (h, message, wParam, lParam); + } + + void timerCallback() override + { + stopTimer(); + systemDeviceChanged(); + } +}; + +#endif // JUCE_WIN32_HIDDENMESSAGEWINDOW_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp new file mode 100644 index 0000000..853f54d --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp @@ -0,0 +1,217 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +extern HWND juce_messageWindowHandle; + +typedef bool (*CheckEventBlockedByModalComps) (const MSG&); +CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; + +//============================================================================== +namespace WindowsMessageHelpers +{ + const unsigned int specialId = WM_APP + 0x4400; + const unsigned int broadcastId = WM_APP + 0x4403; + + const TCHAR messageWindowName[] = _T("JUCEWindow"); + ScopedPointer messageWindow; + + void dispatchMessageFromLParam (LPARAM lParam) + { + MessageManager::MessageBase* const message = reinterpret_cast (lParam); + + JUCE_TRY + { + message->messageCallback(); + } + JUCE_CATCH_EXCEPTION + + message->decReferenceCount(); + } + + //============================================================================== + LRESULT CALLBACK messageWndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) noexcept + { + if (h == juce_messageWindowHandle) + { + if (message == specialId) + { + // (These are trapped early in our dispatch loop, but must also be checked + // here in case some 3rd-party code is running the dispatch loop). + dispatchMessageFromLParam (lParam); + return 0; + } + else if (message == broadcastId) + { + const ScopedPointer messageString ((String*) lParam); + MessageManager::getInstance()->deliverBroadcastMessage (*messageString); + return 0; + } + else if (message == WM_COPYDATA) + { + const COPYDATASTRUCT* const data = reinterpret_cast (lParam); + + if (data->dwData == broadcastId) + { + const String messageString (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData), + data->cbData / sizeof (CharPointer_UTF32::CharType)); + + PostMessage (juce_messageWindowHandle, broadcastId, 0, (LPARAM) new String (messageString)); + return 0; + } + } + } + + return DefWindowProc (h, message, wParam, lParam); + } + + BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam) + { + if (hwnd != juce_messageWindowHandle) + reinterpret_cast *> (lParam)->add (hwnd); + + return TRUE; + } +} + +//============================================================================== +bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPendingMessages) +{ + using namespace WindowsMessageHelpers; + MSG m; + + if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE)) + return false; + + if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) + { + if (m.message == specialId && m.hwnd == juce_messageWindowHandle) + { + dispatchMessageFromLParam (m.lParam); + } + else if (m.message == WM_QUIT) + { + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) + app->systemRequestedQuit(); + } + else if (isEventBlockedByModalComps == nullptr || ! isEventBlockedByModalComps (m)) + { + if ((m.message == WM_LBUTTONDOWN || m.message == WM_RBUTTONDOWN) + && ! JuceWindowIdentifier::isJUCEWindow (m.hwnd)) + { + // if it's someone else's window being clicked on, and the focus is + // currently on a juce window, pass the kb focus over.. + HWND currentFocus = GetFocus(); + + if (currentFocus == 0 || JuceWindowIdentifier::isJUCEWindow (currentFocus)) + SetFocus (m.hwnd); + } + + TranslateMessage (&m); + DispatchMessage (&m); + } + } + + return true; +} + +bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) +{ + message->incReferenceCount(); + return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::specialId, 0, (LPARAM) message) != 0; +} + +void MessageManager::broadcastMessage (const String& value) +{ + Array windows; + EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows); + + const String localCopy (value); + + COPYDATASTRUCT data; + data.dwData = WindowsMessageHelpers::broadcastId; + data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType); + data.lpData = (void*) localCopy.toUTF32().getAddress(); + + for (int i = windows.size(); --i >= 0;) + { + HWND hwnd = windows.getUnchecked(i); + + TCHAR windowName [64]; // no need to read longer strings than this + GetWindowText (hwnd, windowName, 64); + windowName [63] = 0; + + if (String (windowName) == WindowsMessageHelpers::messageWindowName) + { + DWORD_PTR result; + SendMessageTimeout (hwnd, WM_COPYDATA, + (WPARAM) juce_messageWindowHandle, + (LPARAM) &data, + SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); + } + } +} + +//============================================================================== +void MessageManager::doPlatformSpecificInitialisation() +{ + OleInitialize (0); + + using namespace WindowsMessageHelpers; + messageWindow = new HiddenMessageWindow (messageWindowName, (WNDPROC) messageWndProc); + juce_messageWindowHandle = messageWindow->getHWND(); +} + +void MessageManager::doPlatformSpecificShutdown() +{ + WindowsMessageHelpers::messageWindow = nullptr; + + OleUninitialize(); +} + +//============================================================================== +struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector +{ + Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d) + { + File::findFileSystemRoots (lastVolumeList); + } + + void systemDeviceChanged() override + { + Array newList; + File::findFileSystemRoots (newList); + + if (lastVolumeList != newList) + { + lastVolumeList = newList; + owner.mountedVolumeListChanged(); + } + } + + MountedVolumeListChangeDetector& owner; + Array lastVolumeList; +}; + +MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } +MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} diff --git a/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.cpp b/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.cpp new file mode 100644 index 0000000..4885e7b --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.cpp @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct MultiTimerCallback : public Timer +{ + MultiTimerCallback (const int tid, MultiTimer& mt) noexcept + : owner (mt), timerID (tid) + { + } + + void timerCallback() override + { + owner.timerCallback (timerID); + } + + MultiTimer& owner; + const int timerID; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTimerCallback) +}; + +//============================================================================== +MultiTimer::MultiTimer() noexcept {} +MultiTimer::MultiTimer (const MultiTimer&) noexcept {} + +MultiTimer::~MultiTimer() +{ + const SpinLock::ScopedLockType sl (timerListLock); + timers.clear(); +} + +//============================================================================== +Timer* MultiTimer::getCallback (int timerID) const noexcept +{ + for (int i = timers.size(); --i >= 0;) + { + MultiTimerCallback* const t = static_cast (timers.getUnchecked(i)); + + if (t->timerID == timerID) + return t; + } + + return nullptr; +} + +void MultiTimer::startTimer (const int timerID, const int intervalInMilliseconds) noexcept +{ + const SpinLock::ScopedLockType sl (timerListLock); + + Timer* timer = getCallback (timerID); + + if (timer == nullptr) + timers.add (timer = new MultiTimerCallback (timerID, *this)); + + timer->startTimer (intervalInMilliseconds); +} + +void MultiTimer::stopTimer (const int timerID) noexcept +{ + const SpinLock::ScopedLockType sl (timerListLock); + + if (Timer* const t = getCallback (timerID)) + t->stopTimer(); +} + +bool MultiTimer::isTimerRunning (const int timerID) const noexcept +{ + const SpinLock::ScopedLockType sl (timerListLock); + + if (Timer* const t = getCallback (timerID)) + return t->isTimerRunning(); + + return false; +} + +int MultiTimer::getTimerInterval (const int timerID) const noexcept +{ + const SpinLock::ScopedLockType sl (timerListLock); + + if (Timer* const t = getCallback (timerID)) + return t->getTimerInterval(); + + return 0; +} diff --git a/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.h b/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.h new file mode 100644 index 0000000..c7fe0e4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/timers/juce_MultiTimer.h @@ -0,0 +1,126 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MULTITIMER_H_INCLUDED +#define JUCE_MULTITIMER_H_INCLUDED + + +//============================================================================== +/** + A type of timer class that can run multiple timers with different frequencies, + all of which share a single callback. + + This class is very similar to the Timer class, but allows you run multiple + separate timers, where each one has a unique ID number. The methods in this + class are exactly equivalent to those in Timer, but with the addition of + this ID number. + + To use it, you need to create a subclass of MultiTimer, implementing the + timerCallback() method. Then you can start timers with startTimer(), and + each time the callback is triggered, it passes in the ID of the timer that + caused it. + + @see Timer +*/ +class JUCE_API MultiTimer +{ +protected: + //============================================================================== + /** Creates a MultiTimer. + + When created, no timers are running, so use startTimer() to start things off. + */ + MultiTimer() noexcept; + + /** Creates a copy of another timer. + + Note that this timer will not contain any running timers, even if the one you're + copying from was running. + */ + MultiTimer (const MultiTimer&) noexcept; + +public: + //============================================================================== + /** Destructor. */ + virtual ~MultiTimer(); + + //============================================================================== + /** The user-defined callback routine that actually gets called by each of the + timers that are running. + + It's perfectly ok to call startTimer() or stopTimer() from within this + callback to change the subsequent intervals. + */ + virtual void timerCallback (int timerID) = 0; + + //============================================================================== + /** Starts a timer and sets the length of interval required. + + If the timer is already started, this will reset it, so the + time between calling this method and the next timer callback + will not be less than the interval length passed in. + + @param timerID a unique Id number that identifies the timer to + start. This is the id that will be passed back + to the timerCallback() method when this timer is + triggered + @param intervalInMilliseconds the interval to use (any values less than 1 will be + rounded up to 1) + */ + void startTimer (int timerID, int intervalInMilliseconds) noexcept; + + /** Stops a timer. + + If a timer has been started with the given ID number, it will be cancelled. + No more callbacks will be made for the specified timer after this method returns. + + If this is called from a different thread, any callbacks that may + be currently executing may be allowed to finish before the method + returns. + */ + void stopTimer (int timerID) noexcept; + + //============================================================================== + /** Checks whether a timer has been started for a specified ID. + @returns true if a timer with the given ID is running. + */ + bool isTimerRunning (int timerID) const noexcept; + + /** Returns the interval for a specified timer ID. + @returns the timer's interval in milliseconds if it's running, or 0 if no + timer was running for the ID number specified. + */ + int getTimerInterval (int timerID) const noexcept; + + + //============================================================================== +private: + SpinLock timerListLock; + OwnedArray timers; + + Timer* getCallback (int) const noexcept; + MultiTimer& operator= (const MultiTimer&); +}; + +#endif // JUCE_MULTITIMER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp new file mode 100644 index 0000000..f82abca --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp @@ -0,0 +1,358 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class Timer::TimerThread : private Thread, + private DeletedAtShutdown, + private AsyncUpdater +{ +public: + typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..) + + TimerThread() + : Thread ("Juce Timer"), + firstTimer (nullptr), + callbackNeeded (0) + { + triggerAsyncUpdate(); + } + + ~TimerThread() noexcept + { + stopThread (4000); + + jassert (instance == this || instance == nullptr); + if (instance == this) + instance = nullptr; + } + + void run() override + { + uint32 lastTime = Time::getMillisecondCounter(); + MessageManager::MessageBase::Ptr messageToSend (new CallTimersMessage()); + + while (! threadShouldExit()) + { + const uint32 now = Time::getMillisecondCounter(); + + if (now == lastTime) + { + wait (1); + continue; + } + + const int elapsed = (int) (now >= lastTime ? (now - lastTime) + : (std::numeric_limits::max() - (lastTime - now))); + lastTime = now; + + const int timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed); + + if (timeUntilFirstTimer <= 0) + { + /* If we managed to set the atomic boolean to true then send a message, this is needed + as a memory barrier so the message won't be sent before callbackNeeded is set to true, + but if it fails it means the message-thread changed the value from under us so at least + some processing is happenening and we can just loop around and try again + */ + if (callbackNeeded.compareAndSetBool (1, 0)) + { + messageToSend->post(); + + /* Sometimes our message can get discarded by the OS (e.g. when running as an RTAS + when the app has a modal loop), so this is how long to wait before assuming the + message has been lost and trying again. + */ + const uint32 messageDeliveryTimeout = now + 300; + + while (callbackNeeded.get() != 0) + { + wait (4); + + if (threadShouldExit()) + return; + + if (Time::getMillisecondCounter() > messageDeliveryTimeout) + { + messageToSend->post(); + break; + } + } + } + } + else + { + // don't wait for too long because running this loop also helps keep the + // Time::getApproximateMillisecondTimer value stay up-to-date + wait (jlimit (1, 50, timeUntilFirstTimer)); + } + } + } + + void callTimers() + { + const LockType::ScopedLockType sl (lock); + + while (firstTimer != nullptr && firstTimer->countdownMs <= 0) + { + Timer* const t = firstTimer; + t->countdownMs = t->periodMs; + + removeTimer (t); + addTimer (t); + + const LockType::ScopedUnlockType ul (lock); + + JUCE_TRY + { + t->timerCallback(); + } + JUCE_CATCH_EXCEPTION + } + + /* This is needed as a memory barrier to make sure all processing of current timers is done + before the boolean is set. This set should never fail since if it was false in the first place, + we wouldn't get a message (so it can't be changed from false to true from under us), and if we + get a message then the value is true and the other thread can only set it to true again and + we will get another callback to set it to false. + */ + callbackNeeded.set (0); + } + + void callTimersSynchronously() + { + if (! isThreadRunning()) + { + // (This is relied on by some plugins in cases where the MM has + // had to restart and the async callback never started) + cancelPendingUpdate(); + triggerAsyncUpdate(); + } + + callTimers(); + } + + static inline void add (Timer* const tim) noexcept + { + if (instance == nullptr) + instance = new TimerThread(); + + instance->addTimer (tim); + } + + static inline void remove (Timer* const tim) noexcept + { + if (instance != nullptr) + instance->removeTimer (tim); + } + + static inline void resetCounter (Timer* const tim, const int newCounter) noexcept + { + if (instance != nullptr) + { + tim->countdownMs = newCounter; + tim->periodMs = newCounter; + + if ((tim->next != nullptr && tim->next->countdownMs < tim->countdownMs) + || (tim->previous != nullptr && tim->previous->countdownMs > tim->countdownMs)) + { + instance->removeTimer (tim); + instance->addTimer (tim); + } + } + } + + static TimerThread* instance; + static LockType lock; + +private: + Timer* volatile firstTimer; + Atomic callbackNeeded; + + struct CallTimersMessage : public MessageManager::MessageBase + { + CallTimersMessage() {} + + void messageCallback() override + { + if (instance != nullptr) + instance->callTimers(); + } + }; + + //============================================================================== + void addTimer (Timer* const t) noexcept + { + #if JUCE_DEBUG + // trying to add a timer that's already here - shouldn't get to this point, + // so if you get this assertion, let me know! + jassert (! timerExists (t)); + #endif + + Timer* i = firstTimer; + + if (i == nullptr || i->countdownMs > t->countdownMs) + { + t->next = firstTimer; + firstTimer = t; + } + else + { + while (i->next != nullptr && i->next->countdownMs <= t->countdownMs) + i = i->next; + + jassert (i != nullptr); + + t->next = i->next; + t->previous = i; + i->next = t; + } + + if (t->next != nullptr) + t->next->previous = t; + + jassert ((t->next == nullptr || t->next->countdownMs >= t->countdownMs) + && (t->previous == nullptr || t->previous->countdownMs <= t->countdownMs)); + + notify(); + } + + void removeTimer (Timer* const t) noexcept + { + #if JUCE_DEBUG + // trying to remove a timer that's not here - shouldn't get to this point, + // so if you get this assertion, let me know! + jassert (timerExists (t)); + #endif + + if (t->previous != nullptr) + { + jassert (firstTimer != t); + t->previous->next = t->next; + } + else + { + jassert (firstTimer == t); + firstTimer = t->next; + } + + if (t->next != nullptr) + t->next->previous = t->previous; + + t->next = nullptr; + t->previous = nullptr; + } + + int getTimeUntilFirstTimer (const int numMillisecsElapsed) const + { + const LockType::ScopedLockType sl (lock); + + for (Timer* t = firstTimer; t != nullptr; t = t->next) + t->countdownMs -= numMillisecsElapsed; + + return firstTimer != nullptr ? firstTimer->countdownMs : 1000; + } + + void handleAsyncUpdate() override + { + startThread (7); + } + + #if JUCE_DEBUG + bool timerExists (Timer* const t) const noexcept + { + for (Timer* tt = firstTimer; tt != nullptr; tt = tt->next) + if (tt == t) + return true; + + return false; + } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) +}; + +Timer::TimerThread* Timer::TimerThread::instance = nullptr; +Timer::TimerThread::LockType Timer::TimerThread::lock; + +//============================================================================== +Timer::Timer() noexcept + : countdownMs (0), + periodMs (0), + previous (nullptr), + next (nullptr) +{ +} + +Timer::Timer (const Timer&) noexcept + : countdownMs (0), + periodMs (0), + previous (nullptr), + next (nullptr) +{ +} + +Timer::~Timer() +{ + stopTimer(); +} + +void Timer::startTimer (const int interval) noexcept +{ + const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); + + if (periodMs == 0) + { + countdownMs = interval; + periodMs = jmax (1, interval); + TimerThread::add (this); + } + else + { + TimerThread::resetCounter (this, interval); + } +} + +void Timer::startTimerHz (int timerFrequencyHz) noexcept +{ + if (timerFrequencyHz > 0) + startTimer (1000 / timerFrequencyHz); + else + stopTimer(); +} + +void Timer::stopTimer() noexcept +{ + const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); + + if (periodMs > 0) + { + TimerThread::remove (this); + periodMs = 0; + } +} + +void JUCE_CALLTYPE Timer::callPendingTimersSynchronously() +{ + if (TimerThread::instance != nullptr) + TimerThread::instance->callTimersSynchronously(); +} diff --git a/JuceLibraryCode/modules/juce_events/timers/juce_Timer.h b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.h new file mode 100644 index 0000000..57b0ce8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.h @@ -0,0 +1,135 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TIMER_H_INCLUDED +#define JUCE_TIMER_H_INCLUDED + + +//============================================================================== +/** + Makes repeated callbacks to a virtual method at a specified time interval. + + A Timer's timerCallback() method will be repeatedly called at a given + interval. When you create a Timer object, it will do nothing until the + startTimer() method is called, which will cause the message thread to + start making callbacks at the specified interval, until stopTimer() is called + or the object is deleted. + + The time interval isn't guaranteed to be precise to any more than maybe + 10-20ms, and the intervals may end up being much longer than requested if the + system is busy. Because the callbacks are made by the main message thread, + anything that blocks the message queue for a period of time will also prevent + any timers from running until it can carry on. + + If you need to have a single callback that is shared by multiple timers with + different frequencies, then the MultiTimer class allows you to do that - its + structure is very similar to the Timer class, but contains multiple timers + internally, each one identified by an ID number. + + @see HighResolutionTimer, MultiTimer +*/ +class JUCE_API Timer +{ +protected: + //============================================================================== + /** Creates a Timer. + + When created, the timer is stopped, so use startTimer() to get it going. + */ + Timer() noexcept; + + /** Creates a copy of another timer. + + Note that this timer won't be started, even if the one you're copying + is running. + */ + Timer (const Timer& other) noexcept; + +public: + //============================================================================== + /** Destructor. */ + virtual ~Timer(); + + //============================================================================== + /** The user-defined callback routine that actually gets called periodically. + + It's perfectly ok to call startTimer() or stopTimer() from within this + callback to change the subsequent intervals. + */ + virtual void timerCallback() = 0; + + //============================================================================== + /** Starts the timer and sets the length of interval required. + + If the timer is already started, this will reset it, so the + time between calling this method and the next timer callback + will not be less than the interval length passed in. + + @param intervalInMilliseconds the interval to use (any values less than 1 will be + rounded up to 1) + */ + void startTimer (int intervalInMilliseconds) noexcept; + + /** Starts the timer with an interval specified in Hertz. + This is effectively the same as calling startTimer (1000 / timerFrequencyHz). + */ + void startTimerHz (int timerFrequencyHz) noexcept; + + /** Stops the timer. + + No more callbacks will be made after this method returns. + + If this is called from a different thread, any callbacks that may + be currently executing may be allowed to finish before the method + returns. + */ + void stopTimer() noexcept; + + //============================================================================== + /** Returns true if the timer is currently running. */ + bool isTimerRunning() const noexcept { return periodMs > 0; } + + /** Returns the timer's interval. + @returns the timer's interval in milliseconds if it's running, or 0 if it's not. + */ + int getTimerInterval() const noexcept { return periodMs; } + + + //============================================================================== + /** For internal use only: invokes any timers that need callbacks. + Don't call this unless you really know what you're doing! + */ + static void JUCE_CALLTYPE callPendingTimersSynchronously(); + +private: + class TimerThread; + friend class TimerThread; + int countdownMs, periodMs; + Timer* previous; + Timer* next; + + Timer& operator= (const Timer&); +}; + +#endif // JUCE_TIMER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp new file mode 100644 index 0000000..aaf9468 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp @@ -0,0 +1,444 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +namespace ColourHelpers +{ + static uint8 floatToUInt8 (const float n) noexcept + { + return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : static_cast (n * 255.996f)); + } + + //============================================================================== + struct HSB + { + HSB (Colour col) noexcept + { + const int r = col.getRed(); + const int g = col.getGreen(); + const int b = col.getBlue(); + + const int hi = jmax (r, g, b); + const int lo = jmin (r, g, b); + + if (hi != 0) + { + saturation = (hi - lo) / (float) hi; + + if (saturation > 0) + { + const float invDiff = 1.0f / (hi - lo); + + const float red = (hi - r) * invDiff; + const float green = (hi - g) * invDiff; + const float blue = (hi - b) * invDiff; + + if (r == hi) + hue = blue - green; + else if (g == hi) + hue = 2.0f + red - blue; + else + hue = 4.0f + green - red; + + hue *= 1.0f / 6.0f; + + if (hue < 0) + ++hue; + } + else + { + hue = 0; + } + } + else + { + saturation = hue = 0; + } + + brightness = hi / 255.0f; + } + + Colour toColour (Colour original) const noexcept + { + return Colour (hue, saturation, brightness, original.getAlpha()); + } + + static PixelARGB toRGB (float h, float s, float v, const uint8 alpha) noexcept + { + v = jlimit (0.0f, 255.0f, v * 255.0f); + const uint8 intV = (uint8) roundToInt (v); + + if (s <= 0) + return PixelARGB (alpha, intV, intV, intV); + + s = jmin (1.0f, s); + h = (h - std::floor (h)) * 6.0f + 0.00001f; // need a small adjustment to compensate for rounding errors + const float f = h - std::floor (h); + const uint8 x = (uint8) roundToInt (v * (1.0f - s)); + + if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x); + if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x); + if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f))))); + if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV); + if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV); + return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f))); + } + + float hue, saturation, brightness; + }; + + //============================================================================== + struct YIQ + { + YIQ (Colour c) noexcept + { + const float r = c.getFloatRed(); + const float g = c.getFloatGreen(); + const float b = c.getFloatBlue(); + + y = 0.2999f * r + 0.5870f * g + 0.1140f * b; + i = 0.5957f * r - 0.2744f * g - 0.3212f * b; + q = 0.2114f * r - 0.5225f * g - 0.3113f * b; + alpha = c.getFloatAlpha(); + } + + Colour toColour() const noexcept + { + return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q, + y - 0.2721f * i - 0.6474f * q, + y - 1.1070f * i + 1.7046f * q, + alpha); + } + + float y, i, q, alpha; + }; +} + +//============================================================================== +Colour::Colour() noexcept + : argb (0) +{ +} + +Colour::Colour (const Colour& other) noexcept + : argb (other.argb) +{ +} + +Colour& Colour::operator= (const Colour& other) noexcept +{ + argb = other.argb; + return *this; +} + +bool Colour::operator== (const Colour& other) const noexcept { return argb.getARGB() == other.argb.getARGB(); } +bool Colour::operator!= (const Colour& other) const noexcept { return argb.getARGB() != other.argb.getARGB(); } + +//============================================================================== +Colour::Colour (const uint32 col) noexcept : argb (col) +{ +} + +Colour::Colour (const uint8 red, const uint8 green, const uint8 blue) noexcept +{ + argb.setARGB (0xff, red, green, blue); +} + +Colour Colour::fromRGB (const uint8 red, const uint8 green, const uint8 blue) noexcept +{ + return Colour (red, green, blue); +} + +Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept +{ + argb.setARGB (alpha, red, green, blue); +} + +Colour Colour::fromRGBA (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept +{ + return Colour (red, green, blue, alpha); +} + +Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const float alpha) noexcept +{ + argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue); +} + +Colour Colour::fromFloatRGBA (const float red, const float green, const float blue, const float alpha) noexcept +{ + return Colour (ColourHelpers::floatToUInt8 (red), + ColourHelpers::floatToUInt8 (green), + ColourHelpers::floatToUInt8 (blue), alpha); +} + +Colour::Colour (const float hue, const float saturation, const float brightness, const float alpha) noexcept + : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha))) +{ +} + +Colour Colour::fromHSV (const float hue, const float saturation, const float brightness, const float alpha) noexcept +{ + return Colour (hue, saturation, brightness, alpha); +} + +Colour::Colour (const float hue, const float saturation, const float brightness, const uint8 alpha) noexcept + : argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha)) +{ +} + +Colour::~Colour() noexcept +{ +} + +//============================================================================== +const PixelARGB Colour::getPixelARGB() const noexcept +{ + PixelARGB p (argb); + p.premultiply(); + return p; +} + +uint32 Colour::getARGB() const noexcept +{ + return argb.getARGB(); +} + +//============================================================================== +bool Colour::isTransparent() const noexcept +{ + return getAlpha() == 0; +} + +bool Colour::isOpaque() const noexcept +{ + return getAlpha() == 0xff; +} + +Colour Colour::withAlpha (const uint8 newAlpha) const noexcept +{ + PixelARGB newCol (argb); + newCol.setAlpha (newAlpha); + return Colour (newCol.getARGB()); +} + +Colour Colour::withAlpha (const float newAlpha) const noexcept +{ + jassert (newAlpha >= 0 && newAlpha <= 1.0f); + + PixelARGB newCol (argb); + newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha)); + return Colour (newCol.getARGB()); +} + +Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const noexcept +{ + jassert (alphaMultiplier >= 0); + + PixelARGB newCol (argb); + newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); + return Colour (newCol.getARGB()); +} + +//============================================================================== +Colour Colour::overlaidWith (Colour src) const noexcept +{ + const int destAlpha = getAlpha(); + + if (destAlpha <= 0) + return src; + + const int invA = 0xff - (int) src.getAlpha(); + const int resA = 0xff - (((0xff - destAlpha) * invA) >> 8); + + if (resA <= 0) + return *this; + + const int da = (invA * destAlpha) / resA; + + return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), + (uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), + (uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), + (uint8) resA); +} + +Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept +{ + if (proportionOfOther <= 0) + return *this; + + if (proportionOfOther >= 1.0f) + return other; + + PixelARGB c1 (getPixelARGB()); + const PixelARGB c2 (other.getPixelARGB()); + c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f)); + c1.unpremultiply(); + + return Colour (c1.getARGB()); +} + +//============================================================================== +float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; } +float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; } +float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; } +float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; } + +//============================================================================== +void Colour::getHSB (float& h, float& s, float& v) const noexcept +{ + const ColourHelpers::HSB hsb (*this); + h = hsb.hue; + s = hsb.saturation; + v = hsb.brightness; +} + +float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; } +float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; } +float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; } + +Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); } +Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); } +Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); } + +float Colour::getPerceivedBrightness() const noexcept +{ + return std::sqrt (0.241f * square (getFloatRed()) + + 0.691f * square (getFloatGreen()) + + 0.068f * square (getFloatBlue())); +} + +//============================================================================== +Colour Colour::withRotatedHue (const float amountToRotate) const noexcept +{ + ColourHelpers::HSB hsb (*this); + hsb.hue += amountToRotate; + return hsb.toColour (*this); +} + +Colour Colour::withMultipliedSaturation (const float amount) const noexcept +{ + ColourHelpers::HSB hsb (*this); + hsb.saturation = jmin (1.0f, hsb.saturation * amount); + return hsb.toColour (*this); +} + +Colour Colour::withMultipliedBrightness (const float amount) const noexcept +{ + ColourHelpers::HSB hsb (*this); + hsb.brightness = jmin (1.0f, hsb.brightness * amount); + return hsb.toColour (*this); +} + +//============================================================================== +Colour Colour::brighter (float amount) const noexcept +{ + amount = 1.0f / (1.0f + amount); + + return Colour ((uint8) (255 - (amount * (255 - getRed()))), + (uint8) (255 - (amount * (255 - getGreen()))), + (uint8) (255 - (amount * (255 - getBlue()))), + getAlpha()); +} + +Colour Colour::darker (float amount) const noexcept +{ + amount = 1.0f / (1.0f + amount); + + return Colour ((uint8) (amount * getRed()), + (uint8) (amount * getGreen()), + (uint8) (amount * getBlue()), + getAlpha()); +} + +//============================================================================== +Colour Colour::greyLevel (const float brightness) noexcept +{ + const uint8 level = ColourHelpers::floatToUInt8 (brightness); + return Colour (level, level, level); +} + +//============================================================================== +Colour Colour::contrasting (const float amount) const noexcept +{ + return overlaidWith ((getPerceivedBrightness() >= 0.5f + ? Colours::black + : Colours::white).withAlpha (amount)); +} + +Colour Colour::contrasting (Colour target, float minContrast) const noexcept +{ + const ColourHelpers::YIQ bg (*this); + ColourHelpers::YIQ fg (target); + + if (std::abs (bg.y - fg.y) >= minContrast) + return target; + + const float y1 = jmax (0.0f, bg.y - minContrast); + const float y2 = jmin (1.0f, bg.y + minContrast); + fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2; + + return fg.toColour(); +} + +Colour Colour::contrasting (Colour colour1, + Colour colour2) noexcept +{ + const float b1 = colour1.getPerceivedBrightness(); + const float b2 = colour2.getPerceivedBrightness(); + float best = 0.0f; + float bestDist = 0.0f; + + for (float i = 0.0f; i < 1.0f; i += 0.02f) + { + const float d1 = std::abs (i - b1); + const float d2 = std::abs (i - b2); + const float dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); + + if (dist > bestDist) + { + best = i; + bestDist = dist; + } + } + + return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) + .withBrightness (best); +} + +//============================================================================== +String Colour::toString() const +{ + return String::toHexString ((int) argb.getARGB()); +} + +Colour Colour::fromString (StringRef encodedColourString) +{ + return Colour ((uint32) CharacterFunctions::HexParser::parse (encodedColourString.text)); +} + +String Colour::toDisplayString (const bool includeAlphaValue) const +{ + return String::toHexString ((int) (argb.getARGB() & (includeAlphaValue ? 0xffffffff : 0xffffff))) + .paddedLeft ('0', includeAlphaValue ? 8 : 6) + .toUpperCase(); +} diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h new file mode 100644 index 0000000..4d84504 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h @@ -0,0 +1,360 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_COLOUR_H_INCLUDED +#define JUCE_COLOUR_H_INCLUDED + + +//============================================================================== +/** + Represents a colour, also including a transparency value. + + The colour is stored internally as unsigned 8-bit red, green, blue and alpha values. +*/ +class JUCE_API Colour +{ +public: + //============================================================================== + /** Creates a transparent black colour. */ + Colour() noexcept; + + /** Creates a copy of another Colour object. */ + Colour (const Colour& other) noexcept; + + /** Creates a colour from a 32-bit ARGB value. + + The format of this number is: + ((alpha << 24) | (red << 16) | (green << 8) | blue). + + All components in the range 0x00 to 0xff. + An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. + + @see getPixelARGB + */ + explicit Colour (uint32 argb) noexcept; + + /** Creates an opaque colour using 8-bit red, green and blue values */ + Colour (uint8 red, + uint8 green, + uint8 blue) noexcept; + + /** Creates an opaque colour using 8-bit red, green and blue values */ + static Colour fromRGB (uint8 red, + uint8 green, + uint8 blue) noexcept; + + /** Creates a colour using 8-bit red, green, blue and alpha values. */ + Colour (uint8 red, + uint8 green, + uint8 blue, + uint8 alpha) noexcept; + + /** Creates a colour using 8-bit red, green, blue and alpha values. */ + static Colour fromRGBA (uint8 red, + uint8 green, + uint8 blue, + uint8 alpha) noexcept; + + /** Creates a colour from 8-bit red, green, and blue values, and a floating-point alpha. + + Alpha of 0.0 is transparent, alpha of 1.0f is opaque. + Values outside the valid range will be clipped. + */ + Colour (uint8 red, + uint8 green, + uint8 blue, + float alpha) noexcept; + + /** Creates a colour using floating point red, green, blue and alpha values. + Numbers outside the range 0..1 will be clipped. + */ + static Colour fromFloatRGBA (float red, + float green, + float blue, + float alpha) noexcept; + + /** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. + + The floating point values must be between 0.0 and 1.0. + An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. + Values outside the valid range will be clipped. + */ + Colour (float hue, + float saturation, + float brightness, + uint8 alpha) noexcept; + + /** Creates a colour using floating point hue, saturation, brightness and alpha values. + + All values must be between 0.0 and 1.0. + Numbers outside the valid range will be clipped. + */ + Colour (float hue, + float saturation, + float brightness, + float alpha) noexcept; + + /** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. + + The floating point values must be between 0.0 and 1.0. + An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. + Values outside the valid range will be clipped. + */ + static Colour fromHSV (float hue, + float saturation, + float brightness, + float alpha) noexcept; + + /** Destructor. */ + ~Colour() noexcept; + + /** Copies another Colour object. */ + Colour& operator= (const Colour& other) noexcept; + + /** Compares two colours. */ + bool operator== (const Colour& other) const noexcept; + /** Compares two colours. */ + bool operator!= (const Colour& other) const noexcept; + + //============================================================================== + /** Returns the red component of this colour. + @returns a value between 0x00 and 0xff. + */ + uint8 getRed() const noexcept { return argb.getRed(); } + + /** Returns the green component of this colour. + @returns a value between 0x00 and 0xff. + */ + uint8 getGreen() const noexcept { return argb.getGreen(); } + + /** Returns the blue component of this colour. + @returns a value between 0x00 and 0xff. + */ + uint8 getBlue() const noexcept { return argb.getBlue(); } + + /** Returns the red component of this colour as a floating point value. + @returns a value between 0.0 and 1.0 + */ + float getFloatRed() const noexcept; + + /** Returns the green component of this colour as a floating point value. + @returns a value between 0.0 and 1.0 + */ + float getFloatGreen() const noexcept; + + /** Returns the blue component of this colour as a floating point value. + @returns a value between 0.0 and 1.0 + */ + float getFloatBlue() const noexcept; + + /** Returns a premultiplied ARGB pixel object that represents this colour. + */ + const PixelARGB getPixelARGB() const noexcept; + + /** Returns a 32-bit integer that represents this colour. + + The format of this number is: + ((alpha << 24) | (red << 16) | (green << 16) | blue). + */ + uint32 getARGB() const noexcept; + + //============================================================================== + /** Returns the colour's alpha (opacity). + + Alpha of 0x00 is completely transparent, 0xff is completely opaque. + */ + uint8 getAlpha() const noexcept { return argb.getAlpha(); } + + /** Returns the colour's alpha (opacity) as a floating point value. + + Alpha of 0.0 is completely transparent, 1.0 is completely opaque. + */ + float getFloatAlpha() const noexcept; + + /** Returns true if this colour is completely opaque. + + Equivalent to (getAlpha() == 0xff). + */ + bool isOpaque() const noexcept; + + /** Returns true if this colour is completely transparent. + + Equivalent to (getAlpha() == 0x00). + */ + bool isTransparent() const noexcept; + + /** Returns a colour that's the same colour as this one, but with a new alpha value. */ + Colour withAlpha (uint8 newAlpha) const noexcept; + + /** Returns a colour that's the same colour as this one, but with a new alpha value. */ + Colour withAlpha (float newAlpha) const noexcept; + + /** Returns a colour that's the same colour as this one, but with a modified alpha value. + The new colour's alpha will be this object's alpha multiplied by the value passed-in. + */ + Colour withMultipliedAlpha (float alphaMultiplier) const noexcept; + + //============================================================================== + /** Returns a colour that is the result of alpha-compositing a new colour over this one. + If the foreground colour is semi-transparent, it is blended onto this colour accordingly. + */ + Colour overlaidWith (Colour foregroundColour) const noexcept; + + /** Returns a colour that lies somewhere between this one and another. + If amountOfOther is zero, the result is 100% this colour, if amountOfOther + is 1.0, the result is 100% of the other colour. + */ + Colour interpolatedWith (Colour other, float proportionOfOther) const noexcept; + + //============================================================================== + /** Returns the colour's hue component. + The value returned is in the range 0.0 to 1.0 + */ + float getHue() const noexcept; + + /** Returns the colour's saturation component. + The value returned is in the range 0.0 to 1.0 + */ + float getSaturation() const noexcept; + + /** Returns the colour's brightness component. + The value returned is in the range 0.0 to 1.0 + */ + float getBrightness() const noexcept; + + /** Returns a skewed brightness value, adjusted to better reflect the way the human + eye responds to different colour channels. This makes it better than getBrightness() + for comparing differences in brightness. + */ + float getPerceivedBrightness() const noexcept; + + /** Returns the colour's hue, saturation and brightness components all at once. + The values returned are in the range 0.0 to 1.0 + */ + void getHSB (float& hue, + float& saturation, + float& brightness) const noexcept; + + //============================================================================== + /** Returns a copy of this colour with a different hue. */ + Colour withHue (float newHue) const noexcept; + + /** Returns a copy of this colour with a different saturation. */ + Colour withSaturation (float newSaturation) const noexcept; + + /** Returns a copy of this colour with a different brightness. + @see brighter, darker, withMultipliedBrightness + */ + Colour withBrightness (float newBrightness) const noexcept; + + /** Returns a copy of this colour with it hue rotated. + + The new colour's hue is ((this->getHue() + amountToRotate) % 1.0) + + @see brighter, darker, withMultipliedBrightness + */ + Colour withRotatedHue (float amountToRotate) const noexcept; + + /** Returns a copy of this colour with its saturation multiplied by the given value. + + The new colour's saturation is (this->getSaturation() * multiplier) + (the result is clipped to legal limits). + */ + Colour withMultipliedSaturation (float multiplier) const noexcept; + + /** Returns a copy of this colour with its brightness multiplied by the given value. + + The new colour's saturation is (this->getBrightness() * multiplier) + (the result is clipped to legal limits). + */ + Colour withMultipliedBrightness (float amount) const noexcept; + + //============================================================================== + /** Returns a brighter version of this colour. + + @param amountBrighter how much brighter to make it - a value from 0 to 1.0 where 0 is + unchanged, and higher values make it brighter + @see withMultipliedBrightness + */ + Colour brighter (float amountBrighter = 0.4f) const noexcept; + + /** Returns a darker version of this colour. + + @param amountDarker how much darker to make it - a value from 0 to 1.0 where 0 is + unchanged, and higher values make it darker + @see withMultipliedBrightness + */ + Colour darker (float amountDarker = 0.4f) const noexcept; + + //============================================================================== + /** Returns a colour that will be clearly visible against this colour. + + The amount parameter indicates how contrasting the new colour should + be, so e.g. Colours::black.contrasting (0.1f) will return a colour + that's just a little bit lighter; Colours::black.contrasting (1.0f) will + return white; Colours::white.contrasting (1.0f) will return black, etc. + */ + Colour contrasting (float amount = 1.0f) const noexcept; + + /** Returns a colour that is as close as possible to a target colour whilst + still being in contrast to this one. + + The colour that is returned will be the targetColour, but with its luminosity + nudged up or down so that it differs from the luminosity of this colour + by at least the amount specified by minLuminosityDiff. + */ + Colour contrasting (Colour targetColour, float minLuminosityDiff) const noexcept; + + /** Returns a colour that contrasts against two colours. + Looks for a colour that contrasts with both of the colours passed-in. + Handy for things like choosing a highlight colour in text editors, etc. + */ + static Colour contrasting (Colour colour1, + Colour colour2) noexcept; + + //============================================================================== + /** Returns an opaque shade of grey. + @param brightness the level of grey to return - 0 is black, 1.0 is white + */ + static Colour greyLevel (float brightness) noexcept; + + //============================================================================== + /** Returns a stringified version of this colour. + The string can be turned back into a colour using the fromString() method. + */ + String toString() const; + + /** Reads the colour from a string that was created with toString(). */ + static Colour fromString (StringRef encodedColourString); + + /** Returns the colour as a hex string in the form RRGGBB or AARRGGBB. */ + String toDisplayString (bool includeAlphaValue) const; + +private: + //============================================================================== + PixelARGB argb; +}; + + +#endif // JUCE_COLOUR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp new file mode 100644 index 0000000..75d1712 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -0,0 +1,220 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ColourGradient::ColourGradient() noexcept +{ + #if JUCE_DEBUG + point1.setX (987654.0f); + #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (point1.x != 987654.0f); + #else + #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED + #endif +} + +ColourGradient::ColourGradient (Colour colour1, const float x1_, const float y1_, + Colour colour2, const float x2_, const float y2_, + const bool isRadial_) + : point1 (x1_, y1_), + point2 (x2_, y2_), + isRadial (isRadial_) +{ + colours.add (ColourPoint (0.0, colour1)); + colours.add (ColourPoint (1.0, colour2)); +} + +ColourGradient::~ColourGradient() +{ +} + +bool ColourGradient::operator== (const ColourGradient& other) const noexcept +{ + return point1 == other.point1 && point2 == other.point2 + && isRadial == other.isRadial + && colours == other.colours; +} + +bool ColourGradient::operator!= (const ColourGradient& other) const noexcept +{ + return ! operator== (other); +} + +//============================================================================== +void ColourGradient::clearColours() +{ + colours.clear(); +} + +int ColourGradient::addColour (const double proportionAlongGradient, Colour colour) +{ + // must be within the two end-points + jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); + + const double pos = jlimit (0.0, 1.0, proportionAlongGradient); + + int i; + for (i = 0; i < colours.size(); ++i) + if (colours.getReference(i).position > pos) + break; + + colours.insert (i, ColourPoint (pos, colour)); + return i; +} + +void ColourGradient::removeColour (int index) +{ + jassert (index > 0 && index < colours.size() - 1); + colours.remove (index); +} + +void ColourGradient::multiplyOpacity (const float multiplier) noexcept +{ + for (int i = 0; i < colours.size(); ++i) + { + Colour& c = colours.getReference(i).colour; + c = c.withMultipliedAlpha (multiplier); + } +} + +//============================================================================== +int ColourGradient::getNumColours() const noexcept +{ + return colours.size(); +} + +double ColourGradient::getColourPosition (const int index) const noexcept +{ + if (isPositiveAndBelow (index, colours.size())) + return colours.getReference (index).position; + + return 0; + } + +Colour ColourGradient::getColour (const int index) const noexcept +{ + if (isPositiveAndBelow (index, colours.size())) + return colours.getReference (index).colour; + + return Colour(); +} + +void ColourGradient::setColour (int index, Colour newColour) noexcept +{ + if (isPositiveAndBelow (index, colours.size())) + colours.getReference (index).colour = newColour; +} + +Colour ColourGradient::getColourAtPosition (const double position) const noexcept +{ + jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 + + if (position <= 0 || colours.size() <= 1) + return colours.getReference(0).colour; + + int i = colours.size() - 1; + while (position < colours.getReference(i).position) + --i; + + const ColourPoint& p1 = colours.getReference (i); + + if (i >= colours.size() - 1) + return p1.colour; + + const ColourPoint& p2 = colours.getReference (i + 1); + + return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position))); +} + +//============================================================================== +void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept +{ + JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? + jassert (colours.size() >= 2); + jassert (numEntries > 0); + jassert (colours.getReference(0).position == 0); // The first colour specified has to go at position 0 + + PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); + int index = 0; + + for (int j = 1; j < colours.size(); ++j) + { + const ColourPoint& p = colours.getReference (j); + const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; + const PixelARGB pix2 (p.colour.getPixelARGB()); + + for (int i = 0; i < numToDo; ++i) + { + jassert (index >= 0 && index < numEntries); + + lookupTable[index] = pix1; + lookupTable[index].tween (pix2, (uint32) ((i << 8) / numToDo)); + ++index; + } + + pix1 = pix2; + } + + while (index < numEntries) + lookupTable [index++] = pix1; +} + +int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock & lookupTable) const +{ + JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? + jassert (colours.size() >= 2); + + const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), + 3 * (int) point1.transformedBy (transform) + .getDistanceFrom (point2.transformedBy (transform))); + lookupTable.malloc ((size_t) numEntries); + createLookupTable (lookupTable, numEntries); + return numEntries; +} + +bool ColourGradient::isOpaque() const noexcept +{ + for (int i = 0; i < colours.size(); ++i) + if (! colours.getReference(i).colour.isOpaque()) + return false; + + return true; +} + +bool ColourGradient::isInvisible() const noexcept +{ + for (int i = 0; i < colours.size(); ++i) + if (! colours.getReference(i).colour.isTransparent()) + return false; + + return true; +} + +bool ColourGradient::ColourPoint::operator== (const ColourPoint& other) const noexcept +{ + return position == other.position && colour == other.colour; +} + +bool ColourGradient::ColourPoint::operator!= (const ColourPoint& other) const noexcept +{ + return position != other.position || colour != other.colour; +} diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h new file mode 100644 index 0000000..73e6631 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h @@ -0,0 +1,181 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_COLOURGRADIENT_H_INCLUDED +#define JUCE_COLOURGRADIENT_H_INCLUDED + + +//============================================================================== +/** + Describes the layout and colours that should be used to paint a colour gradient. + + @see Graphics::setGradientFill +*/ +class JUCE_API ColourGradient +{ +public: + //============================================================================== + /** Creates a gradient object. + + (x1, y1) is the location to draw with colour1. Likewise (x2, y2) is where + colour2 should be. In between them there's a gradient. + + If isRadial is true, the colours form a circular gradient with (x1, y1) at + its centre. + + The alpha transparencies of the colours are used, so note that + if you blend from transparent to a solid colour, the RGB of the transparent + colour will become visible in parts of the gradient. e.g. blending + from Colour::transparentBlack to Colours::white will produce a + muddy grey colour midway, but Colour::transparentWhite to Colours::white + will be white all the way across. + + @see ColourGradient + */ + ColourGradient (Colour colour1, float x1, float y1, + Colour colour2, float x2, float y2, + bool isRadial); + + /** Creates an uninitialised gradient. + + If you use this constructor instead of the other one, be sure to set all the + object's public member variables before using it! + */ + ColourGradient() noexcept; + + /** Destructor */ + ~ColourGradient(); + + //============================================================================== + /** Removes any colours that have been added. + + This will also remove any start and end colours, so the gradient won't work. You'll + need to add more colours with addColour(). + */ + void clearColours(); + + /** Adds a colour at a point along the length of the gradient. + + This allows the gradient to go through a spectrum of colours, instead of just a + start and end colour. + + @param proportionAlongGradient a value between 0 and 1.0, which is the proportion + of the distance along the line between the two points + at which the colour should occur. + @param colour the colour that should be used at this point + @returns the index at which the new point was added + */ + int addColour (double proportionAlongGradient, + Colour colour); + + /** Removes one of the colours from the gradient. */ + void removeColour (int index); + + /** Multiplies the alpha value of all the colours by the given scale factor */ + void multiplyOpacity (float multiplier) noexcept; + + //============================================================================== + /** Returns the number of colour-stops that have been added. */ + int getNumColours() const noexcept; + + /** Returns the position along the length of the gradient of the colour with this index. + + The index is from 0 to getNumColours() - 1. The return value will be between 0.0 and 1.0 + */ + double getColourPosition (int index) const noexcept; + + /** Returns the colour that was added with a given index. + The index is from 0 to getNumColours() - 1. + */ + Colour getColour (int index) const noexcept; + + /** Changes the colour at a given index. + The index is from 0 to getNumColours() - 1. + */ + void setColour (int index, Colour newColour) noexcept; + + /** Returns the an interpolated colour at any position along the gradient. + @param position the position along the gradient, between 0 and 1 + */ + Colour getColourAtPosition (double position) const noexcept; + + //============================================================================== + /** Creates a set of interpolated premultiplied ARGB values. + This will resize the HeapBlock, fill it with the colours, and will return the number of + colours that it added. + When calling this, the ColourGradient must have at least 2 colour stops specified. + */ + int createLookupTable (const AffineTransform& transform, HeapBlock & resultLookupTable) const; + + /** Creates a set of interpolated premultiplied ARGB values. + This will fill an array of a user-specified size with the gradient, interpolating to fit. + The numEntries argument specifies the size of the array, and this size must be greater than zero. + When calling this, the ColourGradient must have at least 2 colour stops specified. + */ + void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; + + /** Returns true if all colours are opaque. */ + bool isOpaque() const noexcept; + + /** Returns true if all colours are completely transparent. */ + bool isInvisible() const noexcept; + + //============================================================================== + Point point1, point2; + + /** If true, the gradient should be filled circularly, centred around + point1, with point2 defining a point on the circumference. + + If false, the gradient is linear between the two points. + */ + bool isRadial; + + bool operator== (const ColourGradient&) const noexcept; + bool operator!= (const ColourGradient&) const noexcept; + + +private: + //============================================================================== + struct ColourPoint + { + ColourPoint() noexcept {} + + ColourPoint (const double pos, Colour col) noexcept + : position (pos), colour (col) + {} + + bool operator== (const ColourPoint&) const noexcept; + bool operator!= (const ColourPoint&) const noexcept; + + double position; + Colour colour; + }; + + Array colours; + + JUCE_LEAK_DETECTOR (ColourGradient) +}; + + +#endif // JUCE_COLOURGRADIENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.cpp new file mode 100644 index 0000000..fbde0ba --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.cpp @@ -0,0 +1,320 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +const Colour Colours::transparentBlack (0); +const Colour Colours::transparentWhite (0x00ffffff); + +const Colour Colours::aliceblue (0xfff0f8ff); +const Colour Colours::antiquewhite (0xfffaebd7); +const Colour Colours::aqua (0xff00ffff); +const Colour Colours::aquamarine (0xff7fffd4); +const Colour Colours::azure (0xfff0ffff); +const Colour Colours::beige (0xfff5f5dc); +const Colour Colours::bisque (0xffffe4c4); +const Colour Colours::black (0xff000000); +const Colour Colours::blanchedalmond (0xffffebcd); +const Colour Colours::blue (0xff0000ff); +const Colour Colours::blueviolet (0xff8a2be2); +const Colour Colours::brown (0xffa52a2a); +const Colour Colours::burlywood (0xffdeb887); +const Colour Colours::cadetblue (0xff5f9ea0); +const Colour Colours::chartreuse (0xff7fff00); +const Colour Colours::chocolate (0xffd2691e); +const Colour Colours::coral (0xffff7f50); +const Colour Colours::cornflowerblue (0xff6495ed); +const Colour Colours::cornsilk (0xfffff8dc); +const Colour Colours::crimson (0xffdc143c); +const Colour Colours::cyan (0xff00ffff); +const Colour Colours::darkblue (0xff00008b); +const Colour Colours::darkcyan (0xff008b8b); +const Colour Colours::darkgoldenrod (0xffb8860b); +const Colour Colours::darkgrey (0xff555555); +const Colour Colours::darkgreen (0xff006400); +const Colour Colours::darkkhaki (0xffbdb76b); +const Colour Colours::darkmagenta (0xff8b008b); +const Colour Colours::darkolivegreen (0xff556b2f); +const Colour Colours::darkorange (0xffff8c00); +const Colour Colours::darkorchid (0xff9932cc); +const Colour Colours::darkred (0xff8b0000); +const Colour Colours::darksalmon (0xffe9967a); +const Colour Colours::darkseagreen (0xff8fbc8f); +const Colour Colours::darkslateblue (0xff483d8b); +const Colour Colours::darkslategrey (0xff2f4f4f); +const Colour Colours::darkturquoise (0xff00ced1); +const Colour Colours::darkviolet (0xff9400d3); +const Colour Colours::deeppink (0xffff1493); +const Colour Colours::deepskyblue (0xff00bfff); +const Colour Colours::dimgrey (0xff696969); +const Colour Colours::dodgerblue (0xff1e90ff); +const Colour Colours::firebrick (0xffb22222); +const Colour Colours::floralwhite (0xfffffaf0); +const Colour Colours::forestgreen (0xff228b22); +const Colour Colours::fuchsia (0xffff00ff); +const Colour Colours::gainsboro (0xffdcdcdc); +const Colour Colours::gold (0xffffd700); +const Colour Colours::goldenrod (0xffdaa520); +const Colour Colours::grey (0xff808080); +const Colour Colours::green (0xff008000); +const Colour Colours::greenyellow (0xffadff2f); +const Colour Colours::honeydew (0xfff0fff0); +const Colour Colours::hotpink (0xffff69b4); +const Colour Colours::indianred (0xffcd5c5c); +const Colour Colours::indigo (0xff4b0082); +const Colour Colours::ivory (0xfffffff0); +const Colour Colours::khaki (0xfff0e68c); +const Colour Colours::lavender (0xffe6e6fa); +const Colour Colours::lavenderblush (0xfffff0f5); +const Colour Colours::lemonchiffon (0xfffffacd); +const Colour Colours::lightblue (0xffadd8e6); +const Colour Colours::lightcoral (0xfff08080); +const Colour Colours::lightcyan (0xffe0ffff); +const Colour Colours::lightgoldenrodyellow (0xfffafad2); +const Colour Colours::lightgreen (0xff90ee90); +const Colour Colours::lightgrey (0xffd3d3d3); +const Colour Colours::lightpink (0xffffb6c1); +const Colour Colours::lightsalmon (0xffffa07a); +const Colour Colours::lightseagreen (0xff20b2aa); +const Colour Colours::lightskyblue (0xff87cefa); +const Colour Colours::lightslategrey (0xff778899); +const Colour Colours::lightsteelblue (0xffb0c4de); +const Colour Colours::lightyellow (0xffffffe0); +const Colour Colours::lime (0xff00ff00); +const Colour Colours::limegreen (0xff32cd32); +const Colour Colours::linen (0xfffaf0e6); +const Colour Colours::magenta (0xffff00ff); +const Colour Colours::maroon (0xff800000); +const Colour Colours::mediumaquamarine (0xff66cdaa); +const Colour Colours::mediumblue (0xff0000cd); +const Colour Colours::mediumorchid (0xffba55d3); +const Colour Colours::mediumpurple (0xff9370db); +const Colour Colours::mediumseagreen (0xff3cb371); +const Colour Colours::mediumslateblue (0xff7b68ee); +const Colour Colours::mediumspringgreen (0xff00fa9a); +const Colour Colours::mediumturquoise (0xff48d1cc); +const Colour Colours::mediumvioletred (0xffc71585); +const Colour Colours::midnightblue (0xff191970); +const Colour Colours::mintcream (0xfff5fffa); +const Colour Colours::mistyrose (0xffffe4e1); +const Colour Colours::navajowhite (0xffffdead); +const Colour Colours::navy (0xff000080); +const Colour Colours::oldlace (0xfffdf5e6); +const Colour Colours::olive (0xff808000); +const Colour Colours::olivedrab (0xff6b8e23); +const Colour Colours::orange (0xffffa500); +const Colour Colours::orangered (0xffff4500); +const Colour Colours::orchid (0xffda70d6); +const Colour Colours::palegoldenrod (0xffeee8aa); +const Colour Colours::palegreen (0xff98fb98); +const Colour Colours::paleturquoise (0xffafeeee); +const Colour Colours::palevioletred (0xffdb7093); +const Colour Colours::papayawhip (0xffffefd5); +const Colour Colours::peachpuff (0xffffdab9); +const Colour Colours::peru (0xffcd853f); +const Colour Colours::pink (0xffffc0cb); +const Colour Colours::plum (0xffdda0dd); +const Colour Colours::powderblue (0xffb0e0e6); +const Colour Colours::purple (0xff800080); +const Colour Colours::red (0xffff0000); +const Colour Colours::rosybrown (0xffbc8f8f); +const Colour Colours::royalblue (0xff4169e1); +const Colour Colours::saddlebrown (0xff8b4513); +const Colour Colours::salmon (0xfffa8072); +const Colour Colours::sandybrown (0xfff4a460); +const Colour Colours::seagreen (0xff2e8b57); +const Colour Colours::seashell (0xfffff5ee); +const Colour Colours::sienna (0xffa0522d); +const Colour Colours::silver (0xffc0c0c0); +const Colour Colours::skyblue (0xff87ceeb); +const Colour Colours::slateblue (0xff6a5acd); +const Colour Colours::slategrey (0xff708090); +const Colour Colours::snow (0xfffffafa); +const Colour Colours::springgreen (0xff00ff7f); +const Colour Colours::steelblue (0xff4682b4); +const Colour Colours::tan (0xffd2b48c); +const Colour Colours::teal (0xff008080); +const Colour Colours::thistle (0xffd8bfd8); +const Colour Colours::tomato (0xffff6347); +const Colour Colours::turquoise (0xff40e0d0); +const Colour Colours::violet (0xffee82ee); +const Colour Colours::wheat (0xfff5deb3); +const Colour Colours::white (0xffffffff); +const Colour Colours::whitesmoke (0xfff5f5f5); +const Colour Colours::yellow (0xffffff00); +const Colour Colours::yellowgreen (0xff9acd32); + +//============================================================================== +Colour Colours::findColourForName (const String& colourName, + Colour defaultColour) +{ + static const uint32 presets[] = + { + // (first value is the string's hashcode, second is ARGB) + + 0x05978fff, 0xff000000, /* black */ + 0x06bdcc29, 0xffffffff, /* white */ + 0x002e305a, 0xff0000ff, /* blue */ + 0x00308adf, 0xff808080, /* grey */ + 0x05e0cf03, 0xff008000, /* green */ + 0x0001b891, 0xffff0000, /* red */ + 0xd43c6474, 0xffffff00, /* yellow */ + 0x620886da, 0xfff0f8ff, /* aliceblue */ + 0x20a2676a, 0xfffaebd7, /* antiquewhite */ + 0x002dcebc, 0xff00ffff, /* aqua */ + 0x46bb5f7e, 0xff7fffd4, /* aquamarine */ + 0x0590228f, 0xfff0ffff, /* azure */ + 0x05947fe4, 0xfff5f5dc, /* beige */ + 0xad388e35, 0xffffe4c4, /* bisque */ + 0x00674f7e, 0xffffebcd, /* blanchedalmond */ + 0x39129959, 0xff8a2be2, /* blueviolet */ + 0x059a8136, 0xffa52a2a, /* brown */ + 0x89cea8f9, 0xffdeb887, /* burlywood */ + 0x0fa260cf, 0xff5f9ea0, /* cadetblue */ + 0x6b748956, 0xff7fff00, /* chartreuse */ + 0x2903623c, 0xffd2691e, /* chocolate */ + 0x05a74431, 0xffff7f50, /* coral */ + 0x618d42dd, 0xff6495ed, /* cornflowerblue */ + 0xe4b479fd, 0xfffff8dc, /* cornsilk */ + 0x3d8c4edf, 0xffdc143c, /* crimson */ + 0x002ed323, 0xff00ffff, /* cyan */ + 0x67cc74d0, 0xff00008b, /* darkblue */ + 0x67cd1799, 0xff008b8b, /* darkcyan */ + 0x31bbd168, 0xffb8860b, /* darkgoldenrod */ + 0x67cecf55, 0xff555555, /* darkgrey */ + 0x920b194d, 0xff006400, /* darkgreen */ + 0x923edd4c, 0xffbdb76b, /* darkkhaki */ + 0x5c293873, 0xff8b008b, /* darkmagenta */ + 0x6b6671fe, 0xff556b2f, /* darkolivegreen */ + 0xbcfd2524, 0xffff8c00, /* darkorange */ + 0xbcfdf799, 0xff9932cc, /* darkorchid */ + 0x55ee0d5b, 0xff8b0000, /* darkred */ + 0xc2e5f564, 0xffe9967a, /* darksalmon */ + 0x61be858a, 0xff8fbc8f, /* darkseagreen */ + 0xc2b0f2bd, 0xff483d8b, /* darkslateblue */ + 0xc2b34d42, 0xff2f4f4f, /* darkslategrey */ + 0x7cf2b06b, 0xff00ced1, /* darkturquoise */ + 0xc8769375, 0xff9400d3, /* darkviolet */ + 0x25832862, 0xffff1493, /* deeppink */ + 0xfcad568f, 0xff00bfff, /* deepskyblue */ + 0x634c8b67, 0xff696969, /* dimgrey */ + 0x45c1ce55, 0xff1e90ff, /* dodgerblue */ + 0xef19e3cb, 0xffb22222, /* firebrick */ + 0xb852b195, 0xfffffaf0, /* floralwhite */ + 0xd086fd06, 0xff228b22, /* forestgreen */ + 0xe106b6d7, 0xffff00ff, /* fuchsia */ + 0x7880d61e, 0xffdcdcdc, /* gainsboro */ + 0x00308060, 0xffffd700, /* gold */ + 0xb3b3bc1e, 0xffdaa520, /* goldenrod */ + 0xbab8a537, 0xffadff2f, /* greenyellow */ + 0xe4cacafb, 0xfff0fff0, /* honeydew */ + 0x41892743, 0xffff69b4, /* hotpink */ + 0xd5796f1a, 0xffcd5c5c, /* indianred */ + 0xb969fed2, 0xff4b0082, /* indigo */ + 0x05fef6a9, 0xfffffff0, /* ivory */ + 0x06149302, 0xfff0e68c, /* khaki */ + 0xad5a05c7, 0xffe6e6fa, /* lavender */ + 0x7c4d5b99, 0xfffff0f5, /* lavenderblush */ + 0x195756f0, 0xfffffacd, /* lemonchiffon */ + 0x28e4ea70, 0xffadd8e6, /* lightblue */ + 0xf3c7ccdb, 0xfff08080, /* lightcoral */ + 0x28e58d39, 0xffe0ffff, /* lightcyan */ + 0x21234e3c, 0xfffafad2, /* lightgoldenrodyellow */ + 0xf40157ad, 0xff90ee90, /* lightgreen */ + 0x28e744f5, 0xffd3d3d3, /* lightgrey */ + 0x28eb3b8c, 0xffffb6c1, /* lightpink */ + 0x9fb78304, 0xffffa07a, /* lightsalmon */ + 0x50632b2a, 0xff20b2aa, /* lightseagreen */ + 0x68fb7b25, 0xff87cefa, /* lightskyblue */ + 0xa8a35ba2, 0xff778899, /* lightslategrey */ + 0xa20d484f, 0xffb0c4de, /* lightsteelblue */ + 0xaa2cf10a, 0xffffffe0, /* lightyellow */ + 0x0032afd5, 0xff00ff00, /* lime */ + 0x607bbc4e, 0xff32cd32, /* limegreen */ + 0x06234efa, 0xfffaf0e6, /* linen */ + 0x316858a9, 0xffff00ff, /* magenta */ + 0xbf8ca470, 0xff800000, /* maroon */ + 0xbd58e0b3, 0xff66cdaa, /* mediumaquamarine */ + 0x967dfd4f, 0xff0000cd, /* mediumblue */ + 0x056f5c58, 0xffba55d3, /* mediumorchid */ + 0x07556b71, 0xff9370db, /* mediumpurple */ + 0x5369b689, 0xff3cb371, /* mediumseagreen */ + 0x066be19e, 0xff7b68ee, /* mediumslateblue */ + 0x3256b281, 0xff00fa9a, /* mediumspringgreen */ + 0xc0ad9f4c, 0xff48d1cc, /* mediumturquoise */ + 0x628e63dd, 0xffc71585, /* mediumvioletred */ + 0x168eb32a, 0xff191970, /* midnightblue */ + 0x4306b960, 0xfff5fffa, /* mintcream */ + 0x4cbc0e6b, 0xffffe4e1, /* mistyrose */ + 0xe97218a6, 0xffffdead, /* navajowhite */ + 0x00337bb6, 0xff000080, /* navy */ + 0xadd2d33e, 0xfffdf5e6, /* oldlace */ + 0x064ee1db, 0xff808000, /* olive */ + 0x9e33a98a, 0xff6b8e23, /* olivedrab */ + 0xc3de262e, 0xffffa500, /* orange */ + 0x58bebba3, 0xffff4500, /* orangered */ + 0xc3def8a3, 0xffda70d6, /* orchid */ + 0x28cb4834, 0xffeee8aa, /* palegoldenrod */ + 0x3d9dd619, 0xff98fb98, /* palegreen */ + 0x74022737, 0xffafeeee, /* paleturquoise */ + 0x15e2ebc8, 0xffdb7093, /* palevioletred */ + 0x5fd898e2, 0xffffefd5, /* papayawhip */ + 0x93e1b776, 0xffffdab9, /* peachpuff */ + 0x003472f8, 0xffcd853f, /* peru */ + 0x00348176, 0xffffc0cb, /* pink */ + 0x00348d94, 0xffdda0dd, /* plum */ + 0xd036be93, 0xffb0e0e6, /* powderblue */ + 0xc5c507bc, 0xff800080, /* purple */ + 0xa89d65b3, 0xffbc8f8f, /* rosybrown */ + 0xbd9413e1, 0xff4169e1, /* royalblue */ + 0xf456044f, 0xff8b4513, /* saddlebrown */ + 0xc9c6f66e, 0xfffa8072, /* salmon */ + 0x0bb131e1, 0xfff4a460, /* sandybrown */ + 0x34636c14, 0xff2e8b57, /* seagreen */ + 0x3507fb41, 0xfffff5ee, /* seashell */ + 0xca348772, 0xffa0522d, /* sienna */ + 0xca37d30d, 0xffc0c0c0, /* silver */ + 0x80da74fb, 0xff87ceeb, /* skyblue */ + 0x44a8dd73, 0xff6a5acd, /* slateblue */ + 0x44ab37f8, 0xff708090, /* slategrey */ + 0x0035f183, 0xfffffafa, /* snow */ + 0xd5440d16, 0xff00ff7f, /* springgreen */ + 0x3e1524a5, 0xff4682b4, /* steelblue */ + 0x0001bfa1, 0xffd2b48c, /* tan */ + 0x0036425c, 0xff008080, /* teal */ + 0xafc8858f, 0xffd8bfd8, /* thistle */ + 0xcc41600a, 0xffff6347, /* tomato */ + 0xfeea9b21, 0xff40e0d0, /* turquoise */ + 0xcf57947f, 0xffee82ee, /* violet */ + 0x06bdbae7, 0xfff5deb3, /* wheat */ + 0x10802ee6, 0xfff5f5f5, /* whitesmoke */ + 0xe1b5130f, 0xff9acd32 /* yellowgreen */ + }; + + const uint32 hash = (uint32) colourName.trim().toLowerCase().hashCode(); + + for (int i = 0; i < numElementsInArray (presets); i += 2) + if (presets [i] == hash) + return Colour (presets [i + 1]); + + return defaultColour; +} diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.h new file mode 100644 index 0000000..90e4aa5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colours.h @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_COLOURS_H_INCLUDED +#define JUCE_COLOURS_H_INCLUDED + + +//============================================================================== +/** + Contains a set of predefined named colours (mostly standard HTML colours) + + @see Colour, Colours::greyLevel +*/ +class Colours +{ +public: + static JUCE_API const Colour + + //============================================================================== + transparentBlack, /**< ARGB = 0x00000000 */ + transparentWhite, /**< ARGB = 0x00ffffff */ + + //============================================================================== + black, /**< ARGB = 0xff000000 */ + white, /**< ARGB = 0xffffffff */ + blue, /**< ARGB = 0xff0000ff */ + grey, /**< ARGB = 0xff808080 */ + green, /**< ARGB = 0xff008000 */ + red, /**< ARGB = 0xffff0000 */ + yellow, /**< ARGB = 0xffffff00 */ + + //============================================================================== + aliceblue, antiquewhite, aqua, aquamarine, + azure, beige, bisque, blanchedalmond, + blueviolet, brown, burlywood, cadetblue, + chartreuse, chocolate, coral, cornflowerblue, + cornsilk, crimson, cyan, darkblue, + darkcyan, darkgoldenrod, darkgrey, darkgreen, + darkkhaki, darkmagenta, darkolivegreen, darkorange, + darkorchid, darkred, darksalmon, darkseagreen, + darkslateblue, darkslategrey, darkturquoise, darkviolet, + deeppink, deepskyblue, dimgrey, dodgerblue, + firebrick, floralwhite, forestgreen, fuchsia, + gainsboro, gold, goldenrod, greenyellow, + honeydew, hotpink, indianred, indigo, + ivory, khaki, lavender, lavenderblush, + lemonchiffon, lightblue, lightcoral, lightcyan, + lightgoldenrodyellow, lightgreen, lightgrey, lightpink, + lightsalmon, lightseagreen, lightskyblue, lightslategrey, + lightsteelblue, lightyellow, lime, limegreen, + linen, magenta, maroon, mediumaquamarine, + mediumblue, mediumorchid, mediumpurple, mediumseagreen, + mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, + midnightblue, mintcream, mistyrose, navajowhite, + navy, oldlace, olive, olivedrab, + orange, orangered, orchid, palegoldenrod, + palegreen, paleturquoise, palevioletred, papayawhip, + peachpuff, peru, pink, plum, + powderblue, purple, rosybrown, royalblue, + saddlebrown, salmon, sandybrown, seagreen, + seashell, sienna, silver, skyblue, + slateblue, slategrey, snow, springgreen, + steelblue, tan, teal, thistle, + tomato, turquoise, violet, wheat, + whitesmoke, yellowgreen; + + /** Attempts to look up a string in the list of known colour names, and return + the appropriate colour. + + A non-case-sensitive search is made of the list of predefined colours, and + if a match is found, that colour is returned. If no match is found, the + colour passed in as the defaultColour parameter is returned. + */ + static JUCE_API Colour findColourForName (const String& colourName, + Colour defaultColour); + +private: + //============================================================================== + // this isn't a class you should ever instantiate - it's just here for the + // static values in it. + Colours(); + + JUCE_DECLARE_NON_COPYABLE (Colours) +}; + +#endif // JUCE_COLOURS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.cpp new file mode 100644 index 0000000..5e5db56 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.cpp @@ -0,0 +1,148 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +FillType::FillType() noexcept + : colour (0xff000000) +{ +} + +FillType::FillType (Colour c) noexcept + : colour (c) +{ +} + +FillType::FillType (const ColourGradient& gradient_) + : colour (0xff000000), gradient (new ColourGradient (gradient_)) +{ +} + +FillType::FillType (const Image& image_, const AffineTransform& transform_) noexcept + : colour (0xff000000), image (image_), transform (transform_) +{ +} + +FillType::FillType (const FillType& other) + : colour (other.colour), + gradient (other.gradient.createCopy()), + image (other.image), + transform (other.transform) +{ +} + +FillType& FillType::operator= (const FillType& other) +{ + if (this != &other) + { + colour = other.colour; + gradient = other.gradient.createCopy(); + image = other.image; + transform = other.transform; + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +FillType::FillType (FillType&& other) noexcept + : colour (other.colour), + gradient (other.gradient.release()), + image (static_cast (other.image)), + transform (other.transform) +{ +} + +FillType& FillType::operator= (FillType&& other) noexcept +{ + jassert (this != &other); // hopefully the compiler should make this situation impossible! + + colour = other.colour; + gradient = other.gradient.release(); + image = static_cast (other.image); + transform = other.transform; + return *this; +} +#endif + +FillType::~FillType() noexcept +{ +} + +bool FillType::operator== (const FillType& other) const +{ + return colour == other.colour && image == other.image + && transform == other.transform + && (gradient == other.gradient + || (gradient != nullptr && other.gradient != nullptr && *gradient == *other.gradient)); +} + +bool FillType::operator!= (const FillType& other) const +{ + return ! operator== (other); +} + +void FillType::setColour (Colour newColour) noexcept +{ + gradient = nullptr; + image = Image::null; + colour = newColour; +} + +void FillType::setGradient (const ColourGradient& newGradient) +{ + if (gradient != nullptr) + { + *gradient = newGradient; + } + else + { + image = Image::null; + gradient = new ColourGradient (newGradient); + colour = Colours::black; + } +} + +void FillType::setTiledImage (const Image& image_, const AffineTransform& transform_) noexcept +{ + gradient = nullptr; + image = image_; + transform = transform_; + colour = Colours::black; +} + +void FillType::setOpacity (const float newOpacity) noexcept +{ + colour = colour.withAlpha (newOpacity); +} + +bool FillType::isInvisible() const noexcept +{ + return colour.isTransparent() || (gradient != nullptr && gradient->isInvisible()); +} + +FillType FillType::transformed (const AffineTransform& t) const +{ + FillType f (*this); + f.transform = f.transform.followedBy (t); + return f; +} diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.h new file mode 100644 index 0000000..19401d6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_FillType.h @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_FILLTYPE_H_INCLUDED +#define JUCE_FILLTYPE_H_INCLUDED + + +//============================================================================== +/** + Represents a colour or fill pattern to use for rendering paths. + + This is used by the Graphics and DrawablePath classes as a way to encapsulate + a brush type. It can either be a solid colour, a gradient, or a tiled image. + + @see Graphics::setFillType, DrawablePath::setFill +*/ +class JUCE_API FillType +{ +public: + //============================================================================== + /** Creates a default fill type, of solid black. */ + FillType() noexcept; + + /** Creates a fill type of a solid colour. + @see setColour + */ + FillType (Colour colour) noexcept; + + /** Creates a gradient fill type. + @see setGradient + */ + FillType (const ColourGradient& gradient); + + /** Creates a tiled image fill type. The transform allows you to set the scaling, offset + and rotation of the pattern. + @see setTiledImage + */ + FillType (const Image& image, const AffineTransform& transform) noexcept; + + /** Creates a copy of another FillType. */ + FillType (const FillType&); + + /** Makes a copy of another FillType. */ + FillType& operator= (const FillType&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + FillType (FillType&&) noexcept; + FillType& operator= (FillType&&) noexcept; + #endif + + /** Destructor. */ + ~FillType() noexcept; + + //============================================================================== + /** Returns true if this is a solid colour fill, and not a gradient or image. */ + bool isColour() const noexcept { return gradient == nullptr && image.isNull(); } + + /** Returns true if this is a gradient fill. */ + bool isGradient() const noexcept { return gradient != nullptr; } + + /** Returns true if this is a tiled image pattern fill. */ + bool isTiledImage() const noexcept { return image.isValid(); } + + /** Turns this object into a solid colour fill. + If the object was an image or gradient, those fields will no longer be valid. */ + void setColour (Colour newColour) noexcept; + + /** Turns this object into a gradient fill. */ + void setGradient (const ColourGradient& newGradient); + + /** Turns this object into a tiled image fill type. The transform allows you to set + the scaling, offset and rotation of the pattern. + */ + void setTiledImage (const Image& image, const AffineTransform& transform) noexcept; + + /** Changes the opacity that should be used. + If the fill is a solid colour, this just changes the opacity of that colour. For + gradients and image tiles, it changes the opacity that will be used for them. + */ + void setOpacity (float newOpacity) noexcept; + + /** Returns the current opacity to be applied to the colour, gradient, or image. + @see setOpacity + */ + float getOpacity() const noexcept { return colour.getFloatAlpha(); } + + /** Returns true if this fill type is completely transparent. */ + bool isInvisible() const noexcept; + + /** Returns a copy of this fill, adding the specified transform applied to the + existing transform. + */ + FillType transformed (const AffineTransform& transform) const; + + //============================================================================== + /** The solid colour being used. + + If the fill type is not a solid colour, the alpha channel of this colour indicates + the opacity that should be used for the fill, and the RGB channels are ignored. + */ + Colour colour; + + /** Returns the gradient that should be used for filling. + This will be zero if the object is some other type of fill. + If a gradient is active, the overall opacity with which it should be applied + is indicated by the alpha channel of the colour variable. + */ + ScopedPointer gradient; + + /** The image that should be used for tiling. + If an image fill is active, the overall opacity with which it should be applied + is indicated by the alpha channel of the colour variable. + */ + Image image; + + /** The transform that should be applied to the image or gradient that's being drawn. */ + AffineTransform transform; + + //============================================================================== + bool operator== (const FillType&) const; + bool operator!= (const FillType&) const; + +private: + JUCE_LEAK_DETECTOR (FillType) +}; + + +#endif // JUCE_FILLTYPE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_PixelFormats.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_PixelFormats.h new file mode 100644 index 0000000..2715cb2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_PixelFormats.h @@ -0,0 +1,604 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_PIXELFORMATS_H_INCLUDED +#define JUCE_PIXELFORMATS_H_INCLUDED + + +//============================================================================== +#if JUCE_MSVC + #pragma pack (push, 1) +#endif + +class PixelRGB; +class PixelAlpha; + +inline uint32 maskPixelComponents (uint32 x) noexcept +{ + return (x >> 8) & 0x00ff00ff; +} + +inline uint32 clampPixelComponents (uint32 x) noexcept +{ + return (x | (0x01000100 - maskPixelComponents (x))) & 0x00ff00ff; +} + +//============================================================================== +/** + Represents a 32-bit ARGB pixel with premultiplied alpha, and can perform compositing + operations with it. + + This is used internally by the imaging classes. + + @see PixelRGB +*/ +class JUCE_API PixelARGB +{ +public: + /** Creates a pixel without defining its colour. */ + PixelARGB() noexcept {} + ~PixelARGB() noexcept {} + + /** Creates a pixel from a 32-bit argb value. + */ + PixelARGB (const uint32 argbValue) noexcept + : argb (argbValue) + { + } + + PixelARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept + { + components.b = b; + components.g = g; + components.r = r; + components.a = a; + } + + forcedinline uint32 getARGB() const noexcept { return argb; } + forcedinline uint32 getUnpremultipliedARGB() const noexcept { PixelARGB p (argb); p.unpremultiply(); return p.getARGB(); } + + forcedinline uint32 getRB() const noexcept { return 0x00ff00ff & argb; } + forcedinline uint32 getAG() const noexcept { return 0x00ff00ff & (argb >> 8); } + + forcedinline uint8 getAlpha() const noexcept { return components.a; } + forcedinline uint8 getRed() const noexcept { return components.r; } + forcedinline uint8 getGreen() const noexcept { return components.g; } + forcedinline uint8 getBlue() const noexcept { return components.b; } + + #if JUCE_GCC && ! JUCE_CLANG + // NB these are here as a workaround because GCC refuses to bind to packed values. + forcedinline uint8& getAlpha() noexcept { return comps [indexA]; } + forcedinline uint8& getRed() noexcept { return comps [indexR]; } + forcedinline uint8& getGreen() noexcept { return comps [indexG]; } + forcedinline uint8& getBlue() noexcept { return comps [indexB]; } + #else + forcedinline uint8& getAlpha() noexcept { return components.a; } + forcedinline uint8& getRed() noexcept { return components.r; } + forcedinline uint8& getGreen() noexcept { return components.g; } + forcedinline uint8& getBlue() noexcept { return components.b; } + #endif + + /** Blends another pixel onto this one. + + This takes into account the opacity of the pixel being overlaid, and blends + it accordingly. + */ + template + forcedinline void blend (const Pixel& src) noexcept + { + const uint32 alpha = 0x100 - src.getAlpha(); + uint32 rb = src.getRB() + maskPixelComponents (getRB() * alpha); + uint32 ag = src.getAG() + maskPixelComponents (getAG() * alpha); + argb = clampPixelComponents (rb) + (clampPixelComponents (ag) << 8); + } + + /** Blends another pixel onto this one. + + This takes into account the opacity of the pixel being overlaid, and blends + it accordingly. + */ + forcedinline void blend (const PixelRGB src) noexcept; + + + /** Blends another pixel onto this one, applying an extra multiplier to its opacity. + + The opacity of the pixel being overlaid is scaled by the extraAlpha factor before + being used, so this can blend semi-transparently from a PixelRGB argument. + */ + template + forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept + { + uint32 ag = maskPixelComponents (extraAlpha * src.getAG()); + const uint32 alpha = 0x100 - (ag >> 16); + ag += maskPixelComponents (getAG() * alpha); + + uint32 rb = maskPixelComponents (extraAlpha * src.getRB()) + + maskPixelComponents (getRB() * alpha); + + argb = clampPixelComponents(rb) + (clampPixelComponents (ag) << 8); + } + + /** Blends another pixel with this one, creating a colour that is somewhere + between the two, as specified by the amount. + */ + template + forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + { + uint32 drb = getRB(); + drb += (((src.getRB() - drb) * amount) >> 8); + drb &= 0x00ff00ff; + + uint32 dag = getAG(); + dag += (((src.getAG() - dag) * amount) >> 8); + dag &= 0x00ff00ff; + dag <<= 8; + + dag |= drb; + argb = dag; + } + + /** Copies another pixel colour over this one. + + This doesn't blend it - this colour is simply replaced by the other one. + */ + template + forcedinline void set (const Pixel& src) noexcept + { + argb = src.getARGB(); + } + + /** Replaces the colour's alpha value with another one. */ + forcedinline void setAlpha (const uint8 newAlpha) noexcept + { + components.a = newAlpha; + } + + /** Multiplies the colour's alpha value with another one. */ + forcedinline void multiplyAlpha (int multiplier) noexcept + { + ++multiplier; + + argb = ((((uint32) multiplier) * getAG()) & 0xff00ff00) + | (((((uint32) multiplier) * getRB()) >> 8) & 0x00ff00ff); + } + + forcedinline void multiplyAlpha (const float multiplier) noexcept + { + multiplyAlpha ((int) (multiplier * 255.0f)); + } + + /** Sets the pixel's colour from individual components. */ + void setARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept + { + components.b = b; + components.g = g; + components.r = r; + components.a = a; + } + + /** Premultiplies the pixel's RGB values by its alpha. */ + forcedinline void premultiply() noexcept + { + const uint32 alpha = components.a; + + if (alpha < 0xff) + { + if (alpha == 0) + { + components.b = 0; + components.g = 0; + components.r = 0; + } + else + { + components.b = (uint8) ((components.b * alpha + 0x7f) >> 8); + components.g = (uint8) ((components.g * alpha + 0x7f) >> 8); + components.r = (uint8) ((components.r * alpha + 0x7f) >> 8); + } + } + } + + /** Unpremultiplies the pixel's RGB values. */ + forcedinline void unpremultiply() noexcept + { + const uint32 alpha = components.a; + + if (alpha < 0xff) + { + if (alpha == 0) + { + components.b = 0; + components.g = 0; + components.r = 0; + } + else + { + components.b = (uint8) jmin ((uint32) 0xff, (components.b * 0xff) / alpha); + components.g = (uint8) jmin ((uint32) 0xff, (components.g * 0xff) / alpha); + components.r = (uint8) jmin ((uint32) 0xff, (components.r * 0xff) / alpha); + } + } + } + + forcedinline void desaturate() noexcept + { + if (components.a < 0xff && components.a > 0) + { + const int newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a)); + + components.r = components.g = components.b + = (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8); + } + else + { + components.r = components.g = components.b + = (uint8) (((int) components.r + (int) components.g + (int) components.b) / 3); + } + } + + /** Returns a uint32 which when written to memory, will be in the order r, g, b, a. */ + inline uint32 getInRGBAMemoryOrder() const noexcept + { + #if JUCE_BIG_ENDIAN + return (((uint32) components.r) << 24) | (((uint32) components.g) << 16) | (((uint32) components.b) << 8) | components.a; + #else + return (((uint32) components.a) << 24) | (((uint32) components.b) << 16) | (((uint32) components.g) << 8) | components.r; + #endif + } + + //============================================================================== + /** The indexes of the different components in the byte layout of this type of colour. */ + #if JUCE_BIG_ENDIAN + enum { indexA = 0, indexR = 1, indexG = 2, indexB = 3 }; + #else + enum { indexA = 3, indexR = 2, indexG = 1, indexB = 0 }; + #endif + +private: + //============================================================================== + struct Components + { + #if JUCE_BIG_ENDIAN + uint8 a, r, g, b; + #else + uint8 b, g, r, a; + #endif + } JUCE_PACKED; + + union + { + uint32 argb; + Components components; + #if JUCE_GCC + uint8 comps[4]; + #endif + }; +} +#ifndef DOXYGEN + JUCE_PACKED +#endif +; + + +//============================================================================== +/** + Represents a 24-bit RGB pixel, and can perform compositing operations on it. + + This is used internally by the imaging classes. + + @see PixelARGB +*/ +class JUCE_API PixelRGB +{ +public: + /** Creates a pixel without defining its colour. */ + PixelRGB() noexcept {} + ~PixelRGB() noexcept {} + + /** Creates a pixel from a 32-bit argb value. + + (The argb format is that used by PixelARGB) + */ + PixelRGB (const uint32 argb) noexcept + { + r = (uint8) (argb >> 16); + g = (uint8) (argb >> 8); + b = (uint8) (argb); + } + + forcedinline uint32 getARGB() const noexcept { return 0xff000000 | b | (((uint32) g) << 8) | (((uint32) r) << 16); } + forcedinline uint32 getUnpremultipliedARGB() const noexcept { return getARGB(); } + + forcedinline uint32 getRB() const noexcept { return b | (uint32) (r << 16); } + forcedinline uint32 getAG() const noexcept { return (uint32) (0xff0000 | g); } + + forcedinline uint8 getAlpha() const noexcept { return 0xff; } + forcedinline uint8 getRed() const noexcept { return r; } + forcedinline uint8 getGreen() const noexcept { return g; } + forcedinline uint8 getBlue() const noexcept { return b; } + + forcedinline uint8& getRed() noexcept { return r; } + forcedinline uint8& getGreen() noexcept { return g; } + forcedinline uint8& getBlue() noexcept { return b; } + + /** Blends another pixel onto this one. + + This takes into account the opacity of the pixel being overlaid, and blends + it accordingly. + */ + template + forcedinline void blend (const Pixel& src) noexcept + { + const uint32 alpha = 0x100 - src.getAlpha(); + + uint32 rb = clampPixelComponents (src.getRB() + maskPixelComponents (getRB() * alpha)); + uint32 ag = src.getAG() + (g * alpha >> 8); + + r = (uint8) (rb >> 16); + g = (uint8) clampPixelComponents (ag); + b = (uint8) rb; + } + + forcedinline void blend (const PixelRGB src) noexcept + { + set (src); + } + + /** Blends another pixel onto this one, applying an extra multiplier to its opacity. + + The opacity of the pixel being overlaid is scaled by the extraAlpha factor before + being used, so this can blend semi-transparently from a PixelRGB argument. + */ + template + forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept + { + uint32 ag = maskPixelComponents (extraAlpha * src.getAG()); + const uint32 alpha = 0x100 - (ag >> 16); + ag += g * alpha >> 8; + + uint32 rb = clampPixelComponents (maskPixelComponents (extraAlpha * src.getRB()) + + maskPixelComponents (getRB() * alpha)); + + b = (uint8) rb; + g = (uint8) clampPixelComponents (ag); + r = (uint8) (rb >> 16); + } + + /** Blends another pixel with this one, creating a colour that is somewhere + between the two, as specified by the amount. + */ + template + forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + { + uint32 drb = getRB(); + drb += (((src.getRB() - drb) * amount) >> 8); + + uint32 dag = getAG(); + dag += (((src.getAG() - dag) * amount) >> 8); + + b = (uint8) drb; + g = (uint8) dag; + r = (uint8) (drb >> 16); + } + + /** Copies another pixel colour over this one. + + This doesn't blend it - this colour is simply replaced by the other one. + Because PixelRGB has no alpha channel, any alpha value in the source pixel + is thrown away. + */ + template + forcedinline void set (const Pixel& src) noexcept + { + b = src.getBlue(); + g = src.getGreen(); + r = src.getRed(); + } + + /** This method is included for compatibility with the PixelARGB class. */ + forcedinline void setAlpha (const uint8) noexcept {} + + /** Multiplies the colour's alpha value with another one. */ + forcedinline void multiplyAlpha (int) noexcept {} + + /** Multiplies the colour's alpha value with another one. */ + forcedinline void multiplyAlpha (float) noexcept {} + + /** Sets the pixel's colour from individual components. */ + void setARGB (const uint8, const uint8 red, const uint8 green, const uint8 blue) noexcept + { + r = red; + g = green; + b = blue; + } + + /** Premultiplies the pixel's RGB values by its alpha. */ + forcedinline void premultiply() noexcept {} + + /** Unpremultiplies the pixel's RGB values. */ + forcedinline void unpremultiply() noexcept {} + + forcedinline void desaturate() noexcept + { + r = g = b = (uint8) (((int) r + (int) g + (int) b) / 3); + } + + //============================================================================== + /** The indexes of the different components in the byte layout of this type of colour. */ + #if JUCE_MAC + enum { indexR = 0, indexG = 1, indexB = 2 }; + #else + enum { indexR = 2, indexG = 1, indexB = 0 }; + #endif + +private: + //============================================================================== + #if JUCE_MAC + uint8 r, g, b; + #else + uint8 b, g, r; + #endif + +} +#ifndef DOXYGEN + JUCE_PACKED +#endif +; + +forcedinline void PixelARGB::blend (const PixelRGB src) noexcept +{ + set (src); +} + +//============================================================================== +/** + Represents an 8-bit single-channel pixel, and can perform compositing operations on it. + + This is used internally by the imaging classes. + + @see PixelARGB, PixelRGB +*/ +class JUCE_API PixelAlpha +{ +public: + /** Creates a pixel without defining its colour. */ + PixelAlpha() noexcept {} + ~PixelAlpha() noexcept {} + + /** Creates a pixel from a 32-bit argb value. + + (The argb format is that used by PixelARGB) + */ + PixelAlpha (const uint32 argb) noexcept + { + a = (uint8) (argb >> 24); + } + + forcedinline uint32 getARGB() const noexcept { return (((uint32) a) << 24) | (((uint32) a) << 16) | (((uint32) a) << 8) | a; } + forcedinline uint32 getUnpremultipliedARGB() const noexcept { return (((uint32) a) << 24) | 0xffffff; } + + forcedinline uint32 getRB() const noexcept { return (((uint32) a) << 16) | a; } + forcedinline uint32 getAG() const noexcept { return (((uint32) a) << 16) | a; } + + forcedinline uint8 getAlpha() const noexcept { return a; } + forcedinline uint8& getAlpha() noexcept { return a; } + + forcedinline uint8 getRed() const noexcept { return 0; } + forcedinline uint8 getGreen() const noexcept { return 0; } + forcedinline uint8 getBlue() const noexcept { return 0; } + + /** Blends another pixel onto this one. + + This takes into account the opacity of the pixel being overlaid, and blends + it accordingly. + */ + template + forcedinline void blend (const Pixel& src) noexcept + { + const int srcA = src.getAlpha(); + a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA); + } + + /** Blends another pixel onto this one, applying an extra multiplier to its opacity. + + The opacity of the pixel being overlaid is scaled by the extraAlpha factor before + being used, so this can blend semi-transparently from a PixelRGB argument. + */ + template + forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept + { + ++extraAlpha; + const int srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8); + a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha); + } + + /** Blends another pixel with this one, creating a colour that is somewhere + between the two, as specified by the amount. + */ + template + forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + { + a += ((src.getAlpha() - a) * amount) >> 8; + } + + /** Copies another pixel colour over this one. + + This doesn't blend it - this colour is simply replaced by the other one. + */ + template + forcedinline void set (const Pixel& src) noexcept + { + a = src.getAlpha(); + } + + /** Replaces the colour's alpha value with another one. */ + forcedinline void setAlpha (const uint8 newAlpha) noexcept + { + a = newAlpha; + } + + /** Multiplies the colour's alpha value with another one. */ + forcedinline void multiplyAlpha (int multiplier) noexcept + { + ++multiplier; + a = (uint8) ((a * multiplier) >> 8); + } + + forcedinline void multiplyAlpha (const float multiplier) noexcept + { + a = (uint8) (a * multiplier); + } + + /** Sets the pixel's colour from individual components. */ + forcedinline void setARGB (const uint8 a_, const uint8 /*r*/, const uint8 /*g*/, const uint8 /*b*/) noexcept + { + a = a_; + } + + /** Premultiplies the pixel's RGB values by its alpha. */ + forcedinline void premultiply() noexcept {} + + /** Unpremultiplies the pixel's RGB values. */ + forcedinline void unpremultiply() noexcept {} + + forcedinline void desaturate() noexcept {} + + //============================================================================== + /** The indexes of the different components in the byte layout of this type of colour. */ + enum { indexA = 0 }; + +private: + //============================================================================== + uint8 a; +} +#ifndef DOXYGEN + JUCE_PACKED +#endif +; + +#if JUCE_MSVC + #pragma pack (pop) +#endif + +#endif // JUCE_PIXELFORMATS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp new file mode 100644 index 0000000..a0dcd80 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -0,0 +1,678 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +namespace +{ + template + Rectangle coordsToRectangle (Type x, Type y, Type w, Type h) + { + #if JUCE_DEBUG + const int maxVal = 0x3fffffff; + + jassert ((int) x >= -maxVal && (int) x <= maxVal + && (int) y >= -maxVal && (int) y <= maxVal + && (int) w >= -maxVal && (int) w <= maxVal + && (int) h >= -maxVal && (int) h <= maxVal); + #endif + + return Rectangle (x, y, w, h); + } +} + +//============================================================================== +LowLevelGraphicsContext::LowLevelGraphicsContext() {} +LowLevelGraphicsContext::~LowLevelGraphicsContext() {} + +//============================================================================== +Graphics::Graphics (const Image& imageToDrawOnto) + : context (*imageToDrawOnto.createLowLevelContext()), + contextToDelete (&context), + saveStatePending (false) +{ + jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! +} + +Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept + : context (internalContext), + saveStatePending (false) +{ +} + +Graphics::~Graphics() +{ +} + +//============================================================================== +void Graphics::resetToDefaultState() +{ + saveStateIfPending(); + context.setFill (FillType()); + context.setFont (Font()); + context.setInterpolationQuality (Graphics::mediumResamplingQuality); +} + +bool Graphics::isVectorDevice() const +{ + return context.isVectorDevice(); +} + +bool Graphics::reduceClipRegion (const Rectangle& area) +{ + saveStateIfPending(); + return context.clipToRectangle (area); +} + +bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) +{ + return reduceClipRegion (Rectangle (x, y, w, h)); +} + +bool Graphics::reduceClipRegion (const RectangleList& clipRegion) +{ + saveStateIfPending(); + return context.clipToRectangleList (clipRegion); +} + +bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) +{ + saveStateIfPending(); + context.clipToPath (path, transform); + return ! context.isClipEmpty(); +} + +bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform) +{ + saveStateIfPending(); + context.clipToImageAlpha (image, transform); + return ! context.isClipEmpty(); +} + +void Graphics::excludeClipRegion (const Rectangle& rectangleToExclude) +{ + saveStateIfPending(); + context.excludeClipRectangle (rectangleToExclude); +} + +bool Graphics::isClipEmpty() const +{ + return context.isClipEmpty(); +} + +Rectangle Graphics::getClipBounds() const +{ + return context.getClipBounds(); +} + +void Graphics::saveState() +{ + saveStateIfPending(); + saveStatePending = true; +} + +void Graphics::restoreState() +{ + if (saveStatePending) + saveStatePending = false; + else + context.restoreState(); +} + +void Graphics::saveStateIfPending() +{ + if (saveStatePending) + { + saveStatePending = false; + context.saveState(); + } +} + +void Graphics::setOrigin (Point newOrigin) +{ + saveStateIfPending(); + context.setOrigin (newOrigin); +} + +void Graphics::setOrigin (int x, int y) +{ + setOrigin (Point (x, y)); +} + +void Graphics::addTransform (const AffineTransform& transform) +{ + saveStateIfPending(); + context.addTransform (transform); +} + +bool Graphics::clipRegionIntersects (const Rectangle& area) const +{ + return context.clipRegionIntersects (area); +} + +void Graphics::beginTransparencyLayer (float layerOpacity) +{ + saveStateIfPending(); + context.beginTransparencyLayer (layerOpacity); +} + +void Graphics::endTransparencyLayer() +{ + context.endTransparencyLayer(); +} + +//============================================================================== +void Graphics::setColour (Colour newColour) +{ + saveStateIfPending(); + context.setFill (newColour); +} + +void Graphics::setOpacity (const float newOpacity) +{ + saveStateIfPending(); + context.setOpacity (newOpacity); +} + +void Graphics::setGradientFill (const ColourGradient& gradient) +{ + setFillType (gradient); +} + +void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity) +{ + saveStateIfPending(); + context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY))); + context.setOpacity (opacity); +} + +void Graphics::setFillType (const FillType& newFill) +{ + saveStateIfPending(); + context.setFill (newFill); +} + +//============================================================================== +void Graphics::setFont (const Font& newFont) +{ + saveStateIfPending(); + context.setFont (newFont); +} + +void Graphics::setFont (const float newFontHeight) +{ + setFont (context.getFont().withHeight (newFontHeight)); +} + +Font Graphics::getCurrentFont() const +{ + return context.getFont(); +} + +//============================================================================== +void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, + Justification justification) const +{ + if (text.isNotEmpty()) + { + // Don't pass any vertical placement flags to this method - they'll be ignored. + jassert (justification.getOnlyVerticalFlags() == 0); + + const int flags = justification.getOnlyHorizontalFlags(); + + if (flags == Justification::right) + { + if (startX < context.getClipBounds().getX()) + return; + } + else if (flags == Justification::left) + if (startX > context.getClipBounds().getRight()) + return; + + GlyphArrangement arr; + arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); + + if (flags != Justification::left) + { + float w = arr.getBoundingBox (0, -1, true).getWidth(); + + if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) + w /= 2.0f; + + arr.draw (*this, AffineTransform::translation (-w, 0)); + } + else + { + arr.draw (*this); + } + } +} + +void Graphics::drawMultiLineText (const String& text, const int startX, + const int baselineY, const int maximumLineWidth) const +{ + if (text.isNotEmpty() + && startX < context.getClipBounds().getRight()) + { + GlyphArrangement arr; + arr.addJustifiedText (context.getFont(), text, + (float) startX, (float) baselineY, (float) maximumLineWidth, + Justification::left); + arr.draw (*this); + } +} + +void Graphics::drawText (const String& text, const Rectangle& area, + Justification justificationType, bool useEllipsesIfTooBig) const +{ + if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) + { + GlyphArrangement arr; + arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, + area.getWidth(), useEllipsesIfTooBig); + + arr.justifyGlyphs (0, arr.getNumGlyphs(), + area.getX(), area.getY(), area.getWidth(), area.getHeight(), + justificationType); + arr.draw (*this); + } +} + +void Graphics::drawText (const String& text, const Rectangle& area, + Justification justificationType, bool useEllipsesIfTooBig) const +{ + drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); +} + +void Graphics::drawText (const String& text, const int x, const int y, const int width, const int height, + Justification justificationType, const bool useEllipsesIfTooBig) const +{ + drawText (text, Rectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); +} + +void Graphics::drawFittedText (const String& text, const Rectangle& area, + Justification justification, + const int maximumNumberOfLines, + const float minimumHorizontalScale) const +{ + if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area)) + { + GlyphArrangement arr; + arr.addFittedText (context.getFont(), text, + (float) area.getX(), (float) area.getY(), + (float) area.getWidth(), (float) area.getHeight(), + justification, + maximumNumberOfLines, + minimumHorizontalScale); + + arr.draw (*this); + } +} + +void Graphics::drawFittedText (const String& text, const int x, const int y, const int width, const int height, + Justification justification, + const int maximumNumberOfLines, + const float minimumHorizontalScale) const +{ + drawFittedText (text, coordsToRectangle (x, y, width, height), + justification, maximumNumberOfLines, minimumHorizontalScale); +} + +//============================================================================== +void Graphics::fillRect (const Rectangle& r) const +{ + context.fillRect (r, false); +} + +void Graphics::fillRect (const Rectangle& r) const +{ + context.fillRect (r); +} + +void Graphics::fillRect (int x, int y, int width, int height) const +{ + context.fillRect (coordsToRectangle (x, y, width, height), false); +} + +void Graphics::fillRect (float x, float y, float width, float height) const +{ + fillRect (coordsToRectangle (x, y, width, height)); +} + +void Graphics::fillRectList (const RectangleList& rectangles) const +{ + context.fillRectList (rectangles); +} + +void Graphics::fillRectList (const RectangleList& rects) const +{ + for (const Rectangle* r = rects.begin(), * const e = rects.end(); r != e; ++r) + context.fillRect (*r, false); +} + +void Graphics::setPixel (int x, int y) const +{ + context.fillRect (Rectangle (x, y, 1, 1), false); +} + +void Graphics::fillAll() const +{ + fillRect (context.getClipBounds()); +} + +void Graphics::fillAll (Colour colourToUse) const +{ + if (! colourToUse.isTransparent()) + { + const Rectangle clip (context.getClipBounds()); + + context.saveState(); + context.setFill (colourToUse); + context.fillRect (clip, false); + context.restoreState(); + } +} + + +//============================================================================== +void Graphics::fillPath (const Path& path, const AffineTransform& transform) const +{ + if ((! context.isClipEmpty()) && ! path.isEmpty()) + context.fillPath (path, transform); +} + +void Graphics::strokePath (const Path& path, + const PathStrokeType& strokeType, + const AffineTransform& transform) const +{ + Path stroke; + strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor()); + fillPath (stroke); +} + +//============================================================================== +void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const +{ + drawRect (coordsToRectangle (x, y, width, height), lineThickness); +} + +void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const +{ + drawRect (coordsToRectangle (x, y, width, height), lineThickness); +} + +void Graphics::drawRect (const Rectangle& r, int lineThickness) const +{ + drawRect (r.toFloat(), (float) lineThickness); +} + +void Graphics::drawRect (Rectangle r, const float lineThickness) const +{ + RectangleList rects; + rects.addWithoutMerging (r.removeFromTop (lineThickness)); + rects.addWithoutMerging (r.removeFromBottom (lineThickness)); + rects.addWithoutMerging (r.removeFromLeft (lineThickness)); + rects.addWithoutMerging (r.removeFromRight (lineThickness)); + context.fillRectList (rects); +} + +//============================================================================== +void Graphics::fillEllipse (const Rectangle& area) const +{ + Path p; + p.addEllipse (area); + fillPath (p); +} + +void Graphics::fillEllipse (float x, float y, float w, float h) const +{ + fillEllipse (Rectangle (x, y, w, h)); +} + +void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const +{ + Path p; + p.addEllipse (x, y, width, height); + strokePath (p, PathStrokeType (lineThickness)); +} + +void Graphics::drawEllipse (const Rectangle& area, float lineThickness) const +{ + drawEllipse (area.getX(), area.getY(), area.getWidth(), area.getHeight(), lineThickness); +} + +void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const +{ + fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize); +} + +void Graphics::fillRoundedRectangle (const Rectangle& r, const float cornerSize) const +{ + Path p; + p.addRoundedRectangle (r, cornerSize); + fillPath (p); +} + +void Graphics::drawRoundedRectangle (float x, float y, float width, float height, + float cornerSize, float lineThickness) const +{ + drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness); +} + +void Graphics::drawRoundedRectangle (const Rectangle& r, float cornerSize, float lineThickness) const +{ + Path p; + p.addRoundedRectangle (r, cornerSize); + strokePath (p, PathStrokeType (lineThickness)); +} + +void Graphics::drawArrow (const Line& line, float lineThickness, float arrowheadWidth, float arrowheadLength) const +{ + Path p; + p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); + fillPath (p); +} + +void Graphics::fillCheckerBoard (const Rectangle& area, + const int checkWidth, const int checkHeight, + Colour colour1, Colour colour2) const +{ + jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! + + if (checkWidth > 0 && checkHeight > 0) + { + context.saveState(); + + if (colour1 == colour2) + { + context.setFill (colour1); + context.fillRect (area, false); + } + else + { + const Rectangle clipped (context.getClipBounds().getIntersection (area)); + + if (! clipped.isEmpty()) + { + context.clipToRectangle (clipped); + + const int checkNumX = (clipped.getX() - area.getX()) / checkWidth; + const int checkNumY = (clipped.getY() - area.getY()) / checkHeight; + const int startX = area.getX() + checkNumX * checkWidth; + const int startY = area.getY() + checkNumY * checkHeight; + const int right = clipped.getRight(); + const int bottom = clipped.getBottom(); + + for (int i = 0; i < 2; ++i) + { + context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2); + + int cy = i; + for (int y = startY; y < bottom; y += checkHeight) + for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2) + context.fillRect (Rectangle (x, y, checkWidth, checkHeight), false); + } + } + } + + context.restoreState(); + } +} + +//============================================================================== +void Graphics::drawVerticalLine (const int x, float top, float bottom) const +{ + if (top < bottom) + context.fillRect (Rectangle ((float) x, top, 1.0f, bottom - top)); +} + +void Graphics::drawHorizontalLine (const int y, float left, float right) const +{ + if (left < right) + context.fillRect (Rectangle (left, (float) y, right - left, 1.0f)); +} + +void Graphics::drawLine (const Line& line) const +{ + context.drawLine (line); +} + +void Graphics::drawLine (float x1, float y1, float x2, float y2) const +{ + context.drawLine (Line (x1, y1, x2, y2)); +} + +void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const +{ + drawLine (Line (x1, y1, x2, y2), lineThickness); +} + +void Graphics::drawLine (const Line& line, const float lineThickness) const +{ + Path p; + p.addLineSegment (line, lineThickness); + fillPath (p); +} + +void Graphics::drawDashedLine (const Line& line, const float* const dashLengths, + const int numDashLengths, const float lineThickness, int n) const +{ + jassert (n >= 0 && n < numDashLengths); // your start index must be valid! + + const Point delta ((line.getEnd() - line.getStart()).toDouble()); + const double totalLen = delta.getDistanceFromOrigin(); + + if (totalLen >= 0.1) + { + const double onePixAlpha = 1.0 / totalLen; + + for (double alpha = 0.0; alpha < 1.0;) + { + jassert (dashLengths[n] > 0); // can't have zero-length dashes! + + const double lastAlpha = alpha; + alpha += dashLengths [n] * onePixAlpha; + n = (n + 1) % numDashLengths; + + if ((n & 1) != 0) + { + const Line segment (line.getStart() + (delta * lastAlpha).toFloat(), + line.getStart() + (delta * jmin (1.0, alpha)).toFloat()); + + if (lineThickness != 1.0f) + drawLine (segment, lineThickness); + else + context.drawLine (segment); + } + } + } +} + +//============================================================================== +void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) +{ + saveStateIfPending(); + context.setInterpolationQuality (newQuality); +} + +//============================================================================== +void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const +{ + drawImageTransformed (imageToDraw, + AffineTransform::translation ((float) x, (float) y), + fillAlphaChannel); +} + +void Graphics::drawImageWithin (const Image& imageToDraw, + int dx, int dy, int dw, int dh, + RectanglePlacement placementWithinTarget, + const bool fillAlphaChannelWithCurrentBrush) const +{ + if (imageToDraw.isValid()) + drawImageTransformed (imageToDraw, + placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), + coordsToRectangle (dx, dy, dw, dh).toFloat()), + fillAlphaChannelWithCurrentBrush); +} + +void Graphics::drawImage (const Image& imageToDraw, + int dx, int dy, int dw, int dh, + int sx, int sy, int sw, int sh, + const bool fillAlphaChannelWithCurrentBrush) const +{ + if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh))) + drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)), + AffineTransform::scale (dw / (float) sw, dh / (float) sh) + .translated ((float) dx, (float) dy), + fillAlphaChannelWithCurrentBrush); +} + +void Graphics::drawImageTransformed (const Image& imageToDraw, + const AffineTransform& transform, + const bool fillAlphaChannelWithCurrentBrush) const +{ + if (imageToDraw.isValid() && ! context.isClipEmpty()) + { + if (fillAlphaChannelWithCurrentBrush) + { + context.saveState(); + context.clipToImageAlpha (imageToDraw, transform); + fillAll(); + context.restoreState(); + } + else + { + context.drawImage (imageToDraw, transform); + } + } +} + +//============================================================================== +Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g) +{ + context.saveState(); +} + +Graphics::ScopedSaveState::~ScopedSaveState() +{ + context.restoreState(); +} diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.h b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.h new file mode 100644 index 0000000..ed0bc40 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.h @@ -0,0 +1,730 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_GRAPHICSCONTEXT_H_INCLUDED +#define JUCE_GRAPHICSCONTEXT_H_INCLUDED + + +//============================================================================== +/** + A graphics context, used for drawing a component or image. + + When a Component needs painting, a Graphics context is passed to its + Component::paint() method, and this you then call methods within this + object to actually draw the component's content. + + A Graphics can also be created from an image, to allow drawing directly onto + that image. + + @see Component::paint +*/ +class JUCE_API Graphics +{ +public: + //============================================================================== + /** Creates a Graphics object to draw directly onto the given image. + + The graphics object that is created will be set up to draw onto the image, + with the context's clipping area being the entire size of the image, and its + origin being the image's origin. To draw into a subsection of an image, use the + reduceClipRegion() and setOrigin() methods. + + Obviously you shouldn't delete the image before this context is deleted. + */ + explicit Graphics (const Image& imageToDrawOnto); + + /** Destructor. */ + ~Graphics(); + + //============================================================================== + /** Changes the current drawing colour. + + This sets the colour that will now be used for drawing operations - it also + sets the opacity to that of the colour passed-in. + + If a brush is being used when this method is called, the brush will be deselected, + and any subsequent drawing will be done with a solid colour brush instead. + + @see setOpacity + */ + void setColour (Colour newColour); + + /** Changes the opacity to use with the current colour. + + If a solid colour is being used for drawing, this changes its opacity + to this new value (i.e. it doesn't multiply the colour's opacity by this amount). + + If a gradient is being used, this will have no effect on it. + + A value of 0.0 is completely transparent, 1.0 is completely opaque. + */ + void setOpacity (float newOpacity); + + /** Sets the context to use a gradient for its fill pattern. + */ + void setGradientFill (const ColourGradient& gradient); + + /** Sets the context to use a tiled image pattern for filling. + Make sure that you don't delete this image while it's still being used by + this context! + */ + void setTiledImageFill (const Image& imageToUse, + int anchorX, int anchorY, + float opacity); + + /** Changes the current fill settings. + @see setColour, setGradientFill, setTiledImageFill + */ + void setFillType (const FillType& newFill); + + //============================================================================== + /** Changes the font to use for subsequent text-drawing functions. + + Note there's also a setFont (float, int) method to quickly change the size and + style of the current font. + + @see drawSingleLineText, drawMultiLineText, drawText, drawFittedText + */ + void setFont (const Font& newFont); + + /** Changes the size of the currently-selected font. + This is a convenient shortcut that changes the context's current font to a + different size. The typeface won't be changed. + @see Font + */ + void setFont (float newFontHeight); + + /** Returns the currently selected font. */ + Font getCurrentFont() const; + + /** Draws a one-line text string. + + This will use the current colour (or brush) to fill the text. The font is the last + one specified by setFont(). + + @param text the string to draw + @param startX the position to draw the left-hand edge of the text + @param baselineY the position of the text's baseline + @param justification the horizontal flags indicate which end of the text string is + anchored at the specified point. + @see drawMultiLineText, drawText, drawFittedText, GlyphArrangement::addLineOfText + */ + void drawSingleLineText (const String& text, + int startX, int baselineY, + Justification justification = Justification::left) const; + + /** Draws text across multiple lines. + + This will break the text onto a new line where there's a new-line or + carriage-return character, or at a word-boundary when the text becomes wider + than the size specified by the maximumLineWidth parameter. + + @see setFont, drawSingleLineText, drawFittedText, GlyphArrangement::addJustifiedText + */ + void drawMultiLineText (const String& text, + int startX, int baselineY, + int maximumLineWidth) const; + + /** Draws a line of text within a specified rectangle. + + The text will be positioned within the rectangle based on the justification + flags passed-in. If the string is too long to fit inside the rectangle, it will + either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig + flag is true). + + @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText + */ + void drawText (const String& text, + int x, int y, int width, int height, + Justification justificationType, + bool useEllipsesIfTooBig = true) const; + + /** Draws a line of text within a specified rectangle. + + The text will be positioned within the rectangle based on the justification + flags passed-in. If the string is too long to fit inside the rectangle, it will + either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig + flag is true). + + @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText + */ + void drawText (const String& text, + const Rectangle& area, + Justification justificationType, + bool useEllipsesIfTooBig = true) const; + + /** Draws a line of text within a specified rectangle. + + The text will be positioned within the rectangle based on the justification + flags passed-in. If the string is too long to fit inside the rectangle, it will + either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig + flag is true). + + @see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText + */ + void drawText (const String& text, + const Rectangle& area, + Justification justificationType, + bool useEllipsesIfTooBig = true) const; + + /** Tries to draw a text string inside a given space. + + This does its best to make the given text readable within the specified rectangle, + so it useful for labelling things. + + If the text is too big, it'll be squashed horizontally or broken over multiple lines + if the maximumLinesToUse value allows this. If the text just won't fit into the space, + it'll cram as much as possible in there, and put some ellipsis at the end to show that + it's been truncated. + + A Justification parameter lets you specify how the text is laid out within the rectangle, + both horizontally and vertically. + + The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally + to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you + can set this value to 1.0f. + + @see GlyphArrangement::addFittedText + */ + void drawFittedText (const String& text, + int x, int y, int width, int height, + Justification justificationFlags, + int maximumNumberOfLines, + float minimumHorizontalScale = 0.7f) const; + + /** Tries to draw a text string inside a given space. + + This does its best to make the given text readable within the specified rectangle, + so it useful for labelling things. + + If the text is too big, it'll be squashed horizontally or broken over multiple lines + if the maximumLinesToUse value allows this. If the text just won't fit into the space, + it'll cram as much as possible in there, and put some ellipsis at the end to show that + it's been truncated. + + A Justification parameter lets you specify how the text is laid out within the rectangle, + both horizontally and vertically. + + The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally + to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you + can set this value to 1.0f. + + @see GlyphArrangement::addFittedText + */ + void drawFittedText (const String& text, + const Rectangle& area, + Justification justificationFlags, + int maximumNumberOfLines, + float minimumHorizontalScale = 0.7f) const; + + //============================================================================== + /** Fills the context's entire clip region with the current colour or brush. + + (See also the fillAll (Colour) method which is a quick way of filling + it with a given colour). + */ + void fillAll() const; + + /** Fills the context's entire clip region with a given colour. + + This leaves the context's current colour and brush unchanged, it just + uses the specified colour temporarily. + */ + void fillAll (Colour colourToUse) const; + + //============================================================================== + /** Fills a rectangle with the current colour or brush. + @see drawRect, fillRoundedRectangle + */ + void fillRect (const Rectangle& rectangle) const; + + /** Fills a rectangle with the current colour or brush. + @see drawRect, fillRoundedRectangle + */ + void fillRect (const Rectangle& rectangle) const; + + /** Fills a rectangle with the current colour or brush. + @see drawRect, fillRoundedRectangle + */ + void fillRect (int x, int y, int width, int height) const; + + /** Fills a rectangle with the current colour or brush. + @see drawRect, fillRoundedRectangle + */ + void fillRect (float x, float y, float width, float height) const; + + /** Fills a set of rectangles using the current colour or brush. + If you have a lot of rectangles to draw, it may be more efficient + to create a RectangleList and use this method than to call fillRect() + multiple times. + */ + void fillRectList (const RectangleList& rectangles) const; + + /** Fills a set of rectangles using the current colour or brush. + If you have a lot of rectangles to draw, it may be more efficient + to create a RectangleList and use this method than to call fillRect() + multiple times. + */ + void fillRectList (const RectangleList& rectangles) const; + + /** Uses the current colour or brush to fill a rectangle with rounded corners. + @see drawRoundedRectangle, Path::addRoundedRectangle + */ + void fillRoundedRectangle (float x, float y, float width, float height, + float cornerSize) const; + + /** Uses the current colour or brush to fill a rectangle with rounded corners. + @see drawRoundedRectangle, Path::addRoundedRectangle + */ + void fillRoundedRectangle (const Rectangle& rectangle, + float cornerSize) const; + + /** Fills a rectangle with a checkerboard pattern, alternating between two colours. */ + void fillCheckerBoard (const Rectangle& area, + int checkWidth, int checkHeight, + Colour colour1, Colour colour2) const; + + /** Draws a rectangular outline, using the current colour or brush. + The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. + @see fillRect + */ + void drawRect (int x, int y, int width, int height, int lineThickness = 1) const; + + /** Draws a rectangular outline, using the current colour or brush. + The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. + @see fillRect + */ + void drawRect (float x, float y, float width, float height, float lineThickness = 1.0f) const; + + /** Draws a rectangular outline, using the current colour or brush. + The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. + @see fillRect + */ + void drawRect (const Rectangle& rectangle, int lineThickness = 1) const; + + /** Draws a rectangular outline, using the current colour or brush. + The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. + @see fillRect + */ + void drawRect (Rectangle rectangle, float lineThickness = 1.0f) const; + + /** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. + @see fillRoundedRectangle, Path::addRoundedRectangle + */ + void drawRoundedRectangle (float x, float y, float width, float height, + float cornerSize, float lineThickness) const; + + /** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. + @see fillRoundedRectangle, Path::addRoundedRectangle + */ + void drawRoundedRectangle (const Rectangle& rectangle, + float cornerSize, float lineThickness) const; + + /** Fills a 1x1 pixel using the current colour or brush. + Note that because the context may be transformed, this is effectively the same as + calling fillRect (x, y, 1, 1), and the actual result may involve multiple pixels. + */ + void setPixel (int x, int y) const; + + //============================================================================== + /** Fills an ellipse with the current colour or brush. + The ellipse is drawn to fit inside the given rectangle. + @see drawEllipse, Path::addEllipse + */ + void fillEllipse (float x, float y, float width, float height) const; + + /** Fills an ellipse with the current colour or brush. + The ellipse is drawn to fit inside the given rectangle. + @see drawEllipse, Path::addEllipse + */ + void fillEllipse (const Rectangle& area) const; + + /** Draws an elliptical stroke using the current colour or brush. + @see fillEllipse, Path::addEllipse + */ + void drawEllipse (float x, float y, float width, float height, + float lineThickness) const; + + /** Draws an elliptical stroke using the current colour or brush. + @see fillEllipse, Path::addEllipse + */ + void drawEllipse (const Rectangle& area, float lineThickness) const; + + //============================================================================== + /** Draws a line between two points. + The line is 1 pixel wide and drawn with the current colour or brush. + */ + void drawLine (float startX, float startY, float endX, float endY) const; + + /** Draws a line between two points with a given thickness. + @see Path::addLineSegment + */ + void drawLine (float startX, float startY, float endX, float endY, float lineThickness) const; + + /** Draws a line between two points. + The line is 1 pixel wide and drawn with the current colour or brush. + */ + void drawLine (const Line& line) const; + + /** Draws a line between two points with a given thickness. + @see Path::addLineSegment + */ + void drawLine (const Line& line, float lineThickness) const; + + /** Draws a dashed line using a custom set of dash-lengths. + + @param line the line to draw + @param dashLengths a series of lengths to specify the on/off lengths - e.g. + { 4, 5, 6, 7 } will draw a line of 4 pixels, skip 5 pixels, + draw 6 pixels, skip 7 pixels, and then repeat. + @param numDashLengths the number of elements in the array (this must be an even number). + @param lineThickness the thickness of the line to draw + @param dashIndexToStartFrom the index in the dash-length array to use for the first segment + @see PathStrokeType::createDashedStroke + */ + void drawDashedLine (const Line& line, + const float* dashLengths, int numDashLengths, + float lineThickness = 1.0f, + int dashIndexToStartFrom = 0) const; + + /** Draws a vertical line of pixels at a given x position. + + The x position is an integer, but the top and bottom of the line can be sub-pixel + positions, and these will be anti-aliased if necessary. + + The bottom parameter must be greater than or equal to the top parameter. + */ + void drawVerticalLine (int x, float top, float bottom) const; + + /** Draws a horizontal line of pixels at a given y position. + + The y position is an integer, but the left and right ends of the line can be sub-pixel + positions, and these will be anti-aliased if necessary. + + The right parameter must be greater than or equal to the left parameter. + */ + void drawHorizontalLine (int y, float left, float right) const; + + //============================================================================== + /** Fills a path using the currently selected colour or brush. */ + void fillPath (const Path& path, + const AffineTransform& transform = AffineTransform::identity) const; + + /** Draws a path's outline using the currently selected colour or brush. */ + void strokePath (const Path& path, + const PathStrokeType& strokeType, + const AffineTransform& transform = AffineTransform::identity) const; + + /** Draws a line with an arrowhead at its end. + + @param line the line to draw + @param lineThickness the thickness of the line + @param arrowheadWidth the width of the arrow head (perpendicular to the line) + @param arrowheadLength the length of the arrow head (along the length of the line) + */ + void drawArrow (const Line& line, + float lineThickness, + float arrowheadWidth, + float arrowheadLength) const; + + + //============================================================================== + /** Types of rendering quality that can be specified when drawing images. + + @see blendImage, Graphics::setImageResamplingQuality + */ + enum ResamplingQuality + { + lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */ + mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */ + highResamplingQuality = 2 /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */ + }; + + /** Changes the quality that will be used when resampling images. + By default a Graphics object will be set to mediumRenderingQuality. + @see Graphics::drawImage, Graphics::drawImageTransformed, Graphics::drawImageWithin + */ + void setImageResamplingQuality (const ResamplingQuality newQuality); + + /** Draws an image. + + This will draw the whole of an image, positioning its top-left corner at the + given coordinates, and keeping its size the same. This is the simplest image + drawing method - the others give more control over the scaling and clipping + of the images. + + Images are composited using the context's current opacity, so if you + don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) + (or setColour() with an opaque colour) before drawing images. + */ + void drawImageAt (const Image& imageToDraw, int topLeftX, int topLeftY, + bool fillAlphaChannelWithCurrentBrush = false) const; + + /** Draws part of an image, rescaling it to fit in a given target region. + + The specified area of the source image is rescaled and drawn to fill the + specifed destination rectangle. + + Images are composited using the context's current opacity, so if you + don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) + (or setColour() with an opaque colour) before drawing images. + + @param imageToDraw the image to overlay + @param destX the left of the destination rectangle + @param destY the top of the destination rectangle + @param destWidth the width of the destination rectangle + @param destHeight the height of the destination rectangle + @param sourceX the left of the rectangle to copy from the source image + @param sourceY the top of the rectangle to copy from the source image + @param sourceWidth the width of the rectangle to copy from the source image + @param sourceHeight the height of the rectangle to copy from the source image + @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the source image's pixels, + the source image's alpha channel is used as a mask with + which to fill the destination using the current colour + or brush. (If the source is has no alpha channel, then + it will just fill the target with a solid rectangle) + @see setImageResamplingQuality, drawImageAt, drawImageWithin, fillAlphaMap + */ + void drawImage (const Image& imageToDraw, + int destX, int destY, int destWidth, int destHeight, + int sourceX, int sourceY, int sourceWidth, int sourceHeight, + bool fillAlphaChannelWithCurrentBrush = false) const; + + /** Draws an image, having applied an affine transform to it. + + This lets you throw the image around in some wacky ways, rotate it, shear, + scale it, etc. + + Images are composited using the context's current opacity, so if you + don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) + (or setColour() with an opaque colour) before drawing images. + + If fillAlphaChannelWithCurrentBrush is set to true, then the image's RGB channels + are ignored and it is filled with the current brush, masked by its alpha channel. + + If you want to render only a subsection of an image, use Image::getClippedImage() to + create the section that you need. + + @see setImageResamplingQuality, drawImage + */ + void drawImageTransformed (const Image& imageToDraw, + const AffineTransform& transform, + bool fillAlphaChannelWithCurrentBrush = false) const; + + /** Draws an image to fit within a designated rectangle. + + If the image is too big or too small for the space, it will be rescaled + to fit as nicely as it can do without affecting its aspect ratio. It will + then be placed within the target rectangle according to the justification flags + specified. + + @param imageToDraw the source image to draw + @param destX top-left of the target rectangle to fit it into + @param destY top-left of the target rectangle to fit it into + @param destWidth size of the target rectangle to fit the image into + @param destHeight size of the target rectangle to fit the image into + @param placementWithinTarget this specifies how the image should be positioned + within the target rectangle - see the RectanglePlacement + class for more details about this. + @param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its + alpha channel will be used as a mask with which to + draw with the current brush or colour. This is + similar to fillAlphaMap(), and see also drawImage() + @see setImageResamplingQuality, drawImage, drawImageTransformed, drawImageAt, RectanglePlacement + */ + void drawImageWithin (const Image& imageToDraw, + int destX, int destY, int destWidth, int destHeight, + RectanglePlacement placementWithinTarget, + bool fillAlphaChannelWithCurrentBrush = false) const; + + + //============================================================================== + /** Returns the position of the bounding box for the current clipping region. + @see getClipRegion, clipRegionIntersects + */ + Rectangle getClipBounds() const; + + /** Checks whether a rectangle overlaps the context's clipping region. + + If this returns false, no part of the given area can be drawn onto, so this + method can be used to optimise a component's paint() method, by letting it + avoid drawing complex objects that aren't within the region being repainted. + */ + bool clipRegionIntersects (const Rectangle& area) const; + + /** Intersects the current clipping region with another region. + + @returns true if the resulting clipping region is non-zero in size + @see setOrigin, clipRegionIntersects + */ + bool reduceClipRegion (int x, int y, int width, int height); + + /** Intersects the current clipping region with another region. + + @returns true if the resulting clipping region is non-zero in size + @see setOrigin, clipRegionIntersects + */ + bool reduceClipRegion (const Rectangle& area); + + /** Intersects the current clipping region with a rectangle list region. + + @returns true if the resulting clipping region is non-zero in size + @see setOrigin, clipRegionIntersects + */ + bool reduceClipRegion (const RectangleList& clipRegion); + + /** Intersects the current clipping region with a path. + + @returns true if the resulting clipping region is non-zero in size + @see reduceClipRegion + */ + bool reduceClipRegion (const Path& path, const AffineTransform& transform = AffineTransform::identity); + + /** Intersects the current clipping region with an image's alpha-channel. + + The current clipping path is intersected with the area covered by this image's + alpha-channel, after the image has been transformed by the specified matrix. + + @param image the image whose alpha-channel should be used. If the image doesn't + have an alpha-channel, it is treated as entirely opaque. + @param transform a matrix to apply to the image + @returns true if the resulting clipping region is non-zero in size + @see reduceClipRegion + */ + bool reduceClipRegion (const Image& image, const AffineTransform& transform); + + /** Excludes a rectangle to stop it being drawn into. */ + void excludeClipRegion (const Rectangle& rectangleToExclude); + + /** Returns true if no drawing can be done because the clip region is zero. */ + bool isClipEmpty() const; + + //============================================================================== + /** Saves the current graphics state on an internal stack. + To restore the state, use restoreState(). + @see ScopedSaveState + */ + void saveState(); + + /** Restores a graphics state that was previously saved with saveState(). + @see ScopedSaveState + */ + void restoreState(); + + /** Uses RAII to save and restore the state of a graphics context. + On construction, this calls Graphics::saveState(), and on destruction it calls + Graphics::restoreState() on the Graphics object that you supply. + */ + class ScopedSaveState + { + public: + ScopedSaveState (Graphics&); + ~ScopedSaveState(); + + private: + Graphics& context; + JUCE_DECLARE_NON_COPYABLE (ScopedSaveState) + }; + + //============================================================================== + /** Begins rendering to an off-screen bitmap which will later be flattened onto the current + context with the given opacity. + + The context uses an internal stack of temporary image layers to do this. When you've + finished drawing to the layer, call endTransparencyLayer() to complete the operation and + composite the finished layer. Every call to beginTransparencyLayer() MUST be matched + by a corresponding call to endTransparencyLayer()! + + This call also saves the current state, and endTransparencyLayer() restores it. + */ + void beginTransparencyLayer (float layerOpacity); + + /** Completes a drawing operation to a temporary semi-transparent buffer. + See beginTransparencyLayer() for more details. + */ + void endTransparencyLayer(); + + /** Moves the position of the context's origin. + + This changes the position that the context considers to be (0, 0) to + the specified position. + + So if you call setOrigin with (100, 100), then the position that was previously + referred to as (100, 100) will subsequently be considered to be (0, 0). + + @see reduceClipRegion, addTransform + */ + void setOrigin (Point newOrigin); + + /** Moves the position of the context's origin. + + This changes the position that the context considers to be (0, 0) to + the specified position. + + So if you call setOrigin (100, 100), then the position that was previously + referred to as (100, 100) will subsequently be considered to be (0, 0). + + @see reduceClipRegion, addTransform + */ + void setOrigin (int newOriginX, int newOriginY); + + /** Adds a transformation which will be performed on all the graphics operations that + the context subsequently performs. + + After calling this, all the coordinates that are passed into the context will be + transformed by this matrix. + + @see setOrigin + */ + void addTransform (const AffineTransform& transform); + + /** Resets the current colour, brush, and font to default settings. */ + void resetToDefaultState(); + + /** Returns true if this context is drawing to a vector-based device, such as a printer. */ + bool isVectorDevice() const; + + //============================================================================== + /** Create a graphics that draws with a given low-level renderer. + This method is intended for use only by people who know what they're doing. + Note that the LowLevelGraphicsContext will NOT be deleted by this object. + */ + Graphics (LowLevelGraphicsContext&) noexcept; + + /** @internal */ + LowLevelGraphicsContext& getInternalContext() const noexcept { return context; } + +private: + //============================================================================== + LowLevelGraphicsContext& context; + ScopedPointer contextToDelete; + + bool saveStatePending; + void saveStateIfPending(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics) +}; + + +#endif // JUCE_GRAPHICSCONTEXT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h new file mode 100644 index 0000000..8a0176f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h @@ -0,0 +1,101 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_LOWLEVELGRAPHICSCONTEXT_H_INCLUDED +#define JUCE_LOWLEVELGRAPHICSCONTEXT_H_INCLUDED + + +//============================================================================== +/** + Interface class for graphics context objects, used internally by the Graphics class. + + Users are not supposed to create instances of this class directly - do your drawing + via the Graphics object instead. + + It's a base class for different types of graphics context, that may perform software-based + or OS-accelerated rendering. + + E.g. the LowLevelGraphicsSoftwareRenderer renders onto an image in memory, but other + subclasses could render directly to a windows HDC, a Quartz context, or an OpenGL + context. +*/ +class JUCE_API LowLevelGraphicsContext +{ +protected: + //============================================================================== + LowLevelGraphicsContext(); + +public: + virtual ~LowLevelGraphicsContext(); + + /** Returns true if this device is vector-based, e.g. a printer. */ + virtual bool isVectorDevice() const = 0; + + //============================================================================== + /** Moves the origin to a new position. + + The coordinates are relative to the current origin, and indicate the new position + of (0, 0). + */ + virtual void setOrigin (Point) = 0; + virtual void addTransform (const AffineTransform&) = 0; + virtual float getPhysicalPixelScaleFactor() = 0; + + virtual bool clipToRectangle (const Rectangle&) = 0; + virtual bool clipToRectangleList (const RectangleList&) = 0; + virtual void excludeClipRectangle (const Rectangle&) = 0; + virtual void clipToPath (const Path&, const AffineTransform&) = 0; + virtual void clipToImageAlpha (const Image&, const AffineTransform&) = 0; + + virtual bool clipRegionIntersects (const Rectangle&) = 0; + virtual Rectangle getClipBounds() const = 0; + virtual bool isClipEmpty() const = 0; + + virtual void saveState() = 0; + virtual void restoreState() = 0; + + virtual void beginTransparencyLayer (float opacity) = 0; + virtual void endTransparencyLayer() = 0; + + //============================================================================== + virtual void setFill (const FillType&) = 0; + virtual void setOpacity (float) = 0; + virtual void setInterpolationQuality (Graphics::ResamplingQuality) = 0; + + //============================================================================== + virtual void fillRect (const Rectangle&, bool replaceExistingContents) = 0; + virtual void fillRect (const Rectangle&) = 0; + virtual void fillRectList (const RectangleList&) = 0; + virtual void fillPath (const Path&, const AffineTransform&) = 0; + virtual void drawImage (const Image&, const AffineTransform&) = 0; + virtual void drawLine (const Line&) = 0; + + virtual void setFont (const Font&) = 0; + virtual const Font& getFont() = 0; + virtual void drawGlyph (int glyphNumber, const AffineTransform&) = 0; + virtual bool drawTextLayout (const AttributedString&, const Rectangle&) { return false; } +}; + + +#endif // JUCE_LOWLEVELGRAPHICSCONTEXT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp new file mode 100644 index 0000000..d963ed6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp @@ -0,0 +1,533 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +// this will throw an assertion if you try to draw something that's not +// possible in postscript +#define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 + +//============================================================================== +#if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS + #define notPossibleInPostscriptAssert jassertfalse +#else + #define notPossibleInPostscriptAssert +#endif + +//============================================================================== +LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, + const String& documentTitle, + const int totalWidth_, + const int totalHeight_) + : out (resultingPostScript), + totalWidth (totalWidth_), + totalHeight (totalHeight_), + needToClip (true) +{ + stateStack.add (new SavedState()); + stateStack.getLast()->clip = Rectangle (totalWidth_, totalHeight_); + + const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); + + out << "%!PS-Adobe-3.0 EPSF-3.0" + "\n%%BoundingBox: 0 0 600 824" + "\n%%Pages: 0" + "\n%%Creator: Raw Material Software JUCE" + "\n%%Title: " << documentTitle << + "\n%%CreationDate: none" + "\n%%LanguageLevel: 2" + "\n%%EndComments" + "\n%%BeginProlog" + "\n%%BeginResource: JRes" + "\n/bd {bind def} bind def" + "\n/c {setrgbcolor} bd" + "\n/m {moveto} bd" + "\n/l {lineto} bd" + "\n/rl {rlineto} bd" + "\n/ct {curveto} bd" + "\n/cp {closepath} bd" + "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" + "\n/doclip {initclip newpath} bd" + "\n/endclip {clip newpath} bd" + "\n%%EndResource" + "\n%%EndProlog" + "\n%%BeginSetup" + "\n%%EndSetup" + "\n%%Page: 1 1" + "\n%%BeginPageSetup" + "\n%%EndPageSetup\n\n" + << "40 800 translate\n" + << scale << ' ' << scale << " scale\n\n"; +} + +LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() +{ +} + +//============================================================================== +bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const +{ + return true; +} + +void LowLevelGraphicsPostScriptRenderer::setOrigin (Point o) +{ + if (! o.isOrigin()) + { + stateStack.getLast()->xOffset += o.x; + stateStack.getLast()->yOffset += o.y; + needToClip = true; + } +} + +void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/) +{ + //xxx + jassertfalse; +} + +float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; } + +bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle& r) +{ + needToClip = true; + return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} + +bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList& clipRegion) +{ + needToClip = true; + return stateStack.getLast()->clip.clipTo (clipRegion); +} + +void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle& r) +{ + needToClip = true; + stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} + +void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform) +{ + writeClip(); + + Path p (path); + p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); + writePath (p); + out << "clip\n"; +} + +void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/) +{ + needToClip = true; + jassertfalse; // xxx +} + +bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle& r) +{ + return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); +} + +Rectangle LowLevelGraphicsPostScriptRenderer::getClipBounds() const +{ + return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset, + -stateStack.getLast()->yOffset); +} + +bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const +{ + return stateStack.getLast()->clip.isEmpty(); +} + +//============================================================================== +LowLevelGraphicsPostScriptRenderer::SavedState::SavedState() + : xOffset (0), + yOffset (0) +{ +} + +LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() +{ +} + +void LowLevelGraphicsPostScriptRenderer::saveState() +{ + stateStack.add (new SavedState (*stateStack.getLast())); +} + +void LowLevelGraphicsPostScriptRenderer::restoreState() +{ + jassert (stateStack.size() > 0); + + if (stateStack.size() > 0) + stateStack.removeLast(); +} + +void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float) +{ +} + +void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer() +{ +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::writeClip() +{ + if (needToClip) + { + needToClip = false; + + out << "doclip "; + + int itemsOnLine = 0; + + for (const Rectangle* i = stateStack.getLast()->clip.begin(), * const e = stateStack.getLast()->clip.end(); i != e; ++i) + { + if (++itemsOnLine == 6) + { + itemsOnLine = 0; + out << '\n'; + } + + out << i->getX() << ' ' << -i->getY() << ' ' + << i->getWidth() << ' ' << -i->getHeight() << " pr "; + } + + out << "endclip\n"; + } +} + +void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour) +{ + Colour c (Colours::white.overlaidWith (colour)); + + if (lastColour != c) + { + lastColour = c; + + out << String (c.getFloatRed(), 3) << ' ' + << String (c.getFloatGreen(), 3) << ' ' + << String (c.getFloatBlue(), 3) << " c\n"; + } +} + +void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const +{ + out << String (x, 2) << ' ' + << String (-y, 2) << ' '; +} + +void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const +{ + out << "newpath "; + + float lastX = 0.0f; + float lastY = 0.0f; + int itemsOnLine = 0; + + Path::Iterator i (path); + + while (i.next()) + { + if (++itemsOnLine == 4) + { + itemsOnLine = 0; + out << '\n'; + } + + switch (i.elementType) + { + case Path::Iterator::startNewSubPath: + writeXY (i.x1, i.y1); + lastX = i.x1; + lastY = i.y1; + out << "m "; + break; + + case Path::Iterator::lineTo: + writeXY (i.x1, i.y1); + lastX = i.x1; + lastY = i.y1; + out << "l "; + break; + + case Path::Iterator::quadraticTo: + { + const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; + const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; + const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; + const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; + + writeXY (cp1x, cp1y); + writeXY (cp2x, cp2y); + writeXY (i.x2, i.y2); + out << "ct "; + lastX = i.x2; + lastY = i.y2; + } + break; + + case Path::Iterator::cubicTo: + writeXY (i.x1, i.y1); + writeXY (i.x2, i.y2); + writeXY (i.x3, i.y3); + out << "ct "; + lastX = i.x3; + lastY = i.y3; + break; + + case Path::Iterator::closePath: + out << "cp "; + break; + + default: + jassertfalse; + break; + } + } + + out << '\n'; +} + +void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const +{ + out << "[ " + << trans.mat00 << ' ' + << trans.mat10 << ' ' + << trans.mat01 << ' ' + << trans.mat11 << ' ' + << trans.mat02 << ' ' + << trans.mat12 << " ] concat "; +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType) +{ + stateStack.getLast()->fillType = fillType; +} + +void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/) +{ +} + +void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) +{ +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle& r, const bool /*replaceExistingContents*/) +{ + fillRect (r.toFloat()); +} + +void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle& r) +{ + if (stateStack.getLast()->fillType.isColour()) + { + writeClip(); + writeColour (stateStack.getLast()->fillType.colour); + + Rectangle r2 (r.translated ((float) stateStack.getLast()->xOffset, + (float) stateStack.getLast()->yOffset)); + + out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n"; + } + else + { + Path p; + p.addRectangle (r); + fillPath (p, AffineTransform::identity); + } +} + +void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList& list) +{ + fillPath (list.toPath(), AffineTransform::identity); +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t) +{ + if (stateStack.getLast()->fillType.isColour()) + { + writeClip(); + + Path p (path); + p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, + (float) stateStack.getLast()->yOffset)); + writePath (p); + + writeColour (stateStack.getLast()->fillType.colour); + + out << "fill\n"; + } + else if (stateStack.getLast()->fillType.isGradient()) + { + // this doesn't work correctly yet - it could be improved to handle solid gradients, but + // postscript can't do semi-transparent ones. + notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file + + writeClip(); + out << "gsave "; + + { + Path p (path); + p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); + writePath (p); + out << "clip\n"; + } + + const Rectangle bounds (stateStack.getLast()->clip.getBounds()); + + // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the + // time-being, this just fills it with the average colour.. + writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f)); + out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; + + out << "grestore\n"; + } +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, + const int sx, const int sy, + const int maxW, const int maxH) const +{ + out << "{<\n"; + + const int w = jmin (maxW, im.getWidth()); + const int h = jmin (maxH, im.getHeight()); + + int charsOnLine = 0; + const Image::BitmapData srcData (im, 0, 0, w, h); + Colour pixel; + + for (int y = h; --y >= 0;) + { + for (int x = 0; x < w; ++x) + { + const uint8* pixelData = srcData.getPixelPointer (x, y); + + if (x >= sx && y >= sy) + { + if (im.isARGB()) + { + PixelARGB p (*(const PixelARGB*) pixelData); + p.unpremultiply(); + pixel = Colours::white.overlaidWith (Colour (p.getARGB())); + } + else if (im.isRGB()) + { + pixel = Colour (((const PixelRGB*) pixelData)->getARGB()); + } + else + { + pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); + } + } + else + { + pixel = Colours::transparentWhite; + } + + const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() }; + + out << String::toHexString (pixelValues, 3, 0); + charsOnLine += 3; + + if (charsOnLine > 100) + { + out << '\n'; + charsOnLine = 0; + } + } + } + + out << "\n>}\n"; +} + +void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform) +{ + const int w = sourceImage.getWidth(); + const int h = sourceImage.getHeight(); + + writeClip(); + + out << "gsave "; + writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) + .scaled (1.0f, -1.0f)); + + RectangleList imageClip; + sourceImage.createSolidAreaMask (imageClip, 0.5f); + + out << "newpath "; + int itemsOnLine = 0; + + for (const Rectangle* i = imageClip.begin(), * const e = imageClip.end(); i != e; ++i) + { + if (++itemsOnLine == 6) + { + out << '\n'; + itemsOnLine = 0; + } + + out << i->getX() << ' ' << i->getY() << ' ' << i->getWidth() << ' ' << i->getHeight() << " pr "; + } + + out << " clip newpath\n"; + + out << w << ' ' << h << " scale\n"; + out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; + + writeImage (sourceImage, 0, 0, w, h); + + out << "false 3 colorimage grestore\n"; + needToClip = true; +} + + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) +{ + Path p; + p.addLineSegment (line, 1.0f); + fillPath (p, AffineTransform::identity); +} + +//============================================================================== +void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont) +{ + stateStack.getLast()->font = newFont; +} + +const Font& LowLevelGraphicsPostScriptRenderer::getFont() +{ + return stateStack.getLast()->font; +} + +void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) +{ + Path p; + Font& font = stateStack.getLast()->font; + font.getTypeface()->getOutlineForGlyph (glyphNumber, p); + fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); +} diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h new file mode 100644 index 0000000..2b893a3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h @@ -0,0 +1,121 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_LOWLEVELGRAPHICSPOSTSCRIPTRENDERER_H_INCLUDED +#define JUCE_LOWLEVELGRAPHICSPOSTSCRIPTRENDERER_H_INCLUDED + + +//============================================================================== +/** + An implementation of LowLevelGraphicsContext that turns the drawing operations + into a PostScript document. + +*/ +class JUCE_API LowLevelGraphicsPostScriptRenderer : public LowLevelGraphicsContext +{ +public: + //============================================================================== + LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, + const String& documentTitle, + int totalWidth, + int totalHeight); + + ~LowLevelGraphicsPostScriptRenderer(); + + //============================================================================== + bool isVectorDevice() const override; + void setOrigin (Point) override; + void addTransform (const AffineTransform&) override; + float getPhysicalPixelScaleFactor() override; + + bool clipToRectangle (const Rectangle&) override; + bool clipToRectangleList (const RectangleList&) override; + void excludeClipRectangle (const Rectangle&) override; + void clipToPath (const Path&, const AffineTransform&) override; + void clipToImageAlpha (const Image&, const AffineTransform&) override; + + void saveState() override; + void restoreState() override; + + void beginTransparencyLayer (float) override; + void endTransparencyLayer() override; + + bool clipRegionIntersects (const Rectangle&) override; + Rectangle getClipBounds() const override; + bool isClipEmpty() const override; + + //============================================================================== + void setFill (const FillType&) override; + void setOpacity (float) override; + void setInterpolationQuality (Graphics::ResamplingQuality) override; + + //============================================================================== + void fillRect (const Rectangle&, bool replaceExistingContents) override; + void fillRect (const Rectangle&) override; + void fillRectList (const RectangleList&) override; + void fillPath (const Path&, const AffineTransform&) override; + void drawImage (const Image&, const AffineTransform&) override; + void drawLine (const Line &) override; + + //============================================================================== + const Font& getFont() override; + void setFont (const Font&) override; + void drawGlyph (int glyphNumber, const AffineTransform&) override; + +protected: + //============================================================================== + OutputStream& out; + int totalWidth, totalHeight; + bool needToClip; + Colour lastColour; + + struct SavedState + { + SavedState(); + ~SavedState(); + + RectangleList clip; + int xOffset, yOffset; + FillType fillType; + Font font; + + private: + SavedState& operator= (const SavedState&); + }; + + OwnedArray stateStack; + + void writeClip(); + void writeColour (Colour colour); + void writePath (const Path&) const; + void writeXY (float x, float y) const; + void writeTransform (const AffineTransform&) const; + void writeImage (const Image&, int sx, int sy, int maxW, int maxH) const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsPostScriptRenderer) +}; + + + +#endif // JUCE_LOWLEVELGRAPHICSPOSTSCRIPTRENDERER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp new file mode 100644 index 0000000..a57e4d6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -0,0 +1,38 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image) + : RenderingHelpers::StackBasedLowLevelGraphicsContext + (new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds())) +{ +} + +LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point origin, + const RectangleList& initialClip) + : RenderingHelpers::StackBasedLowLevelGraphicsContext + (new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin)) +{ +} + +LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() {} diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h new file mode 100644 index 0000000..9928092 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h @@ -0,0 +1,56 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_LOWLEVELGRAPHICSSOFTWARERENDERER_H_INCLUDED +#define JUCE_LOWLEVELGRAPHICSSOFTWARERENDERER_H_INCLUDED + + +//============================================================================== +/** + A lowest-common-denominator implementation of LowLevelGraphicsContext that does all + its rendering in memory. + + User code is not supposed to create instances of this class directly - do all your + rendering via the Graphics class instead. +*/ +class JUCE_API LowLevelGraphicsSoftwareRenderer : public RenderingHelpers::StackBasedLowLevelGraphicsContext +{ +public: + //============================================================================== + /** Creates a context to render into an image. */ + LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto); + + /** Creates a context to render into a clipped subsection of an image. */ + LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, Point origin, + const RectangleList& initialClip); + + /** Destructor. */ + ~LowLevelGraphicsSoftwareRenderer(); + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer) +}; + + +#endif // JUCE_LOWLEVELGRAPHICSSOFTWARERENDERER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.cpp b/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.cpp new file mode 100644 index 0000000..448fab8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.cpp @@ -0,0 +1,182 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +static inline void blurDataTriplets (uint8* d, int num, const int delta) noexcept +{ + uint32 last = d[0]; + d[0] = (uint8) ((d[0] + d[delta] + 1) / 3); + d += delta; + + num -= 2; + + do + { + const uint32 newLast = d[0]; + d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3); + d += delta; + last = newLast; + } + while (--num > 0); + + d[0] = (uint8) ((last + d[0] + 1) / 3); +} + +static void blurSingleChannelImage (uint8* const data, const int width, const int height, + const int lineStride, const int repetitions) noexcept +{ + jassert (width > 2 && height > 2); + + for (int y = 0; y < height; ++y) + for (int i = repetitions; --i >= 0;) + blurDataTriplets (data + lineStride * y, width, 1); + + for (int x = 0; x < width; ++x) + for (int i = repetitions; --i >= 0;) + blurDataTriplets (data + x, height, lineStride); +} + +static void blurSingleChannelImage (Image& image, int radius) +{ + const Image::BitmapData bm (image, Image::BitmapData::readWrite); + blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius); +} + +//============================================================================== +DropShadow::DropShadow() noexcept + : colour (0x90000000), radius (4) +{ +} + +DropShadow::DropShadow (Colour shadowColour, const int r, Point o) noexcept + : colour (shadowColour), radius (r), offset (o) +{ + jassert (radius > 0); +} + +void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const +{ + jassert (radius > 0); + + if (srcImage.isValid()) + { + Image shadowImage (srcImage.convertedToFormat (Image::SingleChannel)); + shadowImage.duplicateIfShared(); + + blurSingleChannelImage (shadowImage, radius); + + g.setColour (colour); + g.drawImageAt (shadowImage, offset.x, offset.y, true); + } +} + +void DropShadow::drawForPath (Graphics& g, const Path& path) const +{ + jassert (radius > 0); + + const Rectangle area ((path.getBounds().getSmallestIntegerContainer() + offset) + .expanded (radius + 1) + .getIntersection (g.getClipBounds().expanded (radius + 1))); + + if (area.getWidth() > 2 && area.getHeight() > 2) + { + Image renderedPath (Image::SingleChannel, area.getWidth(), area.getHeight(), true); + + { + Graphics g2 (renderedPath); + g2.setColour (Colours::white); + g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()), + (float) (offset.y - area.getY()))); + } + + blurSingleChannelImage (renderedPath, radius); + + g.setColour (colour); + g.drawImageAt (renderedPath, area.getX(), area.getY(), true); + } +} + +static void drawShadowSection (Graphics& g, ColourGradient& cg, const Rectangle& area, + bool isCorner, float centreX, float centreY, float edgeX, float edgeY) +{ + cg.point1 = area.getRelativePoint (centreX, centreY).toFloat(); + cg.point2 = area.getRelativePoint (edgeX, edgeY).toFloat(); + cg.isRadial = isCorner; + + g.setGradientFill (cg); + g.fillRect (area); +} + +void DropShadow::drawForRectangle (Graphics& g, const Rectangle& targetArea) const +{ + ColourGradient cg (colour, 0, 0, colour.withAlpha (0.0f), 0, 0, false); + + for (float i = 0.05f; i < 1.0f; i += 0.1f) + cg.addColour (1.0 - i, colour.withMultipliedAlpha (i * i)); + + const int radiusInset = (radius + 1) / 2; + const int expandedRadius = radius + radiusInset; + + const Rectangle area (targetArea.reduced (radiusInset) + offset); + + Rectangle r (area.expanded (expandedRadius)); + Rectangle top (r.removeFromTop (expandedRadius)); + Rectangle bottom (r.removeFromBottom (expandedRadius)); + + drawShadowSection (g, cg, top.removeFromLeft (expandedRadius), true, 1.0f, 1.0f, 0, 1.0f); + drawShadowSection (g, cg, top.removeFromRight (expandedRadius), true, 0, 1.0f, 1.0f, 1.0f); + drawShadowSection (g, cg, top, false, 0, 1.0f, 0, 0); + + drawShadowSection (g, cg, bottom.removeFromLeft (expandedRadius), true, 1.0f, 0, 0, 0); + drawShadowSection (g, cg, bottom.removeFromRight (expandedRadius), true, 0, 0, 1.0f, 0); + drawShadowSection (g, cg, bottom, false, 0, 0, 0, 1.0f); + + drawShadowSection (g, cg, r.removeFromLeft (expandedRadius), false, 1.0f, 0, 0, 0); + drawShadowSection (g, cg, r.removeFromRight (expandedRadius), false, 0, 0, 1.0f, 0); + + g.setColour (colour); + g.fillRect (area); +} + +//============================================================================== +DropShadowEffect::DropShadowEffect() {} +DropShadowEffect::~DropShadowEffect() {} + +void DropShadowEffect::setShadowProperties (const DropShadow& newShadow) +{ + shadow = newShadow; +} + +void DropShadowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) +{ + DropShadow s (shadow); + s.radius = roundToInt (s.radius * scaleFactor); + s.colour = s.colour.withMultipliedAlpha (alpha); + s.offset.x = roundToInt (s.offset.x * scaleFactor); + s.offset.y = roundToInt (s.offset.y * scaleFactor); + + s.drawForImage (g, image); + + g.setOpacity (alpha); + g.drawImageAt (image, 0, 0); +} diff --git a/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.h b/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.h new file mode 100644 index 0000000..1f9362a --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/effects/juce_DropShadowEffect.h @@ -0,0 +1,110 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DROPSHADOWEFFECT_H_INCLUDED +#define JUCE_DROPSHADOWEFFECT_H_INCLUDED + + +//============================================================================== +/** + Defines a drop-shadow effect. +*/ +struct JUCE_API DropShadow +{ + /** Creates a default drop-shadow effect. */ + DropShadow() noexcept; + + /** Creates a drop-shadow object with the given parameters. */ + DropShadow (Colour shadowColour, int radius, Point offset) noexcept; + + /** Renders a drop-shadow based on the alpha-channel of the given image. */ + void drawForImage (Graphics& g, const Image& srcImage) const; + + /** Renders a drop-shadow based on the shape of a path. */ + void drawForPath (Graphics& g, const Path& path) const; + + /** Renders a drop-shadow for a rectangle. + Note that for speed, this approximates the shadow using gradients. + */ + void drawForRectangle (Graphics& g, const Rectangle& area) const; + + /** The colour with which to render the shadow. + In most cases you'll probably want to leave this as black with an alpha + value of around 0.5 + */ + Colour colour; + + /** The approximate spread of the shadow. */ + int radius; + + /** The offset of the shadow. */ + Point offset; +}; + +//============================================================================== +/** + An effect filter that adds a drop-shadow behind the image's content. + + (This will only work on images/components that aren't opaque, of course). + + When added to a component, this effect will draw a soft-edged + shadow based on what gets drawn inside it. The shadow will also + be applied to the component's children. + + For speed, this doesn't use a proper gaussian blur, but cheats by + using a simple bilinear filter. If you need a really high-quality + shadow, check out ImageConvolutionKernel::createGaussianBlur() + + @see Component::setComponentEffect +*/ +class JUCE_API DropShadowEffect : public ImageEffectFilter +{ +public: + //============================================================================== + /** Creates a default drop-shadow effect. + To customise the shadow's appearance, use the setShadowProperties() method. + */ + DropShadowEffect(); + + /** Destructor. */ + ~DropShadowEffect(); + + //============================================================================== + /** Sets up parameters affecting the shadow's appearance. */ + void setShadowProperties (const DropShadow& newShadow); + + //============================================================================== + /** @internal */ + void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha); + + +private: + //============================================================================== + DropShadow shadow; + + JUCE_LEAK_DETECTOR (DropShadowEffect) +}; + + +#endif // JUCE_DROPSHADOWEFFECT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.cpp b/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.cpp new file mode 100644 index 0000000..7db7ef5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.cpp @@ -0,0 +1,58 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +GlowEffect::GlowEffect() + : radius (2.0f), + colour (Colours::white) +{ +} + +GlowEffect::~GlowEffect() +{ +} + +void GlowEffect::setGlowProperties (const float newRadius, + Colour newColour) +{ + radius = newRadius; + colour = newColour; +} + +void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) +{ + Image temp (image.getFormat(), image.getWidth(), image.getHeight(), true); + + ImageConvolutionKernel blurKernel (roundToInt (radius * scaleFactor * 2.0f)); + + blurKernel.createGaussianBlur (radius); + blurKernel.rescaleAllValues (radius); + + blurKernel.applyToImage (temp, image, image.getBounds()); + + g.setColour (colour.withMultipliedAlpha (alpha)); + g.drawImageAt (temp, 0, 0, true); + + g.setOpacity (alpha); + g.drawImageAt (image, 0, 0, false); +} diff --git a/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.h b/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.h new file mode 100644 index 0000000..ab9ce3b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/effects/juce_GlowEffect.h @@ -0,0 +1,74 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_GLOWEFFECT_H_INCLUDED +#define JUCE_GLOWEFFECT_H_INCLUDED + + +//============================================================================== +/** + A component effect that adds a coloured blur around the component's contents. + + (This will only work on non-opaque components). + + @see Component::setComponentEffect, DropShadowEffect +*/ +class JUCE_API GlowEffect : public ImageEffectFilter +{ +public: + //============================================================================== + /** Creates a default 'glow' effect. + + To customise its appearance, use the setGlowProperties() method. + */ + GlowEffect(); + + /** Destructor. */ + ~GlowEffect(); + + //============================================================================== + /** Sets the glow's radius and colour. + + The radius is how large the blur should be, and the colour is + used to render it (for a less intense glow, lower the colour's + opacity). + */ + void setGlowProperties (float newRadius, + Colour newColour); + + + //============================================================================== + /** @internal */ + void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha); + +private: + //============================================================================== + float radius; + Colour colour; + + JUCE_LEAK_DETECTOR (GlowEffect) +}; + + +#endif // JUCE_GLOWEFFECT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/effects/juce_ImageEffectFilter.h b/JuceLibraryCode/modules/juce_graphics/effects/juce_ImageEffectFilter.h new file mode 100644 index 0000000..84d6b0b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/effects/juce_ImageEffectFilter.h @@ -0,0 +1,69 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGEEFFECTFILTER_H_INCLUDED +#define JUCE_IMAGEEFFECTFILTER_H_INCLUDED + + +//============================================================================== +/** + A graphical effect filter that can be applied to components. + + An ImageEffectFilter can be applied to the image that a component + paints before it hits the screen. + + This is used for adding effects like shadows, blurs, etc. + + @see Component::setComponentEffect +*/ +class JUCE_API ImageEffectFilter +{ +public: + //============================================================================== + /** Overridden to render the effect. + + The implementation of this method must use the image that is passed in + as its source, and should render its output to the graphics context passed in. + + @param sourceImage the image that the source component has just rendered with + its paint() method. The image may or may not have an alpha + channel, depending on whether the component is opaque. + @param destContext the graphics context to use to draw the resultant image. + @param scaleFactor a scale factor that has been applied to the image - e.g. if + this is 2, then the image is actually scaled-up to twice the + original resolution + @param alpha the alpha with which to draw the resultant image to the + target context + */ + virtual void applyEffect (Image& sourceImage, + Graphics& destContext, + float scaleFactor, + float alpha) = 0; + + /** Destructor. */ + virtual ~ImageEffectFilter() {} + +}; + +#endif // JUCE_IMAGEEFFECTFILTER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp new file mode 100644 index 0000000..43b6318 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.cpp @@ -0,0 +1,232 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +AttributedString::Attribute::Attribute (Range range_, Colour colour_) + : range (range_), colour (new Colour (colour_)) +{ +} + +AttributedString::Attribute::Attribute (Range range_, const Font& font_) + : range (range_), font (new Font (font_)) +{ +} + +AttributedString::Attribute::Attribute (const Attribute& other) + : range (other.range), + font (other.font.createCopy()), + colour (other.colour.createCopy()) +{ +} + +AttributedString::Attribute::Attribute (const Attribute& other, const int offset) + : range (other.range + offset), + font (other.font.createCopy()), + colour (other.colour.createCopy()) +{ +} + +AttributedString::Attribute::~Attribute() {} + +//============================================================================== +AttributedString::AttributedString() + : lineSpacing (0.0f), + justification (Justification::left), + wordWrap (AttributedString::byWord), + readingDirection (AttributedString::natural) +{ +} + +AttributedString::AttributedString (const String& newString) + : text (newString), + lineSpacing (0.0f), + justification (Justification::left), + wordWrap (AttributedString::byWord), + readingDirection (AttributedString::natural) +{ +} + +AttributedString::AttributedString (const AttributedString& other) + : text (other.text), + lineSpacing (other.lineSpacing), + justification (other.justification), + wordWrap (other.wordWrap), + readingDirection (other.readingDirection) +{ + attributes.addCopiesOf (other.attributes); +} + +AttributedString& AttributedString::operator= (const AttributedString& other) +{ + if (this != &other) + { + text = other.text; + lineSpacing = other.lineSpacing; + justification = other.justification; + wordWrap = other.wordWrap; + readingDirection = other.readingDirection; + attributes.clear(); + attributes.addCopiesOf (other.attributes); + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +AttributedString::AttributedString (AttributedString&& other) noexcept + : text (static_cast (other.text)), + lineSpacing (other.lineSpacing), + justification (other.justification), + wordWrap (other.wordWrap), + readingDirection (other.readingDirection), + attributes (static_cast &&> (other.attributes)) +{ +} + +AttributedString& AttributedString::operator= (AttributedString&& other) noexcept +{ + text = static_cast (other.text); + lineSpacing = other.lineSpacing; + justification = other.justification; + wordWrap = other.wordWrap; + readingDirection = other.readingDirection; + attributes = static_cast &&> (other.attributes); + return *this; +} +#endif + +AttributedString::~AttributedString() {} + +void AttributedString::setText (const String& other) +{ + text = other; +} + +void AttributedString::append (const String& textToAppend) +{ + text += textToAppend; +} + +void AttributedString::append (const String& textToAppend, const Font& font) +{ + const int oldLength = text.length(); + const int newLength = textToAppend.length(); + + text += textToAppend; + setFont (Range (oldLength, oldLength + newLength), font); +} + +void AttributedString::append (const String& textToAppend, Colour colour) +{ + const int oldLength = text.length(); + const int newLength = textToAppend.length(); + + text += textToAppend; + setColour (Range (oldLength, oldLength + newLength), colour); +} + +void AttributedString::append (const String& textToAppend, const Font& font, Colour colour) +{ + const int oldLength = text.length(); + const int newLength = textToAppend.length(); + + text += textToAppend; + setFont (Range (oldLength, oldLength + newLength), font); + setColour (Range (oldLength, oldLength + newLength), colour); +} + +void AttributedString::append (const AttributedString& other) +{ + const int originalLength = text.length(); + text += other.text; + + for (int i = 0; i < other.attributes.size(); ++i) + attributes.add (new Attribute (*other.attributes.getUnchecked(i), originalLength)); +} + +void AttributedString::clear() +{ + text.clear(); + attributes.clear(); +} + +void AttributedString::setJustification (Justification newJustification) noexcept +{ + justification = newJustification; +} + +void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept +{ + wordWrap = newWordWrap; +} + +void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept +{ + readingDirection = newReadingDirection; +} + +void AttributedString::setLineSpacing (const float newLineSpacing) noexcept +{ + lineSpacing = newLineSpacing; +} + +void AttributedString::setColour (Range range, Colour colour) +{ + attributes.add (new Attribute (range, colour)); +} + +void AttributedString::setColour (Colour colour) +{ + for (int i = attributes.size(); --i >= 0;) + if (attributes.getUnchecked(i)->getColour() != nullptr) + attributes.remove (i); + + setColour (Range (0, text.length()), colour); +} + +void AttributedString::setFont (Range range, const Font& font) +{ + attributes.add (new Attribute (range, font)); +} + +void AttributedString::setFont (const Font& font) +{ + for (int i = attributes.size(); --i >= 0;) + if (attributes.getUnchecked(i)->getFont() != nullptr) + attributes.remove (i); + + setFont (Range (0, text.length()), font); +} + +void AttributedString::draw (Graphics& g, const Rectangle& area) const +{ + if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer())) + { + if (! g.getInternalContext().drawTextLayout (*this, area)) + { + TextLayout layout; + layout.createLayout (*this, area.getWidth()); + layout.draw (g, area); + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.h new file mode 100644 index 0000000..8c33d75 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_AttributedString.h @@ -0,0 +1,218 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ATTRIBUTEDSTRING_H_INCLUDED +#define JUCE_ATTRIBUTEDSTRING_H_INCLUDED + + +//============================================================================== +/** + A text string with a set of colour/font settings that are associated with sub-ranges + of the text. + + An attributed string lets you create a string with varied fonts, colours, word-wrapping, + layout, etc., and draw it using AttributedString::draw(). + + @see TextLayout +*/ +class JUCE_API AttributedString +{ +public: + /** Creates an empty attributed string. */ + AttributedString(); + + /** Creates an attributed string with the given text. */ + explicit AttributedString (const String& text); + + AttributedString (const AttributedString&); + AttributedString& operator= (const AttributedString&); + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + AttributedString (AttributedString&&) noexcept; + AttributedString& operator= (AttributedString&&) noexcept; + #endif + + /** Destructor. */ + ~AttributedString(); + + //============================================================================== + /** Returns the complete text of this attributed string. */ + const String& getText() const noexcept { return text; } + + /** Replaces all the text. + This will change the text, but won't affect any of the colour or font attributes + that have been added. + */ + void setText (const String& newText); + + /** Appends some text (with a default font and colour). */ + void append (const String& textToAppend); + /** Appends some text, with a specified font, and the default colour (black). */ + void append (const String& textToAppend, const Font& font); + /** Appends some text, with a specified colour, and the default font. */ + void append (const String& textToAppend, Colour colour); + /** Appends some text, with a specified font and colour. */ + void append (const String& textToAppend, const Font& font, Colour colour); + + /** Appends another AttributedString to this one. + Note that this will only append the text, fonts, and colours - it won't copy any + other properties such as justification, line-spacing, etc from the other object. + */ + void append (const AttributedString& other); + + /** Resets the string, clearing all text and attributes. + Note that this won't affect global settings like the justification type, + word-wrap mode, etc. + */ + void clear(); + + //============================================================================== + /** Draws this string within the given area. + The layout of the string within the rectangle is controlled by the justification + value passed to setJustification(). + */ + void draw (Graphics& g, const Rectangle& area) const; + + //============================================================================== + /** Returns the justification that should be used for laying-out the text. + This may include both vertical and horizontal flags. + */ + Justification getJustification() const noexcept { return justification; } + + /** Sets the justification that should be used for laying-out the text. + This may include both vertical and horizontal flags. + */ + void setJustification (Justification newJustification) noexcept; + + //============================================================================== + /** Types of word-wrap behaviour. + @see getWordWrap, setWordWrap + */ + enum WordWrap + { + none, /**< No word-wrapping: lines extend indefinitely. */ + byWord, /**< Lines are wrapped on a word boundary. */ + byChar, /**< Lines are wrapped on a character boundary. */ + }; + + /** Returns the word-wrapping behaviour. */ + WordWrap getWordWrap() const noexcept { return wordWrap; } + + /** Sets the word-wrapping behaviour. */ + void setWordWrap (WordWrap newWordWrap) noexcept; + + //============================================================================== + /** Types of reading direction that can be used. + @see getReadingDirection, setReadingDirection + */ + enum ReadingDirection + { + natural, + leftToRight, + rightToLeft, + }; + + /** Returns the reading direction for the text. */ + ReadingDirection getReadingDirection() const noexcept { return readingDirection; } + + /** Sets the reading direction that should be used for the text. */ + void setReadingDirection (ReadingDirection newReadingDirection) noexcept; + + //============================================================================== + /** Returns the extra line-spacing distance. */ + float getLineSpacing() const noexcept { return lineSpacing; } + + /** Sets an extra line-spacing distance. */ + void setLineSpacing (float newLineSpacing) noexcept; + + //============================================================================== + /** An attribute that has been applied to a range of characters in an AttributedString. */ + class JUCE_API Attribute + { + public: + /** Creates an attribute that changes the colour for a range of characters. + @see AttributedString::setColour() + */ + Attribute (Range range, Colour colour); + + /** Creates an attribute that changes the font for a range of characters. + @see AttributedString::setFont() + */ + Attribute (Range range, const Font& font); + + Attribute (const Attribute&); + ~Attribute(); + + /** If this attribute specifies a font, this returns it; otherwise it returns nullptr. */ + const Font* getFont() const noexcept { return font; } + + /** If this attribute specifies a colour, this returns it; otherwise it returns nullptr. */ + const Colour* getColour() const noexcept { return colour; } + + /** The range of characters to which this attribute will be applied. */ + const Range range; + + private: + ScopedPointer font; + ScopedPointer colour; + + friend class AttributedString; + Attribute (const Attribute&, int); + Attribute& operator= (const Attribute&); + + JUCE_LEAK_DETECTOR (Attribute) + }; + + /** Returns the number of attributes that have been added to this string. */ + int getNumAttributes() const noexcept { return attributes.size(); } + + /** Returns one of the string's attributes. + The index provided must be less than getNumAttributes(), and >= 0. + */ + const Attribute* getAttribute (int index) const noexcept { return attributes.getUnchecked (index); } + + //============================================================================== + /** Adds a colour attribute for the specified range. */ + void setColour (Range range, Colour colour); + + /** Removes all existing colour attributes, and applies this colour to the whole string. */ + void setColour (Colour colour); + + /** Adds a font attribute for the specified range. */ + void setFont (Range range, const Font& font); + + /** Removes all existing font attributes, and applies this font to the whole string. */ + void setFont (const Font& font); + +private: + String text; + float lineSpacing; + Justification justification; + WordWrap wordWrap; + ReadingDirection readingDirection; + OwnedArray attributes; + + JUCE_LEAK_DETECTOR (AttributedString) +}; + +#endif // JUCE_ATTRIBUTEDSTRING_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp new file mode 100644 index 0000000..a2adb04 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp @@ -0,0 +1,406 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class CustomTypeface::GlyphInfo +{ +public: + GlyphInfo (const juce_wchar c, const Path& p, const float w) noexcept + : character (c), path (p), width (w) + { + } + + struct KerningPair + { + juce_wchar character2; + float kerningAmount; + }; + + void addKerningPair (const juce_wchar subsequentCharacter, + const float extraKerningAmount) noexcept + { + KerningPair kp; + kp.character2 = subsequentCharacter; + kp.kerningAmount = extraKerningAmount; + kerningPairs.add (kp); + } + + float getHorizontalSpacing (const juce_wchar subsequentCharacter) const noexcept + { + if (subsequentCharacter != 0) + for (int i = kerningPairs.size(); --i >= 0;) + if (kerningPairs.getReference(i).character2 == subsequentCharacter) + return width + kerningPairs.getReference(i).kerningAmount; + + return width; + } + + const juce_wchar character; + const Path path; + float width; + Array kerningPairs; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphInfo) +}; + +//============================================================================== +namespace CustomTypefaceHelpers +{ + static juce_wchar readChar (InputStream& in) + { + uint32 n = (uint32) (uint16) in.readShort(); + + if (n >= 0xd800 && n <= 0xdfff) + { + const uint32 nextWord = (uint32) (uint16) in.readShort(); + jassert (nextWord >= 0xdc00); // illegal unicode character! + + n = 0x10000 + (((n - 0xd800) << 10) | (nextWord - 0xdc00)); + } + + return (juce_wchar) n; + } + + static void writeChar (OutputStream& out, juce_wchar charToWrite) + { + if (charToWrite >= 0x10000) + { + charToWrite -= 0x10000; + out.writeShort ((short) (uint16) (0xd800 + (charToWrite >> 10))); + out.writeShort ((short) (uint16) (0xdc00 + (charToWrite & 0x3ff))); + } + else + { + out.writeShort ((short) (uint16) charToWrite); + } + } +} + +//============================================================================== +CustomTypeface::CustomTypeface() + : Typeface (String(), String()) +{ + clear(); +} + +CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) + : Typeface (String(), String()) +{ + clear(); + + GZIPDecompressorInputStream gzin (serialisedTypefaceStream); + BufferedInputStream in (gzin, 32768); + + name = in.readString(); + + const bool isBold = in.readBool(); + const bool isItalic = in.readBool(); + style = FontStyleHelpers::getStyleName (isBold, isItalic); + + ascent = in.readFloat(); + defaultCharacter = CustomTypefaceHelpers::readChar (in); + + int numChars = in.readInt(); + + for (int i = 0; i < numChars; ++i) + { + const juce_wchar c = CustomTypefaceHelpers::readChar (in); + const float width = in.readFloat(); + + Path p; + p.loadPathFromStream (in); + addGlyph (c, p, width); + } + + const int numKerningPairs = in.readInt(); + + for (int i = 0; i < numKerningPairs; ++i) + { + const juce_wchar char1 = CustomTypefaceHelpers::readChar (in); + const juce_wchar char2 = CustomTypefaceHelpers::readChar (in); + + addKerningPair (char1, char2, in.readFloat()); + } +} + +CustomTypeface::~CustomTypeface() +{ +} + +//============================================================================== +void CustomTypeface::clear() +{ + defaultCharacter = 0; + ascent = 1.0f; + style = "Regular"; + zeromem (lookupTable, sizeof (lookupTable)); + glyphs.clear(); +} + +void CustomTypeface::setCharacteristics (const String& newName, const float newAscent, const bool isBold, + const bool isItalic, const juce_wchar newDefaultCharacter) noexcept +{ + name = newName; + defaultCharacter = newDefaultCharacter; + ascent = newAscent; + style = FontStyleHelpers::getStyleName (isBold, isItalic); +} + +void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle, const float newAscent, + const juce_wchar newDefaultCharacter) noexcept +{ + name = newName; + style = newStyle; + defaultCharacter = newDefaultCharacter; + ascent = newAscent; +} + +void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) noexcept +{ + // Check that you're not trying to add the same character twice.. + jassert (findGlyph (character, false) == nullptr); + + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable))) + lookupTable [character] = (short) glyphs.size(); + + glyphs.add (new GlyphInfo (character, path, width)); +} + +void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) noexcept +{ + if (extraAmount != 0) + { + if (GlyphInfo* const g = findGlyph (char1, true)) + g->addKerningPair (char2, extraAmount); + else + jassertfalse; // can only add kerning pairs for characters that exist! + } +} + +CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) noexcept +{ + if (isPositiveAndBelow ((int) character, (int) numElementsInArray (lookupTable)) && lookupTable [character] > 0) + return glyphs [(int) lookupTable [(int) character]]; + + for (int i = 0; i < glyphs.size(); ++i) + { + GlyphInfo* const g = glyphs.getUnchecked(i); + if (g->character == character) + return g; + } + + if (loadIfNeeded && loadGlyphIfPossible (character)) + return findGlyph (character, false); + + return nullptr; +} + +bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) +{ + return false; +} + +void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept +{ + setCharacteristics (name, style, typefaceToCopy.getAscent(), defaultCharacter); + + for (int i = 0; i < numCharacters; ++i) + { + const juce_wchar c = (juce_wchar) (characterStartIndex + i); + + Array glyphIndexes; + Array offsets; + typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets); + + const int glyphIndex = glyphIndexes.getFirst(); + + if (glyphIndex >= 0 && glyphIndexes.size() > 0) + { + const float glyphWidth = offsets[1]; + + Path p; + typefaceToCopy.getOutlineForGlyph (glyphIndex, p); + + addGlyph (c, p, glyphWidth); + + for (int j = glyphs.size() - 1; --j >= 0;) + { + const juce_wchar char2 = glyphs.getUnchecked (j)->character; + glyphIndexes.clearQuick(); + offsets.clearQuick(); + typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets); + + if (offsets.size() > 1) + addKerningPair (c, char2, offsets[1] - glyphWidth); + } + } + } +} + +bool CustomTypeface::writeToStream (OutputStream& outputStream) +{ + GZIPCompressorOutputStream out (&outputStream); + + out.writeString (name); + out.writeBool (FontStyleHelpers::isBold (style)); + out.writeBool (FontStyleHelpers::isItalic (style)); + out.writeFloat (ascent); + CustomTypefaceHelpers::writeChar (out, defaultCharacter); + out.writeInt (glyphs.size()); + + int numKerningPairs = 0; + + for (int i = 0; i < glyphs.size(); ++i) + { + const GlyphInfo* const g = glyphs.getUnchecked (i); + CustomTypefaceHelpers::writeChar (out, g->character); + out.writeFloat (g->width); + g->path.writePathToStream (out); + + numKerningPairs += g->kerningPairs.size(); + } + + out.writeInt (numKerningPairs); + + for (int i = 0; i < glyphs.size(); ++i) + { + const GlyphInfo* const g = glyphs.getUnchecked (i); + + for (int j = 0; j < g->kerningPairs.size(); ++j) + { + const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j); + CustomTypefaceHelpers::writeChar (out, g->character); + CustomTypefaceHelpers::writeChar (out, p.character2); + out.writeFloat (p.kerningAmount); + } + } + + return true; +} + +//============================================================================== +float CustomTypeface::getAscent() const { return ascent; } +float CustomTypeface::getDescent() const { return 1.0f - ascent; } +float CustomTypeface::getHeightToPointsFactor() const { return ascent; } + +float CustomTypeface::getStringWidth (const String& text) +{ + float x = 0; + + for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) + { + const juce_wchar c = t.getAndAdvance(); + + if (const GlyphInfo* const glyph = findGlyph (c, true)) + { + x += glyph->getHorizontalSpacing (*t); + } + else + { + const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); + + if (fallbackTypeface != nullptr && fallbackTypeface != this) + x += fallbackTypeface->getStringWidth (String::charToString (c)); + } + } + + return x; +} + +void CustomTypeface::getGlyphPositions (const String& text, Array & resultGlyphs, Array& xOffsets) +{ + xOffsets.add (0); + float x = 0; + + for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) + { + float width = 0.0f; + int glyphChar = 0; + + const juce_wchar c = t.getAndAdvance(); + + if (const GlyphInfo* const glyph = findGlyph (c, true)) + { + width = glyph->getHorizontalSpacing (*t); + glyphChar = (int) glyph->character; + } + else + { + const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); + + if (fallbackTypeface != nullptr && fallbackTypeface != this) + { + Array subGlyphs; + Array subOffsets; + fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets); + + if (subGlyphs.size() > 0) + { + glyphChar = subGlyphs.getFirst(); + width = subOffsets[1]; + } + } + } + + x += width; + resultGlyphs.add (glyphChar); + xOffsets.add (x); + } +} + +bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) +{ + if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) + { + path = glyph->path; + return true; + } + + const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); + + if (fallbackTypeface != nullptr && fallbackTypeface != this) + return fallbackTypeface->getOutlineForGlyph (glyphNumber, path); + + return false; +} + +EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) +{ + if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) + { + if (! glyph->path.isEmpty()) + return new EdgeTable (glyph->path.getBoundsTransformed (transform) + .getSmallestIntegerContainer().expanded (1, 0), + glyph->path, transform); + } + else + { + const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); + + if (fallbackTypeface != nullptr && fallbackTypeface != this) + return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight); + } + + return nullptr; +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.h new file mode 100644 index 0000000..25db68b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.h @@ -0,0 +1,164 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CUSTOMTYPEFACE_H_INCLUDED +#define JUCE_CUSTOMTYPEFACE_H_INCLUDED + + +//============================================================================== +/** + A typeface that can be populated with custom glyphs. + + You can create a CustomTypeface if you need one that contains your own glyphs, + or if you need to load a typeface from a Juce-formatted binary stream. + + If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface() + to copy glyphs into this face. + + NOTE! For most people this class is almost certainly NOT the right tool to use! + If what you want to do is to embed a font into your exe, then your best plan is + probably to embed your TTF/OTF font file into your binary using the Introjucer, + and then call Typeface::createSystemTypefaceFor() to load it from memory. + + @see Typeface, Font +*/ +class JUCE_API CustomTypeface : public Typeface +{ +public: + //============================================================================== + /** Creates a new, empty typeface. */ + CustomTypeface(); + + /** Loads a typeface from a previously saved stream. + The stream must have been created by writeToStream(). + + NOTE! Since this class was written, support was added for loading real font files from + memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font + is more appropriate than using this class to store it in a proprietary format. + + @see writeToStream + */ + explicit CustomTypeface (InputStream& serialisedTypefaceStream); + + /** Destructor. */ + ~CustomTypeface(); + + //============================================================================== + /** Resets this typeface, deleting all its glyphs and settings. */ + void clear(); + + /** Sets the vital statistics for the typeface. + @param fontFamily the typeface's font family + @param ascent the ascent - this is normalised to a height of 1.0 and this is + the value that will be returned by Typeface::getAscent(). The + descent is assumed to be (1.0 - ascent) + @param isBold should be true if the typeface is bold + @param isItalic should be true if the typeface is italic + @param defaultCharacter the character to be used as a replacement if there's + no glyph available for the character that's being drawn + */ + void setCharacteristics (const String& fontFamily, float ascent, + bool isBold, bool isItalic, + juce_wchar defaultCharacter) noexcept; + + /** Sets the vital statistics for the typeface. + @param fontFamily the typeface's font family + @param fontStyle the typeface's font style + @param ascent the ascent - this is normalised to a height of 1.0 and this is + the value that will be returned by Typeface::getAscent(). The + descent is assumed to be (1.0 - ascent) + @param defaultCharacter the character to be used as a replacement if there's + no glyph available for the character that's being drawn + */ + void setCharacteristics (const String& fontFamily, const String& fontStyle, + float ascent, juce_wchar defaultCharacter) noexcept; + + /** Adds a glyph to the typeface. + + The path that is passed in is normalised so that the font height is 1.0, and its + origin is the anchor point of the character on its baseline. + + The width is the nominal width of the character, and any extra kerning values that + are specified will be added to this width. + */ + void addGlyph (juce_wchar character, const Path& path, float width) noexcept; + + /** Specifies an extra kerning amount to be used between a pair of characters. + The amount will be added to the nominal width of the first character when laying out a string. + */ + void addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept; + + /** Adds a range of glyphs from another typeface. + This will attempt to pull in the paths and kerning information from another typeface and + add it to this one. + */ + void addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept; + + /** Saves this typeface as a Juce-formatted font file. + A CustomTypeface can be created to reload the data that is written - see the CustomTypeface + constructor. + + NOTE! Since this class was written, support was added for loading real font files from + memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font + is more appropriate than using this class to store it in a proprietary format. + */ + bool writeToStream (OutputStream& outputStream); + + //============================================================================== + // The following methods implement the basic Typeface behaviour. + float getAscent() const override; + float getDescent() const override; + float getHeightToPointsFactor() const override; + float getStringWidth (const String&) override; + void getGlyphPositions (const String&, Array & glyphs, Array& xOffsets) override; + bool getOutlineForGlyph (int glyphNumber, Path&) override; + EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override; + +protected: + //============================================================================== + juce_wchar defaultCharacter; + float ascent; + + //============================================================================== + /** If a subclass overrides this, it can load glyphs into the font on-demand. + When methods such as getGlyphPositions() or getOutlineForGlyph() are asked for a + particular character and there's no corresponding glyph, they'll call this + method so that a subclass can try to add that glyph, returning true if it + manages to do so. + */ + virtual bool loadGlyphIfPossible (juce_wchar characterNeeded); + +private: + //============================================================================== + class GlyphInfo; + friend struct ContainerDeletePolicy; + OwnedArray glyphs; + short lookupTable [128]; + + GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface) +}; + +#endif // JUCE_CUSTOMTYPEFACE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp new file mode 100644 index 0000000..f0668d1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp @@ -0,0 +1,720 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +namespace FontValues +{ + static float limitFontHeight (const float height) noexcept + { + return jlimit (0.1f, 10000.0f, height); + } + + const float defaultFontHeight = 14.0f; + String fallbackFont; + String fallbackFontStyle; +} + +typedef Typeface::Ptr (*GetTypefaceForFont) (const Font&); +GetTypefaceForFont juce_getTypefaceForFont = nullptr; + +//============================================================================== +class TypefaceCache : private DeletedAtShutdown +{ +public: + TypefaceCache() : counter (0) + { + setSize (10); + } + + ~TypefaceCache() + { + clearSingletonInstance(); + } + + juce_DeclareSingleton (TypefaceCache, false) + + void setSize (const int numToCache) + { + const ScopedWriteLock sl (lock); + + faces.clear(); + faces.insertMultiple (-1, CachedFace(), numToCache); + } + + void clear() + { + const ScopedWriteLock sl (lock); + + setSize (faces.size()); + defaultFace = nullptr; + } + + Typeface::Ptr findTypefaceFor (const Font& font) + { + const ScopedReadLock slr (lock); + + const String faceName (font.getTypefaceName()); + const String faceStyle (font.getTypefaceStyle()); + + jassert (faceName.isNotEmpty()); + + for (int i = faces.size(); --i >= 0;) + { + CachedFace& face = faces.getReference(i); + + if (face.typefaceName == faceName + && face.typefaceStyle == faceStyle + && face.typeface != nullptr + && face.typeface->isSuitableForFont (font)) + { + face.lastUsageCount = ++counter; + return face.typeface; + } + } + + const ScopedWriteLock slw (lock); + int replaceIndex = 0; + size_t bestLastUsageCount = std::numeric_limits::max(); + + for (int i = faces.size(); --i >= 0;) + { + const size_t lu = faces.getReference(i).lastUsageCount; + + if (bestLastUsageCount > lu) + { + bestLastUsageCount = lu; + replaceIndex = i; + } + } + + CachedFace& face = faces.getReference (replaceIndex); + face.typefaceName = faceName; + face.typefaceStyle = faceStyle; + face.lastUsageCount = ++counter; + + if (juce_getTypefaceForFont == nullptr) + face.typeface = Font::getDefaultTypefaceForFont (font); + else + face.typeface = juce_getTypefaceForFont (font); + + jassert (face.typeface != nullptr); // the look and feel must return a typeface! + + if (defaultFace == nullptr && font == Font()) + defaultFace = face.typeface; + + return face.typeface; + } + + Typeface::Ptr defaultFace; + +private: + struct CachedFace + { + CachedFace() noexcept : lastUsageCount (0) {} + + // Although it seems a bit wacky to store the name here, it's because it may be a + // placeholder rather than a real one, e.g. "" vs the actual typeface name. + // Since the typeface itself doesn't know that it may have this alias, the name under + // which it was fetched needs to be stored separately. + String typefaceName, typefaceStyle; + size_t lastUsageCount; + Typeface::Ptr typeface; + }; + + ReadWriteLock lock; + Array faces; + size_t counter; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypefaceCache) +}; + +juce_ImplementSingleton (TypefaceCache) + +void Typeface::setTypefaceCacheSize (int numFontsToCache) +{ + TypefaceCache::getInstance()->setSize (numFontsToCache); +} + +#if JUCE_MODULE_AVAILABLE_juce_opengl +extern void clearOpenGLGlyphCache(); +#endif + +void Typeface::clearTypefaceCache() +{ + TypefaceCache::getInstance()->clear(); + + RenderingHelpers::SoftwareRendererSavedState::clearGlyphCache(); + + #if JUCE_MODULE_AVAILABLE_juce_opengl + clearOpenGLGlyphCache(); + #endif +} + +//============================================================================== +class Font::SharedFontInternal : public ReferenceCountedObject +{ +public: + SharedFontInternal() noexcept + : typeface (TypefaceCache::getInstance()->defaultFace), + typefaceName (Font::getDefaultSansSerifFontName()), + typefaceStyle (Font::getDefaultStyle()), + height (FontValues::defaultFontHeight), + horizontalScale (1.0f), kerning (0), ascent (0), underline (false) + { + } + + SharedFontInternal (int styleFlags, float fontHeight) noexcept + : typefaceName (Font::getDefaultSansSerifFontName()), + typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), + height (fontHeight), + horizontalScale (1.0f), kerning (0), ascent (0), underline ((styleFlags & underlined) != 0) + { + if (styleFlags == plain) + typeface = TypefaceCache::getInstance()->defaultFace; + } + + SharedFontInternal (const String& name, int styleFlags, float fontHeight) noexcept + : typefaceName (name), + typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), + height (fontHeight), + horizontalScale (1.0f), kerning (0), ascent (0), underline ((styleFlags & underlined) != 0) + { + if (styleFlags == plain && typefaceName.isEmpty()) + typeface = TypefaceCache::getInstance()->defaultFace; + } + + SharedFontInternal (const String& name, const String& style, float fontHeight) noexcept + : typefaceName (name), typefaceStyle (style), height (fontHeight), + horizontalScale (1.0f), kerning (0), ascent (0), underline (false) + { + if (typefaceName.isEmpty()) + typefaceName = Font::getDefaultSansSerifFontName(); + } + + explicit SharedFontInternal (const Typeface::Ptr& face) noexcept + : typeface (face), + typefaceName (face->getName()), + typefaceStyle (face->getStyle()), + height (FontValues::defaultFontHeight), + horizontalScale (1.0f), kerning (0), ascent (0), underline (false) + { + jassert (typefaceName.isNotEmpty()); + } + + SharedFontInternal (const SharedFontInternal& other) noexcept + : ReferenceCountedObject(), + typeface (other.typeface), + typefaceName (other.typefaceName), + typefaceStyle (other.typefaceStyle), + height (other.height), + horizontalScale (other.horizontalScale), + kerning (other.kerning), + ascent (other.ascent), + underline (other.underline) + { + } + + bool operator== (const SharedFontInternal& other) const noexcept + { + return height == other.height + && underline == other.underline + && horizontalScale == other.horizontalScale + && kerning == other.kerning + && typefaceName == other.typefaceName + && typefaceStyle == other.typefaceStyle; + } + + Typeface::Ptr typeface; + String typefaceName, typefaceStyle; + float height, horizontalScale, kerning, ascent; + bool underline; +}; + +//============================================================================== +Font::Font() : font (new SharedFontInternal()) {} +Font::Font (const Typeface::Ptr& typeface) : font (new SharedFontInternal (typeface)) {} +Font::Font (const Font& other) noexcept : font (other.font) {} + +Font::Font (float fontHeight, int styleFlags) + : font (new SharedFontInternal (styleFlags, FontValues::limitFontHeight (fontHeight))) +{ +} + +Font::Font (const String& typefaceName, float fontHeight, int styleFlags) + : font (new SharedFontInternal (typefaceName, styleFlags, FontValues::limitFontHeight (fontHeight))) +{ +} + +Font::Font (const String& typefaceName, const String& typefaceStyle, float fontHeight) + : font (new SharedFontInternal (typefaceName, typefaceStyle, FontValues::limitFontHeight (fontHeight))) +{ +} + +Font& Font::operator= (const Font& other) noexcept +{ + font = other.font; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Font::Font (Font&& other) noexcept + : font (static_cast&&> (other.font)) +{ +} + +Font& Font::operator= (Font&& other) noexcept +{ + font = static_cast&&> (other.font); + return *this; +} +#endif + +Font::~Font() noexcept +{ +} + +bool Font::operator== (const Font& other) const noexcept +{ + return font == other.font + || *font == *other.font; +} + +bool Font::operator!= (const Font& other) const noexcept +{ + return ! operator== (other); +} + +void Font::dupeInternalIfShared() +{ + if (font->getReferenceCount() > 1) + font = new SharedFontInternal (*font); +} + +void Font::checkTypefaceSuitability() +{ + if (font->typeface != nullptr && ! font->typeface->isSuitableForFont (*this)) + font->typeface = nullptr; +} + +//============================================================================== +struct FontPlaceholderNames +{ + FontPlaceholderNames() + : sans (""), + serif (""), + mono (""), + regular ("") + { + } + + String sans, serif, mono, regular; +}; + +const FontPlaceholderNames& getFontPlaceholderNames() +{ + static FontPlaceholderNames names; + return names; +} + +#if JUCE_MSVC +// This is a workaround for the lack of thread-safety in MSVC's handling of function-local +// statics - if multiple threads all try to create the first Font object at the same time, +// it can cause a race-condition in creating these placeholder strings. +struct FontNamePreloader { FontNamePreloader() { getFontPlaceholderNames(); } }; +static FontNamePreloader fnp; +#endif + +const String& Font::getDefaultSansSerifFontName() { return getFontPlaceholderNames().sans; } +const String& Font::getDefaultSerifFontName() { return getFontPlaceholderNames().serif; } +const String& Font::getDefaultMonospacedFontName() { return getFontPlaceholderNames().mono; } +const String& Font::getDefaultStyle() { return getFontPlaceholderNames().regular; } + +const String& Font::getTypefaceName() const noexcept { return font->typefaceName; } +const String& Font::getTypefaceStyle() const noexcept { return font->typefaceStyle; } + +void Font::setTypefaceName (const String& faceName) +{ + if (faceName != font->typefaceName) + { + jassert (faceName.isNotEmpty()); + + dupeInternalIfShared(); + font->typefaceName = faceName; + font->typeface = nullptr; + font->ascent = 0; + } +} + +void Font::setTypefaceStyle (const String& typefaceStyle) +{ + if (typefaceStyle != font->typefaceStyle) + { + dupeInternalIfShared(); + font->typefaceStyle = typefaceStyle; + font->typeface = nullptr; + font->ascent = 0; + } +} + +Font Font::withTypefaceStyle (const String& newStyle) const +{ + Font f (*this); + f.setTypefaceStyle (newStyle); + return f; +} + +StringArray Font::getAvailableStyles() const +{ + return findAllTypefaceStyles (getTypeface()->getName()); +} + +Typeface* Font::getTypeface() const +{ + if (font->typeface == nullptr) + { + font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this); + jassert (font->typeface != nullptr); + } + + return font->typeface; +} + +//============================================================================== +const String& Font::getFallbackFontName() +{ + return FontValues::fallbackFont; +} + +void Font::setFallbackFontName (const String& name) +{ + FontValues::fallbackFont = name; + + #if JUCE_MAC || JUCE_IOS + jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. + #endif +} + +const String& Font::getFallbackFontStyle() +{ + return FontValues::fallbackFontStyle; +} + +void Font::setFallbackFontStyle (const String& style) +{ + FontValues::fallbackFontStyle = style; + + #if JUCE_MAC || JUCE_IOS + jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. + #endif +} + +//============================================================================== +Font Font::withHeight (const float newHeight) const +{ + Font f (*this); + f.setHeight (newHeight); + return f; +} + +float Font::getHeightToPointsFactor() const +{ + return getTypeface()->getHeightToPointsFactor(); +} + +Font Font::withPointHeight (float heightInPoints) const +{ + Font f (*this); + f.setHeight (heightInPoints / getHeightToPointsFactor()); + return f; +} + +void Font::setHeight (float newHeight) +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight) + { + dupeInternalIfShared(); + font->height = newHeight; + checkTypefaceSuitability(); + } +} + +void Font::setHeightWithoutChangingWidth (float newHeight) +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight) + { + dupeInternalIfShared(); + font->horizontalScale *= (font->height / newHeight); + font->height = newHeight; + checkTypefaceSuitability(); + } +} + +int Font::getStyleFlags() const noexcept +{ + int styleFlags = font->underline ? underlined : plain; + + if (isBold()) styleFlags |= bold; + if (isItalic()) styleFlags |= italic; + + return styleFlags; +} + +Font Font::withStyle (const int newFlags) const +{ + Font f (*this); + f.setStyleFlags (newFlags); + return f; +} + +void Font::setStyleFlags (const int newFlags) +{ + if (getStyleFlags() != newFlags) + { + dupeInternalIfShared(); + font->typeface = nullptr; + font->typefaceStyle = FontStyleHelpers::getStyleName (newFlags); + font->underline = (newFlags & underlined) != 0; + font->ascent = 0; + } +} + +void Font::setSizeAndStyle (float newHeight, + const int newStyleFlags, + const float newHorizontalScale, + const float newKerningAmount) +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight + || font->horizontalScale != newHorizontalScale + || font->kerning != newKerningAmount) + { + dupeInternalIfShared(); + font->height = newHeight; + font->horizontalScale = newHorizontalScale; + font->kerning = newKerningAmount; + checkTypefaceSuitability(); + } + + setStyleFlags (newStyleFlags); +} + +void Font::setSizeAndStyle (float newHeight, + const String& newStyle, + const float newHorizontalScale, + const float newKerningAmount) +{ + newHeight = FontValues::limitFontHeight (newHeight); + + if (font->height != newHeight + || font->horizontalScale != newHorizontalScale + || font->kerning != newKerningAmount) + { + dupeInternalIfShared(); + font->height = newHeight; + font->horizontalScale = newHorizontalScale; + font->kerning = newKerningAmount; + checkTypefaceSuitability(); + } + + setTypefaceStyle (newStyle); +} + +Font Font::withHorizontalScale (const float newHorizontalScale) const +{ + Font f (*this); + f.setHorizontalScale (newHorizontalScale); + return f; +} + +void Font::setHorizontalScale (const float scaleFactor) +{ + dupeInternalIfShared(); + font->horizontalScale = scaleFactor; + checkTypefaceSuitability(); +} + +float Font::getHorizontalScale() const noexcept +{ + return font->horizontalScale; +} + +float Font::getExtraKerningFactor() const noexcept +{ + return font->kerning; +} + +Font Font::withExtraKerningFactor (const float extraKerning) const +{ + Font f (*this); + f.setExtraKerningFactor (extraKerning); + return f; +} + +void Font::setExtraKerningFactor (const float extraKerning) +{ + dupeInternalIfShared(); + font->kerning = extraKerning; + checkTypefaceSuitability(); +} + +Font Font::boldened() const { return withStyle (getStyleFlags() | bold); } +Font Font::italicised() const { return withStyle (getStyleFlags() | italic); } + +bool Font::isBold() const noexcept { return FontStyleHelpers::isBold (font->typefaceStyle); } +bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->typefaceStyle); } +bool Font::isUnderlined() const noexcept { return font->underline; } + +void Font::setBold (const bool shouldBeBold) +{ + const int flags = getStyleFlags(); + setStyleFlags (shouldBeBold ? (flags | bold) + : (flags & ~bold)); +} + +void Font::setItalic (const bool shouldBeItalic) +{ + const int flags = getStyleFlags(); + setStyleFlags (shouldBeItalic ? (flags | italic) + : (flags & ~italic)); +} + +void Font::setUnderline (const bool shouldBeUnderlined) +{ + dupeInternalIfShared(); + font->underline = shouldBeUnderlined; + checkTypefaceSuitability(); +} + +float Font::getAscent() const +{ + if (font->ascent == 0) + font->ascent = getTypeface()->getAscent(); + + return font->height * font->ascent; +} + +float Font::getHeight() const noexcept { return font->height; } +float Font::getDescent() const { return font->height - getAscent(); } + +float Font::getHeightInPoints() const { return getHeight() * getHeightToPointsFactor(); } +float Font::getAscentInPoints() const { return getAscent() * getHeightToPointsFactor(); } +float Font::getDescentInPoints() const { return getDescent() * getHeightToPointsFactor(); } + +int Font::getStringWidth (const String& text) const +{ + return roundToInt (getStringWidthFloat (text)); +} + +float Font::getStringWidthFloat (const String& text) const +{ + float w = getTypeface()->getStringWidth (text); + + if (font->kerning != 0) + w += font->kerning * text.length(); + + return w * font->height * font->horizontalScale; +} + +void Font::getGlyphPositions (const String& text, Array& glyphs, Array& xOffsets) const +{ + getTypeface()->getGlyphPositions (text, glyphs, xOffsets); + + const int num = xOffsets.size(); + + if (num > 0) + { + const float scale = font->height * font->horizontalScale; + float* const x = xOffsets.getRawDataPointer(); + + if (font->kerning != 0) + { + for (int i = 0; i < num; ++i) + x[i] = (x[i] + i * font->kerning) * scale; + } + else + { + for (int i = 0; i < num; ++i) + x[i] *= scale; + } + } +} + +void Font::findFonts (Array& destArray) +{ + const StringArray names (findAllTypefaceNames()); + + for (int i = 0; i < names.size(); ++i) + { + const StringArray styles (findAllTypefaceStyles (names[i])); + + String style ("Regular"); + + if (! styles.contains (style, true)) + style = styles[0]; + + destArray.add (Font (names[i], style, FontValues::defaultFontHeight)); + } +} + +//============================================================================== +String Font::toString() const +{ + String s; + + if (getTypefaceName() != getDefaultSansSerifFontName()) + s << getTypefaceName() << "; "; + + s << String (getHeight(), 1); + + if (getTypefaceStyle() != getDefaultStyle()) + s << ' ' << getTypefaceStyle(); + + return s; +} + +Font Font::fromString (const String& fontDescription) +{ + const int separator = fontDescription.indexOfChar (';'); + String name; + + if (separator > 0) + name = fontDescription.substring (0, separator).trim(); + + if (name.isEmpty()) + name = getDefaultSansSerifFontName(); + + String sizeAndStyle (fontDescription.substring (separator + 1).trimStart()); + + float height = sizeAndStyle.getFloatValue(); + if (height <= 0) + height = 10.0f; + + const String style (sizeAndStyle.fromFirstOccurrenceOf (" ", false, false)); + + return Font (name, style, height); +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.h new file mode 100644 index 0000000..9888b27 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.h @@ -0,0 +1,460 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_FONT_H_INCLUDED +#define JUCE_FONT_H_INCLUDED + + +//============================================================================== +/** + Represents a particular font, including its size, style, etc. + + Apart from the typeface to be used, a Font object also dictates whether + the font is bold, italic, underlined, how big it is, and its kerning and + horizontal scale factor. + + @see Typeface +*/ +class JUCE_API Font +{ +public: + //============================================================================== + /** A combination of these values is used by the constructor to specify the + style of font to use. + */ + enum FontStyleFlags + { + plain = 0, /**< indicates a plain, non-bold, non-italic version of the font. @see setStyleFlags */ + bold = 1, /**< boldens the font. @see setStyleFlags */ + italic = 2, /**< finds an italic version of the font. @see setStyleFlags */ + underlined = 4 /**< underlines the font. @see setStyleFlags */ + }; + + //============================================================================== + /** Creates a sans-serif font in a given size. + + @param fontHeight the height in pixels (can be fractional) + @param styleFlags the style to use - this can be a combination of the + Font::bold, Font::italic and Font::underlined, or + just Font::plain for the normal style. + @see FontStyleFlags, getDefaultSansSerifFontName + */ + Font (float fontHeight, int styleFlags = plain); + + /** Creates a font with a given typeface and parameters. + + @param typefaceName the font family of the typeface to use + @param fontHeight the height in pixels (can be fractional) + @param styleFlags the style to use - this can be a combination of the + Font::bold, Font::italic and Font::underlined, or + just Font::plain for the normal style. + @see FontStyleFlags, getDefaultSansSerifFontName + */ + Font (const String& typefaceName, float fontHeight, int styleFlags); + + /** Creates a font with a given typeface and parameters. + + @param typefaceName the font family of the typeface to use + @param typefaceStyle the font style of the typeface to use + @param fontHeight the height in pixels (can be fractional) + */ + Font (const String& typefaceName, const String& typefaceStyle, float fontHeight); + + /** Creates a copy of another Font object. */ + Font (const Font& other) noexcept; + + /** Creates a font for a typeface. */ + Font (const Typeface::Ptr& typeface); + + /** Creates a basic sans-serif font at a default height. + + You should use one of the other constructors for creating a font that you're planning + on drawing with - this constructor is here to help initialise objects before changing + the font's settings later. + */ + Font(); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Font (Font&& other) noexcept; + Font& operator= (Font&& other) noexcept; + #endif + + /** Copies this font from another one. */ + Font& operator= (const Font& other) noexcept; + + bool operator== (const Font& other) const noexcept; + bool operator!= (const Font& other) const noexcept; + + /** Destructor. */ + ~Font() noexcept; + + //============================================================================== + /** Changes the font family of the typeface. + + e.g. "Arial", "Courier", etc. + + This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), + or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, + but are generic font family names that are used to represent the various default fonts. + If you need to know the exact typeface font family being used, you can call + Font::getTypeface()->getName(), which will give you the platform-specific font family. + + If a suitable font isn't found on the machine, it'll just use a default instead. + */ + void setTypefaceName (const String& faceName); + + /** Returns the font family of the typeface that this font uses. + + e.g. "Arial", "Courier", etc. + + This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), + or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, + but are generic font familiy names that are used to represent the various default fonts. + + If you need to know the exact typeface font family being used, you can call + Font::getTypeface()->getName(), which will give you the platform-specific font family. + */ + const String& getTypefaceName() const noexcept; + + //============================================================================== + /** Returns the font style of the typeface that this font uses. + @see withTypefaceStyle, getAvailableStyles() + */ + const String& getTypefaceStyle() const noexcept; + + /** Changes the font style of the typeface. + @see getAvailableStyles() + */ + void setTypefaceStyle (const String& newStyle); + + /** Returns a copy of this font with a new typeface style. + @see getAvailableStyles() + */ + Font withTypefaceStyle (const String& newStyle) const; + + /** Returns a list of the styles that this font can use. */ + StringArray getAvailableStyles() const; + + //============================================================================== + /** Returns a typeface font family that represents the default sans-serif font. + + This is also the typeface that will be used when a font is created without + specifying any typeface details. + + Note that this method just returns a generic placeholder string that means "the default + sans-serif font" - it's not the actual font family of this font. + + @see setTypefaceName, getDefaultSerifFontName, getDefaultMonospacedFontName + */ + static const String& getDefaultSansSerifFontName(); + + /** Returns a typeface font family that represents the default serif font. + + Note that this method just returns a generic placeholder string that means "the default + serif font" - it's not the actual font family of this font. + + @see setTypefaceName, getDefaultSansSerifFontName, getDefaultMonospacedFontName + */ + static const String& getDefaultSerifFontName(); + + /** Returns a typeface font family that represents the default monospaced font. + + Note that this method just returns a generic placeholder string that means "the default + monospaced font" - it's not the actual font family of this font. + + @see setTypefaceName, getDefaultSansSerifFontName, getDefaultSerifFontName + */ + static const String& getDefaultMonospacedFontName(); + + /** Returns a font style name that represents the default style. + + Note that this method just returns a generic placeholder string that means "the default + font style" - it's not the actual name of the font style of any particular font. + + @see setTypefaceStyle + */ + static const String& getDefaultStyle(); + + /** Returns the default system typeface for the given font. */ + static Typeface::Ptr getDefaultTypefaceForFont (const Font& font); + + //============================================================================== + /** Returns a copy of this font with a new height. */ + Font withHeight (float height) const; + + /** Returns a copy of this font with a new height, specified in points. */ + Font withPointHeight (float heightInPoints) const; + + /** Changes the font's height. + @see getHeight, withHeight, setHeightWithoutChangingWidth + */ + void setHeight (float newHeight); + + /** Changes the font's height without changing its width. + This alters the horizontal scale to compensate for the change in height. + */ + void setHeightWithoutChangingWidth (float newHeight); + + /** Returns the total height of this font, in pixels. + This is the maximum height, from the top of the ascent to the bottom of the + descenders. + + @see withHeight, setHeightWithoutChangingWidth, getAscent + */ + float getHeight() const noexcept; + + /** Returns the total height of this font, in points. + This is the maximum height, from the top of the ascent to the bottom of the + descenders. + + @see withPointHeight, getHeight + */ + float getHeightInPoints() const; + + /** Returns the height of the font above its baseline, in pixels. + This is the maximum height from the baseline to the top. + @see getHeight, getDescent + */ + float getAscent() const; + + /** Returns the height of the font above its baseline, in points. + This is the maximum height from the baseline to the top. + @see getHeight, getDescent + */ + float getAscentInPoints() const; + + /** Returns the amount that the font descends below its baseline, in pixels. + This is calculated as (getHeight() - getAscent()). + @see getAscent, getHeight + */ + float getDescent() const; + + /** Returns the amount that the font descends below its baseline, in points. + This is calculated as (getHeight() - getAscent()). + @see getAscent, getHeight + */ + float getDescentInPoints() const; + + //============================================================================== + /** Returns the font's style flags. + This will return a bitwise-or'ed combination of values from the FontStyleFlags + enum, to describe whether the font is bold, italic, etc. + @see FontStyleFlags, withStyle + */ + int getStyleFlags() const noexcept; + + /** Returns a copy of this font with the given set of style flags. + @param styleFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. + @see FontStyleFlags, getStyleFlags + */ + Font withStyle (int styleFlags) const; + + /** Changes the font's style. + @param newFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. + @see FontStyleFlags, withStyle + */ + void setStyleFlags (int newFlags); + + //============================================================================== + /** Makes the font bold or non-bold. */ + void setBold (bool shouldBeBold); + /** Returns a copy of this font with the bold attribute set. */ + Font boldened() const; + /** Returns true if the font is bold. */ + bool isBold() const noexcept; + + /** Makes the font italic or non-italic. */ + void setItalic (bool shouldBeItalic); + /** Returns a copy of this font with the italic attribute set. */ + Font italicised() const; + /** Returns true if the font is italic. */ + bool isItalic() const noexcept; + + /** Makes the font underlined or non-underlined. */ + void setUnderline (bool shouldBeUnderlined); + /** Returns true if the font is underlined. */ + bool isUnderlined() const noexcept; + + + //============================================================================== + /** Returns the font's horizontal scale. + A value of 1.0 is the normal scale, less than this will be narrower, greater + than 1.0 will be stretched out. + + @see withHorizontalScale + */ + float getHorizontalScale() const noexcept; + + /** Returns a copy of this font with a new horizontal scale. + @param scaleFactor a value of 1.0 is the normal scale, less than this will be + narrower, greater than 1.0 will be stretched out. + @see getHorizontalScale + */ + Font withHorizontalScale (float scaleFactor) const; + + /** Changes the font's horizontal scale factor. + @param scaleFactor a value of 1.0 is the normal scale, less than this will be + narrower, greater than 1.0 will be stretched out. + */ + void setHorizontalScale (float scaleFactor); + + /** Returns the font's kerning. + + This is the extra space added between adjacent characters, as a proportion + of the font's height. + + A value of zero is normal spacing, positive values will spread the letters + out more, and negative values make them closer together. + */ + float getExtraKerningFactor() const noexcept; + + /** Returns a copy of this font with a new kerning factor. + @param extraKerning a multiple of the font's height that will be added + to space between the characters. So a value of zero is + normal spacing, positive values spread the letters out, + negative values make them closer together. + */ + Font withExtraKerningFactor (float extraKerning) const; + + /** Changes the font's kerning. + @param extraKerning a multiple of the font's height that will be added + to space between the characters. So a value of zero is + normal spacing, positive values spread the letters out, + negative values make them closer together. + */ + void setExtraKerningFactor (float extraKerning); + + //============================================================================== + /** Changes all the font's characteristics with one call. */ + void setSizeAndStyle (float newHeight, + int newStyleFlags, + float newHorizontalScale, + float newKerningAmount); + + /** Changes all the font's characteristics with one call. */ + void setSizeAndStyle (float newHeight, + const String& newStyle, + float newHorizontalScale, + float newKerningAmount); + + //============================================================================== + /** Returns the total width of a string as it would be drawn using this font. + For a more accurate floating-point result, use getStringWidthFloat(). + */ + int getStringWidth (const String& text) const; + + /** Returns the total width of a string as it would be drawn using this font. + @see getStringWidth + */ + float getStringWidthFloat (const String& text) const; + + /** Returns the series of glyph numbers and their x offsets needed to represent a string. + + An extra x offset is added at the end of the run, to indicate where the right hand + edge of the last character is. + */ + void getGlyphPositions (const String& text, Array & glyphs, Array & xOffsets) const; + + //============================================================================== + /** Returns the typeface used by this font. + + Note that the object returned may go out of scope if this font is deleted + or has its style changed. + */ + Typeface* getTypeface() const; + + /** Creates an array of Font objects to represent all the fonts on the system. + + If you just need the font family names of the typefaces, you can also use + findAllTypefaceNames() instead. + + @param results the array to which new Font objects will be added. + */ + static void findFonts (Array& results); + + /** Returns a list of all the available typeface font families. + + The names returned can be passed into setTypefaceName(). + + You can use this instead of findFonts() if you only need their font family names, + and not font objects. + */ + static StringArray findAllTypefaceNames(); + + /** Returns a list of all the available typeface font styles. + + The names returned can be passed into setTypefaceStyle(). + + You can use this instead of findFonts() if you only need their styles, and not + font objects. + */ + static StringArray findAllTypefaceStyles (const String& family); + + //============================================================================== + /** Returns the font family of the typeface to be used for rendering glyphs that aren't + found in the requested typeface. + */ + static const String& getFallbackFontName(); + + /** Sets the (platform-specific) font family of the typeface to use to find glyphs that + aren't available in whatever font you're trying to use. + */ + static void setFallbackFontName (const String& name); + + /** Returns the font style of the typeface to be used for rendering glyphs that aren't + found in the requested typeface. + */ + static const String& getFallbackFontStyle(); + + /** Sets the (platform-specific) font style of the typeface to use to find glyphs that + aren't available in whatever font you're trying to use. + */ + static void setFallbackFontStyle (const String& style); + + //============================================================================== + /** Creates a string to describe this font. + The string will contain information to describe the font's typeface, size, and + style. To recreate the font from this string, use fromString(). + */ + String toString() const; + + /** Recreates a font from its stringified encoding. + This method takes a string that was created by toString(), and recreates the + original font. + */ + static Font fromString (const String& fontDescription); + + +private: + //============================================================================== + class SharedFontInternal; + ReferenceCountedObjectPtr font; + void dupeInternalIfShared(); + void checkTypefaceSuitability(); + float getHeightToPointsFactor() const; + + JUCE_LEAK_DETECTOR (Font) +}; + +#endif // JUCE_FONT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp new file mode 100644 index 0000000..8ad3b7b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp @@ -0,0 +1,798 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +PositionedGlyph::PositionedGlyph() noexcept + : character (0), glyph (0), x (0), y (0), w (0), whitespace (false) +{ +} + +PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_, + const float x_, const float y_, const float w_, const bool whitespace_) + : font (font_), character (character_), glyph (glyph_), + x (x_), y (y_), w (w_), whitespace (whitespace_) +{ +} + +PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) + : font (other.font), character (other.character), glyph (other.glyph), + x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) +{ +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept + : font (static_cast (other.font)), + character (other.character), glyph (other.glyph), + x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) +{ +} + +PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept +{ + font = static_cast (other.font); + character = other.character; + glyph = other.glyph; + x = other.x; + y = other.y; + w = other.w; + whitespace = other.whitespace; + return *this; +} +#endif + +PositionedGlyph::~PositionedGlyph() {} + +PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other) +{ + font = other.font; + character = other.character; + glyph = other.glyph; + x = other.x; + y = other.y; + w = other.w; + whitespace = other.whitespace; + return *this; +} + +static inline void drawGlyphWithFont (const Graphics& g, int glyph, const Font& font, const AffineTransform& t) +{ + LowLevelGraphicsContext& context = g.getInternalContext(); + context.setFont (font); + context.drawGlyph (glyph, t); +} + +void PositionedGlyph::draw (const Graphics& g) const +{ + if (! isWhitespace()) + drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y)); +} + +void PositionedGlyph::draw (const Graphics& g, const AffineTransform& transform) const +{ + if (! isWhitespace()) + drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform)); +} + +void PositionedGlyph::createPath (Path& path) const +{ + if (! isWhitespace()) + { + if (Typeface* const t = font.getTypeface()) + { + Path p; + t->getOutlineForGlyph (glyph, p); + + path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) + .translated (x, y)); + } + } +} + +bool PositionedGlyph::hitTest (float px, float py) const +{ + if (getBounds().contains (px, py) && ! isWhitespace()) + { + if (Typeface* const t = font.getTypeface()) + { + Path p; + t->getOutlineForGlyph (glyph, p); + + AffineTransform::translation (-x, -y) + .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight()) + .transformPoint (px, py); + + return p.contains (px, py); + } + } + + return false; +} + +void PositionedGlyph::moveBy (const float deltaX, + const float deltaY) +{ + x += deltaX; + y += deltaY; +} + + +//============================================================================== +GlyphArrangement::GlyphArrangement() +{ + glyphs.ensureStorageAllocated (128); +} + +GlyphArrangement::GlyphArrangement (const GlyphArrangement& other) + : glyphs (other.glyphs) +{ +} + +GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other) +{ + glyphs = other.glyphs; + return *this; +} + +GlyphArrangement::~GlyphArrangement() +{ +} + +//============================================================================== +void GlyphArrangement::clear() +{ + glyphs.clear(); +} + +PositionedGlyph& GlyphArrangement::getGlyph (const int index) const noexcept +{ + return glyphs.getReference (index); +} + +//============================================================================== +void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other) +{ + glyphs.addArray (other.glyphs); +} + +void GlyphArrangement::addGlyph (const PositionedGlyph& glyph) +{ + glyphs.add (glyph); +} + +void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num) +{ + glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num); +} + +//============================================================================== +void GlyphArrangement::addLineOfText (const Font& font, + const String& text, + const float xOffset, + const float yOffset) +{ + addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false); +} + +void GlyphArrangement::addCurtailedLineOfText (const Font& font, + const String& text, + const float xOffset, + const float yOffset, + const float maxWidthPixels, + const bool useEllipsis) +{ + if (text.isNotEmpty()) + { + Array newGlyphs; + Array xOffsets; + font.getGlyphPositions (text, newGlyphs, xOffsets); + const int textLen = newGlyphs.size(); + glyphs.ensureStorageAllocated (glyphs.size() + textLen); + + String::CharPointerType t (text.getCharPointer()); + + for (int i = 0; i < textLen; ++i) + { + const float nextX = xOffsets.getUnchecked (i + 1); + + if (nextX > maxWidthPixels + 1.0f) + { + // curtail the string if it's too wide.. + if (useEllipsis && textLen > 3 && glyphs.size() >= 3) + insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size()); + + break; + } + else + { + const float thisX = xOffsets.getUnchecked (i); + const bool isWhitespace = t.isWhitespace(); + + glyphs.add (PositionedGlyph (font, t.getAndAdvance(), + newGlyphs.getUnchecked(i), + xOffset + thisX, yOffset, + nextX - thisX, isWhitespace)); + } + } + } +} + +int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos, + const int startIndex, int endIndex) +{ + int numDeleted = 0; + + if (glyphs.size() > 0) + { + Array dotGlyphs; + Array dotXs; + font.getGlyphPositions ("..", dotGlyphs, dotXs); + + const float dx = dotXs[1]; + float xOffset = 0.0f, yOffset = 0.0f; + + while (endIndex > startIndex) + { + const PositionedGlyph& pg = glyphs.getReference (--endIndex); + xOffset = pg.x; + yOffset = pg.y; + + glyphs.remove (endIndex); + ++numDeleted; + + if (xOffset + dx * 3 <= maxXPos) + break; + } + + for (int i = 3; --i >= 0;) + { + glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(), + xOffset, yOffset, dx, false)); + --numDeleted; + xOffset += dx; + + if (xOffset > maxXPos) + break; + } + } + + return numDeleted; +} + +void GlyphArrangement::addJustifiedText (const Font& font, + const String& text, + float x, float y, + const float maxLineWidth, + Justification horizontalLayout) +{ + int lineStartIndex = glyphs.size(); + addLineOfText (font, text, x, y); + + const float originalY = y; + + while (lineStartIndex < glyphs.size()) + { + int i = lineStartIndex; + + if (glyphs.getReference(i).getCharacter() != '\n' + && glyphs.getReference(i).getCharacter() != '\r') + ++i; + + const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth; + int lastWordBreakIndex = -1; + + while (i < glyphs.size()) + { + const PositionedGlyph& pg = glyphs.getReference (i); + const juce_wchar c = pg.getCharacter(); + + if (c == '\r' || c == '\n') + { + ++i; + + if (c == '\r' && i < glyphs.size() + && glyphs.getReference(i).getCharacter() == '\n') + ++i; + + break; + } + else if (pg.isWhitespace()) + { + lastWordBreakIndex = i + 1; + } + else if (pg.getRight() - 0.0001f >= lineMaxX) + { + if (lastWordBreakIndex >= 0) + i = lastWordBreakIndex; + + break; + } + + ++i; + } + + const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft(); + float currentLineEndX = currentLineStartX; + + for (int j = i; --j >= lineStartIndex;) + { + if (! glyphs.getReference (j).isWhitespace()) + { + currentLineEndX = glyphs.getReference (j).getRight(); + break; + } + } + + float deltaX = 0.0f; + + if (horizontalLayout.testFlags (Justification::horizontallyJustified)) + spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth); + else if (horizontalLayout.testFlags (Justification::horizontallyCentred)) + deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f; + else if (horizontalLayout.testFlags (Justification::right)) + deltaX = maxLineWidth - (currentLineEndX - currentLineStartX); + + moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, + x + deltaX - currentLineStartX, y - originalY); + + lineStartIndex = i; + + y += font.getHeight(); + } +} + +void GlyphArrangement::addFittedText (const Font& f, + const String& text, + const float x, const float y, + const float width, const float height, + Justification layout, + int maximumLines, + const float minimumHorizontalScale) +{ + // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 + jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); + + if (text.containsAnyOf ("\r\n")) + { + addLinesWithLineBreaks (text, f, x, y, width, height, layout); + } + else + { + const int startIndex = glyphs.size(); + const String trimmed (text.trim()); + addLineOfText (f, trimmed, x, y); + const int numGlyphs = glyphs.size() - startIndex; + + if (numGlyphs > 0) + { + const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() + - glyphs.getReference (startIndex).getLeft(); + + if (lineWidth > 0) + { + if (lineWidth * minimumHorizontalScale < width) + { + if (lineWidth > width) + stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth); + + justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout); + } + else if (maximumLines <= 1) + { + fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height, + f, layout, minimumHorizontalScale); + } + else + { + splitLines (trimmed, f, startIndex, x, y, width, height, + maximumLines, lineWidth, layout, minimumHorizontalScale); + } + } + } + } +} + +//============================================================================== +void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy) +{ + jassert (startIndex >= 0); + + if (dx != 0.0f || dy != 0.0f) + { + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; + + while (--num >= 0) + glyphs.getReference (startIndex++).moveBy (dx, dy); + } +} + +void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f, + float x, float y, float width, float height, Justification layout) +{ + GlyphArrangement ga; + ga.addJustifiedText (f, text, x, y, width, layout); + + const Rectangle bb (ga.getBoundingBox (0, -1, false)); + + float dy = y - bb.getY(); + + if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; + else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight()); + + ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); + + glyphs.addArray (ga.glyphs); +} + +int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, + Justification justification, float minimumHorizontalScale) +{ + int numDeleted = 0; + const float lineStartX = glyphs.getReference (start).getLeft(); + float lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX; + + if (lineWidth > w) + { + if (minimumHorizontalScale < 1.0f) + { + stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth)); + lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f; + } + + if (lineWidth > w) + { + numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs); + numGlyphs -= numDeleted; + } + } + + justifyGlyphs (start, numGlyphs, x, y, w, h, justification); + return numDeleted; +} + +void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, + const float horizontalScaleFactor) +{ + jassert (startIndex >= 0); + + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; + + if (num > 0) + { + const float xAnchor = glyphs.getReference (startIndex).getLeft(); + + while (--num >= 0) + { + PositionedGlyph& pg = glyphs.getReference (startIndex++); + + pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor; + pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor); + pg.w *= horizontalScaleFactor; + } + } +} + +Rectangle GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const +{ + jassert (startIndex >= 0); + + if (num < 0 || startIndex + num > glyphs.size()) + num = glyphs.size() - startIndex; + + Rectangle result; + + while (--num >= 0) + { + const PositionedGlyph& pg = glyphs.getReference (startIndex++); + + if (includeWhitespace || ! pg.isWhitespace()) + result = result.getUnion (pg.getBounds()); + } + + return result; +} + +void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, + const float x, const float y, const float width, const float height, + Justification justification) +{ + jassert (num >= 0 && startIndex >= 0); + + if (glyphs.size() > 0 && num > 0) + { + const Rectangle bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified + | Justification::horizontallyCentred))); + float deltaX = 0.0f, deltaY = 0.0f; + + if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX(); + else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX(); + else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight(); + else deltaX = x - bb.getX(); + + if (justification.testFlags (Justification::top)) deltaY = y - bb.getY(); + else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom(); + else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY(); + + moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); + + if (justification.testFlags (Justification::horizontallyJustified)) + { + int lineStart = 0; + float baseY = glyphs.getReference (startIndex).getBaselineY(); + + int i; + for (i = 0; i < num; ++i) + { + const float glyphY = glyphs.getReference (startIndex + i).getBaselineY(); + + if (glyphY != baseY) + { + spreadOutLine (startIndex + lineStart, i - lineStart, width); + + lineStart = i; + baseY = glyphY; + } + } + + if (i > lineStart) + spreadOutLine (startIndex + lineStart, i - lineStart, width); + } + } +} + +void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth) +{ + if (start + num < glyphs.size() + && glyphs.getReference (start + num - 1).getCharacter() != '\r' + && glyphs.getReference (start + num - 1).getCharacter() != '\n') + { + int numSpaces = 0; + int spacesAtEnd = 0; + + for (int i = 0; i < num; ++i) + { + if (glyphs.getReference (start + i).isWhitespace()) + { + ++spacesAtEnd; + ++numSpaces; + } + else + { + spacesAtEnd = 0; + } + } + + numSpaces -= spacesAtEnd; + + if (numSpaces > 0) + { + const float startX = glyphs.getReference (start).getLeft(); + const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight(); + + const float extraPaddingBetweenWords + = (targetWidth - (endX - startX)) / (float) numSpaces; + + float deltaX = 0.0f; + + for (int i = 0; i < num; ++i) + { + glyphs.getReference (start + i).moveBy (deltaX, 0.0f); + + if (glyphs.getReference (start + i).isWhitespace()) + deltaX += extraPaddingBetweenWords; + } + } + } +} + + +void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, + float x, float y, float width, float height, int maximumLines, + float lineWidth, Justification layout, float minimumHorizontalScale) +{ + const int length = text.length(); + const int originalStartIndex = startIndex; + int numLines = 1; + + if (length <= 12 && ! text.containsAnyOf (" -\t\r\n")) + maximumLines = 1; + + maximumLines = jmin (maximumLines, length); + + while (numLines < maximumLines) + { + ++numLines; + + const float newFontHeight = height / (float) numLines; + + if (newFontHeight < font.getHeight()) + { + font.setHeight (jmax (8.0f, newFontHeight)); + + removeRangeOfGlyphs (startIndex, -1); + addLineOfText (font, text, x, y); + + lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() + - glyphs.getReference (startIndex).getLeft(); + } + + // Try to estimate the point at which there are enough lines to fit the text, + // allowing for unevenness in the lengths due to differently sized words. + const float lineLengthUnevennessAllowance = 80.0f; + + if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f) + break; + } + + if (numLines < 1) + numLines = 1; + + float lineY = y; + float widthPerLine = lineWidth / numLines; + + for (int line = 0; line < numLines; ++line) + { + int i = startIndex; + float lineStartX = glyphs.getReference (startIndex).getLeft(); + + if (line == numLines - 1) + { + widthPerLine = width; + i = glyphs.size(); + } + else + { + while (i < glyphs.size()) + { + lineWidth = (glyphs.getReference (i).getRight() - lineStartX); + + if (lineWidth > widthPerLine) + { + // got to a point where the line's too long, so skip forward to find a + // good place to break it.. + const int searchStartIndex = i; + + while (i < glyphs.size()) + { + if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width) + { + if (glyphs.getReference (i).isWhitespace() + || glyphs.getReference (i).getCharacter() == '-') + { + ++i; + break; + } + } + else + { + // can't find a suitable break, so try looking backwards.. + i = searchStartIndex; + + for (int back = 1; back < jmin (7, i - startIndex - 1); ++back) + { + if (glyphs.getReference (i - back).isWhitespace() + || glyphs.getReference (i - back).getCharacter() == '-') + { + i -= back - 1; + break; + } + } + + break; + } + + ++i; + } + + break; + } + + ++i; + } + + int wsStart = i; + while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace()) + --wsStart; + + int wsEnd = i; + while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace()) + ++wsEnd; + + removeRangeOfGlyphs (wsStart, wsEnd - wsStart); + i = jmax (wsStart, startIndex + 1); + } + + i -= fitLineIntoSpace (startIndex, i - startIndex, + x, lineY, width, font.getHeight(), font, + layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, + minimumHorizontalScale); + + startIndex = i; + lineY += font.getHeight(); + + if (startIndex >= glyphs.size()) + break; + } + + justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, + x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); +} + +//============================================================================== +void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, + const int i, const AffineTransform& transform) const +{ + const float lineThickness = (pg.font.getDescent()) * 0.3f; + + float nextX = pg.x + pg.w; + + if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y) + nextX = glyphs.getReference (i + 1).x; + + Path p; + p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness); + g.fillPath (p, transform); +} + +void GlyphArrangement::draw (const Graphics& g) const +{ + for (int i = 0; i < glyphs.size(); ++i) + { + const PositionedGlyph& pg = glyphs.getReference(i); + + if (pg.font.isUnderlined()) + drawGlyphUnderline (g, pg, i, AffineTransform::identity); + + pg.draw (g); + } +} + +void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const +{ + for (int i = 0; i < glyphs.size(); ++i) + { + const PositionedGlyph& pg = glyphs.getReference(i); + + if (pg.font.isUnderlined()) + drawGlyphUnderline (g, pg, i, transform); + + pg.draw (g, transform); + } +} + +void GlyphArrangement::createPath (Path& path) const +{ + for (int i = 0; i < glyphs.size(); ++i) + glyphs.getReference (i).createPath (path); +} + +int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const +{ + for (int i = 0; i < glyphs.size(); ++i) + if (glyphs.getReference (i).hitTest (x, y)) + return i; + + return -1; +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.h new file mode 100644 index 0000000..ad27a57 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.h @@ -0,0 +1,319 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_GLYPHARRANGEMENT_H_INCLUDED +#define JUCE_GLYPHARRANGEMENT_H_INCLUDED + + +//============================================================================== +/** + A glyph from a particular font, with a particular size, style, + typeface and position. + + You should rarely need to use this class directly - for most purposes, the + GlyphArrangement class will do what you need for text layout. + + @see GlyphArrangement, Font +*/ +class JUCE_API PositionedGlyph +{ +public: + //============================================================================== + PositionedGlyph() noexcept; + PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber, + float anchorX, float baselineY, float width, bool isWhitespace); + + PositionedGlyph (const PositionedGlyph&); + PositionedGlyph& operator= (const PositionedGlyph&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + PositionedGlyph (PositionedGlyph&&) noexcept; + PositionedGlyph& operator= (PositionedGlyph&&) noexcept; + #endif + + ~PositionedGlyph(); + + /** Returns the character the glyph represents. */ + juce_wchar getCharacter() const noexcept { return character; } + /** Checks whether the glyph is actually empty. */ + bool isWhitespace() const noexcept { return whitespace; } + + /** Returns the position of the glyph's left-hand edge. */ + float getLeft() const noexcept { return x; } + /** Returns the position of the glyph's right-hand edge. */ + float getRight() const noexcept { return x + w; } + /** Returns the y position of the glyph's baseline. */ + float getBaselineY() const noexcept { return y; } + /** Returns the y position of the top of the glyph. */ + float getTop() const { return y - font.getAscent(); } + /** Returns the y position of the bottom of the glyph. */ + float getBottom() const { return y + font.getDescent(); } + /** Returns the bounds of the glyph. */ + Rectangle getBounds() const { return Rectangle (x, getTop(), w, font.getHeight()); } + + //============================================================================== + /** Shifts the glyph's position by a relative amount. */ + void moveBy (float deltaX, float deltaY); + + //============================================================================== + /** Draws the glyph into a graphics context. */ + void draw (const Graphics& g) const; + + /** Draws the glyph into a graphics context, with an extra transform applied to it. */ + void draw (const Graphics& g, const AffineTransform& transform) const; + + /** Returns the path for this glyph. + + @param path the glyph's outline will be appended to this path + */ + void createPath (Path& path) const; + + /** Checks to see if a point lies within this glyph. */ + bool hitTest (float x, float y) const; + +private: + //============================================================================== + friend class GlyphArrangement; + Font font; + juce_wchar character; + int glyph; + float x, y, w; + bool whitespace; + + JUCE_LEAK_DETECTOR (PositionedGlyph) +}; + + +//============================================================================== +/** + A set of glyphs, each with a position. + + You can create a GlyphArrangement, text to it and then draw it onto a + graphics context. It's used internally by the text methods in the + Graphics class, but can be used directly if more control is needed. + + @see Font, PositionedGlyph +*/ +class JUCE_API GlyphArrangement +{ +public: + //============================================================================== + /** Creates an empty arrangement. */ + GlyphArrangement(); + + /** Takes a copy of another arrangement. */ + GlyphArrangement (const GlyphArrangement&); + + /** Copies another arrangement onto this one. + To add another arrangement without clearing this one, use addGlyphArrangement(). + */ + GlyphArrangement& operator= (const GlyphArrangement&); + + /** Destructor. */ + ~GlyphArrangement(); + + //============================================================================== + /** Returns the total number of glyphs in the arrangement. */ + int getNumGlyphs() const noexcept { return glyphs.size(); } + + /** Returns one of the glyphs from the arrangement. + + @param index the glyph's index, from 0 to (getNumGlyphs() - 1). Be + careful not to pass an out-of-range index here, as it + doesn't do any bounds-checking. + */ + PositionedGlyph& getGlyph (int index) const noexcept; + + //============================================================================== + /** Clears all text from the arrangement and resets it. */ + void clear(); + + /** Appends a line of text to the arrangement. + + This will add the text as a single line, where x is the left-hand edge of the + first character, and y is the position for the text's baseline. + + If the text contains new-lines or carriage-returns, this will ignore them - use + addJustifiedText() to add multi-line arrangements. + */ + void addLineOfText (const Font& font, + const String& text, + float x, float y); + + /** Adds a line of text, truncating it if it's wider than a specified size. + + This is the same as addLineOfText(), but if the line's width exceeds the value + specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."), + if useEllipsis is true, or if this is false, it will just drop any subsequent characters. + */ + void addCurtailedLineOfText (const Font& font, + const String& text, + float x, float y, + float maxWidthPixels, + bool useEllipsis); + + /** Adds some multi-line text, breaking lines at word-boundaries if they are too wide. + + This will add text to the arrangement, breaking it into new lines either where there + is a new-line or carriage-return character in the text, or where a line's width + exceeds the value set in maxLineWidth. + + Each line that is added will be laid out using the flags set in horizontalLayout, so + the lines can be left- or right-justified, or centred horizontally in the space + between x and (x + maxLineWidth). + + The y coordinate is the position of the baseline of the first line of text - subsequent + lines will be placed below it, separated by a distance of font.getHeight(). + */ + void addJustifiedText (const Font& font, + const String& text, + float x, float y, + float maxLineWidth, + Justification horizontalLayout); + + /** Tries to fit some text withing a given space. + + This does its best to make the given text readable within the specified rectangle, + so it useful for labelling things. + + If the text is too big, it'll be squashed horizontally or broken over multiple lines + if the maximumLinesToUse value allows this. If the text just won't fit into the space, + it'll cram as much as possible in there, and put some ellipsis at the end to show that + it's been truncated. + + A Justification parameter lets you specify how the text is laid out within the rectangle, + both horizontally and vertically. + + @see Graphics::drawFittedText + */ + void addFittedText (const Font& font, + const String& text, + float x, float y, float width, float height, + Justification layout, + int maximumLinesToUse, + float minimumHorizontalScale = 0.7f); + + /** Appends another glyph arrangement to this one. */ + void addGlyphArrangement (const GlyphArrangement&); + + /** Appends a custom glyph to the arrangement. */ + void addGlyph (const PositionedGlyph&); + + //============================================================================== + /** Draws this glyph arrangement to a graphics context. + + This uses cached bitmaps so is much faster than the draw (Graphics&, const AffineTransform&) + method, which renders the glyphs as filled vectors. + */ + void draw (const Graphics&) const; + + /** Draws this glyph arrangement to a graphics context. + + This renders the paths as filled vectors, so is far slower than the draw (Graphics&) + method for non-transformed arrangements. + */ + void draw (const Graphics&, const AffineTransform&) const; + + /** Converts the set of glyphs into a path. + @param path the glyphs' outlines will be appended to this path + */ + void createPath (Path& path) const; + + /** Looks for a glyph that contains the given coordinate. + @returns the index of the glyph, or -1 if none were found. + */ + int findGlyphIndexAt (float x, float y) const; + + //============================================================================== + /** Finds the smallest rectangle that will enclose a subset of the glyphs. + + + @param startIndex the first glyph to test + @param numGlyphs the number of glyphs to include; if this is < 0, all glyphs after + startIndex will be included + @param includeWhitespace if true, the extent of any whitespace characters will also + be taken into account + */ + Rectangle getBoundingBox (int startIndex, int numGlyphs, bool includeWhitespace) const; + + /** Shifts a set of glyphs by a given amount. + + @param startIndex the first glyph to transform + @param numGlyphs the number of glyphs to move; if this is < 0, all glyphs after + startIndex will be used + @param deltaX the amount to add to their x-positions + @param deltaY the amount to add to their y-positions + */ + void moveRangeOfGlyphs (int startIndex, int numGlyphs, + float deltaX, float deltaY); + + /** Removes a set of glyphs from the arrangement. + + @param startIndex the first glyph to remove + @param numGlyphs the number of glyphs to remove; if this is < 0, all glyphs after + startIndex will be deleted + */ + void removeRangeOfGlyphs (int startIndex, int numGlyphs); + + /** Expands or compresses a set of glyphs horizontally. + + @param startIndex the first glyph to transform + @param numGlyphs the number of glyphs to stretch; if this is < 0, all glyphs after + startIndex will be used + @param horizontalScaleFactor how much to scale their horizontal width by + */ + void stretchRangeOfGlyphs (int startIndex, int numGlyphs, + float horizontalScaleFactor); + + /** Justifies a set of glyphs within a given space. + + This moves the glyphs as a block so that the whole thing is located within the + given rectangle with the specified layout. + + If the Justification::horizontallyJustified flag is specified, each line will + be stretched out to fill the specified width. + */ + void justifyGlyphs (int startIndex, int numGlyphs, + float x, float y, float width, float height, + Justification justification); + + +private: + //============================================================================== + Array glyphs; + + int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex); + int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&, + Justification, float minimumHorizontalScale); + void spreadOutLine (int start, int numGlyphs, float targetWidth); + void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines, + float lineWidth, Justification, float minimumHorizontalScale); + void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification); + void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, const AffineTransform&) const; + + JUCE_LEAK_DETECTOR (GlyphArrangement) +}; + + +#endif // JUCE_GLYPHARRANGEMENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp new file mode 100644 index 0000000..526cac1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -0,0 +1,620 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +TextLayout::Glyph::Glyph (const int glyphCode_, Point anchor_, float width_) noexcept + : glyphCode (glyphCode_), anchor (anchor_), width (width_) +{ +} + +TextLayout::Glyph::Glyph (const Glyph& other) noexcept + : glyphCode (other.glyphCode), anchor (other.anchor), width (other.width) +{ +} + +TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept +{ + glyphCode = other.glyphCode; + anchor = other.anchor; + width = other.width; + return *this; +} + +TextLayout::Glyph::~Glyph() noexcept {} + +//============================================================================== +TextLayout::Run::Run() noexcept + : colour (0xff000000) +{ +} + +TextLayout::Run::Run (Range range, const int numGlyphsToPreallocate) + : colour (0xff000000), stringRange (range) +{ + glyphs.ensureStorageAllocated (numGlyphsToPreallocate); +} + +TextLayout::Run::Run (const Run& other) + : font (other.font), + colour (other.colour), + glyphs (other.glyphs), + stringRange (other.stringRange) +{ +} + +TextLayout::Run::~Run() noexcept {} + +//============================================================================== +TextLayout::Line::Line() noexcept + : ascent (0.0f), descent (0.0f), leading (0.0f) +{ +} + +TextLayout::Line::Line (Range stringRange_, Point lineOrigin_, + const float ascent_, const float descent_, const float leading_, + const int numRunsToPreallocate) + : stringRange (stringRange_), lineOrigin (lineOrigin_), + ascent (ascent_), descent (descent_), leading (leading_) +{ + runs.ensureStorageAllocated (numRunsToPreallocate); +} + +TextLayout::Line::Line (const Line& other) + : stringRange (other.stringRange), lineOrigin (other.lineOrigin), + ascent (other.ascent), descent (other.descent), leading (other.leading) +{ + runs.addCopiesOf (other.runs); +} + +TextLayout::Line::~Line() noexcept +{ +} + +Range TextLayout::Line::getLineBoundsX() const noexcept +{ + Range range; + bool isFirst = true; + + for (int i = runs.size(); --i >= 0;) + { + const Run& run = *runs.getUnchecked(i); + + if (run.glyphs.size() > 0) + { + float minX = run.glyphs.getReference(0).anchor.x; + float maxX = minX; + + for (int j = run.glyphs.size(); --j >= 0;) + { + const Glyph& glyph = run.glyphs.getReference (j); + const float x = glyph.anchor.x; + minX = jmin (minX, x); + maxX = jmax (maxX, x + glyph.width); + } + + if (isFirst) + { + isFirst = false; + range = Range (minX, maxX); + } + else + { + range = range.getUnionWith (Range (minX, maxX)); + } + } + } + + return range + lineOrigin.x; +} + +//============================================================================== +TextLayout::TextLayout() + : width (0), justification (Justification::topLeft) +{ +} + +TextLayout::TextLayout (const TextLayout& other) + : width (other.width), + justification (other.justification) +{ + lines.addCopiesOf (other.lines); +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +TextLayout::TextLayout (TextLayout&& other) noexcept + : lines (static_cast &&> (other.lines)), + width (other.width), + justification (other.justification) +{ +} + +TextLayout& TextLayout::operator= (TextLayout&& other) noexcept +{ + lines = static_cast &&> (other.lines); + width = other.width; + justification = other.justification; + return *this; +} +#endif + +TextLayout& TextLayout::operator= (const TextLayout& other) +{ + width = other.width; + justification = other.justification; + lines.clear(); + lines.addCopiesOf (other.lines); + return *this; +} + +TextLayout::~TextLayout() +{ +} + +float TextLayout::getHeight() const noexcept +{ + if (const Line* const lastLine = lines.getLast()) + return lastLine->lineOrigin.y + lastLine->descent; + + return 0.0f; +} + +TextLayout::Line& TextLayout::getLine (const int index) const +{ + return *lines[index]; +} + +void TextLayout::ensureStorageAllocated (int numLinesNeeded) +{ + lines.ensureStorageAllocated (numLinesNeeded); +} + +void TextLayout::addLine (Line* line) +{ + lines.add (line); +} + +void TextLayout::draw (Graphics& g, const Rectangle& area) const +{ + const Point origin (justification.appliedToRectangle (Rectangle (width, getHeight()), area).getPosition()); + + LowLevelGraphicsContext& context = g.getInternalContext(); + + for (int i = 0; i < getNumLines(); ++i) + { + const Line& line = getLine (i); + const Point lineOrigin (origin + line.lineOrigin); + + for (int j = 0; j < line.runs.size(); ++j) + { + const Run& run = *line.runs.getUnchecked (j); + context.setFont (run.font); + context.setFill (run.colour); + + for (int k = 0; k < run.glyphs.size(); ++k) + { + const Glyph& glyph = run.glyphs.getReference (k); + context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x, + lineOrigin.y + glyph.anchor.y)); + } + } + } +} + +void TextLayout::createLayout (const AttributedString& text, float maxWidth) +{ + lines.clear(); + width = maxWidth; + justification = text.getJustification(); + + if (! createNativeLayout (text)) + createStandardLayout (text); + + recalculateWidth (text); +} + +//============================================================================== +namespace TextLayoutHelpers +{ + struct FontAndColour + { + FontAndColour (const Font* f) noexcept : font (f), colour (0xff000000) {} + + const Font* font; + Colour colour; + + bool operator!= (const FontAndColour& other) const noexcept + { + return (font != other.font && *font != *other.font) || colour != other.colour; + } + }; + + struct RunAttribute + { + RunAttribute (const FontAndColour& fc, const Range r) noexcept + : fontAndColour (fc), range (r) + {} + + FontAndColour fontAndColour; + Range range; + }; + + struct Token + { + Token (const String& t, const Font& f, Colour c, const bool whitespace) + : text (t), font (f), colour (c), + area (font.getStringWidthFloat (t), f.getHeight()), + isWhitespace (whitespace), + isNewLine (t.containsChar ('\n') || t.containsChar ('\r')) + {} + + const String text; + const Font font; + const Colour colour; + Rectangle area; + int line; + float lineHeight; + const bool isWhitespace, isNewLine; + + private: + Token& operator= (const Token&); + }; + + class TokenList + { + public: + TokenList() noexcept : totalLines (0) {} + + void createLayout (const AttributedString& text, TextLayout& layout) + { + tokens.ensureStorageAllocated (64); + layout.ensureStorageAllocated (totalLines); + + addTextRuns (text); + layoutRuns (layout.getWidth()); + + int charPosition = 0; + int lineStartPosition = 0; + int runStartPosition = 0; + + ScopedPointer currentLine; + ScopedPointer currentRun; + + bool needToSetLineOrigin = true; + + for (int i = 0; i < tokens.size(); ++i) + { + const Token& t = *tokens.getUnchecked (i); + + Array newGlyphs; + Array xOffsets; + t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets); + + if (currentRun == nullptr) currentRun = new TextLayout::Run(); + if (currentLine == nullptr) currentLine = new TextLayout::Line(); + + if (newGlyphs.size() > 0) + { + currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size()); + const Point tokenOrigin (t.area.getPosition().translated (0, t.font.getAscent())); + + if (needToSetLineOrigin) + { + needToSetLineOrigin = false; + currentLine->lineOrigin = tokenOrigin; + } + + const Point glyphOffset (tokenOrigin - currentLine->lineOrigin); + + for (int j = 0; j < newGlyphs.size(); ++j) + { + const float x = xOffsets.getUnchecked (j); + currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j), + glyphOffset.translated (x, 0), + xOffsets.getUnchecked (j + 1) - x)); + } + + charPosition += newGlyphs.size(); + } + + if (t.isWhitespace || t.isNewLine) + ++charPosition; + + const Token* const nextToken = tokens [i + 1]; + + if (nextToken == nullptr) // this is the last token + { + addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); + currentLine->stringRange = Range (lineStartPosition, charPosition); + + if (! needToSetLineOrigin) + layout.addLine (currentLine.release()); + + needToSetLineOrigin = true; + } + else + { + if (t.font != nextToken->font || t.colour != nextToken->colour) + { + addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); + runStartPosition = charPosition; + } + + if (t.line != nextToken->line) + { + if (currentRun == nullptr) + currentRun = new TextLayout::Run(); + + addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); + currentLine->stringRange = Range (lineStartPosition, charPosition); + + if (! needToSetLineOrigin) + layout.addLine (currentLine.release()); + + runStartPosition = charPosition; + lineStartPosition = charPosition; + needToSetLineOrigin = true; + } + } + } + + if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0) + { + const float totalW = layout.getWidth(); + const bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0; + + for (int i = 0; i < layout.getNumLines(); ++i) + { + float dx = totalW - layout.getLine(i).getLineBoundsX().getLength(); + + if (isCentred) + dx /= 2.0f; + + layout.getLine(i).lineOrigin.x += dx; + } + } + } + + private: + static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun, + const Token& t, const int start, const int end) + { + glyphRun->stringRange = Range (start, end); + glyphRun->font = t.font; + glyphRun->colour = t.colour; + glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent()); + glyphLine.descent = jmax (glyphLine.descent, t.font.getDescent()); + glyphLine.runs.add (glyphRun); + } + + static int getCharacterType (const juce_wchar c) noexcept + { + if (c == '\r' || c == '\n') + return 0; + + return CharacterFunctions::isWhitespace (c) ? 2 : 1; + } + + void appendText (const AttributedString& text, const Range stringRange, + const Font& font, Colour colour) + { + const String stringText (text.getText().substring (stringRange.getStart(), stringRange.getEnd())); + String::CharPointerType t (stringText.getCharPointer()); + String currentString; + int lastCharType = 0; + + for (;;) + { + const juce_wchar c = t.getAndAdvance(); + if (c == 0) + break; + + const int charType = getCharacterType (c); + + if (charType == 0 || charType != lastCharType) + { + if (currentString.isNotEmpty()) + tokens.add (new Token (currentString, font, colour, + lastCharType == 2 || lastCharType == 0)); + + currentString = String::charToString (c); + + if (c == '\r' && *t == '\n') + currentString += t.getAndAdvance(); + } + else + { + currentString += c; + } + + lastCharType = charType; + } + + if (currentString.isNotEmpty()) + tokens.add (new Token (currentString, font, colour, lastCharType == 2)); + } + + void layoutRuns (const float maxWidth) + { + float x = 0, y = 0, h = 0; + int i; + + for (i = 0; i < tokens.size(); ++i) + { + Token& t = *tokens.getUnchecked(i); + t.area.setPosition (x, y); + t.line = totalLines; + x += t.area.getWidth(); + h = jmax (h, t.area.getHeight()); + + const Token* const nextTok = tokens[i + 1]; + + if (nextTok == nullptr) + break; + + if (t.isNewLine || ((! nextTok->isWhitespace) && x + nextTok->area.getWidth() > maxWidth)) + { + setLastLineHeight (i + 1, h); + x = 0; + y += h; + h = 0; + ++totalLines; + } + } + + setLastLineHeight (jmin (i + 1, tokens.size()), h); + ++totalLines; + } + + void setLastLineHeight (int i, const float height) noexcept + { + while (--i >= 0) + { + Token& tok = *tokens.getUnchecked (i); + + if (tok.line == totalLines) + tok.lineHeight = height; + else + break; + } + } + + void addTextRuns (const AttributedString& text) + { + Font defaultFont; + Array runAttributes; + + { + const int stringLength = text.getText().length(); + int rangeStart = 0; + FontAndColour lastFontAndColour (&defaultFont); + + // Iterate through every character in the string + for (int i = 0; i < stringLength; ++i) + { + FontAndColour newFontAndColour (&defaultFont); + const int numCharacterAttributes = text.getNumAttributes(); + + for (int j = 0; j < numCharacterAttributes; ++j) + { + const AttributedString::Attribute& attr = *text.getAttribute (j); + + if (attr.range.contains (i)) + { + if (const Font* f = attr.getFont()) newFontAndColour.font = f; + if (const Colour* c = attr.getColour()) newFontAndColour.colour = *c; + } + } + + if (i > 0 && newFontAndColour != lastFontAndColour) + { + runAttributes.add (RunAttribute (lastFontAndColour, Range (rangeStart, i))); + rangeStart = i; + } + + lastFontAndColour = newFontAndColour; + } + + if (rangeStart < stringLength) + runAttributes.add (RunAttribute (lastFontAndColour, Range (rangeStart, stringLength))); + } + + for (int i = 0; i < runAttributes.size(); ++i) + { + const RunAttribute& r = runAttributes.getReference(i); + appendText (text, r.range, *(r.fontAndColour.font), r.fontAndColour.colour); + } + } + + static String getTrimmedEndIfNotAllWhitespace (const String& s) + { + String trimmed (s.trimEnd()); + if (trimmed.isEmpty() && ! s.isEmpty()) + trimmed = s.replaceCharacters ("\r\n\t", " "); + + return trimmed; + } + + OwnedArray tokens; + int totalLines; + + JUCE_DECLARE_NON_COPYABLE (TokenList) + }; +} + +//============================================================================== +void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth) +{ + const float minimumWidth = maxWidth / 2.0f; + float bestWidth = maxWidth; + float bestLineProportion = 0.0f; + + while (maxWidth > minimumWidth) + { + createLayout (text, maxWidth); + + if (getNumLines() < 2) + return; + + const float line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength(); + const float line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength(); + const float shortestLine = jmin (line1, line2); + const float prop = (shortestLine > 0) ? jmax (line1, line2) / shortestLine : 1.0f; + + if (prop > 0.9f) + return; + + if (prop > bestLineProportion) + { + bestLineProportion = prop; + bestWidth = maxWidth; + } + + maxWidth -= 10.0f; + } + + if (bestWidth != maxWidth) + createLayout (text, bestWidth); +} + +//============================================================================== +void TextLayout::createStandardLayout (const AttributedString& text) +{ + TextLayoutHelpers::TokenList l; + l.createLayout (text, *this); +} + +void TextLayout::recalculateWidth (const AttributedString& text) +{ + if (lines.size() > 0 && text.getReadingDirection() != AttributedString::rightToLeft) + { + Range range (lines.getFirst()->getLineBoundsX()); + + for (int i = lines.size(); --i > 0;) + range = range.getUnionWith (lines.getUnchecked(i)->getLineBoundsX()); + + for (int i = lines.size(); --i >= 0;) + lines.getUnchecked(i)->lineOrigin.x -= range.getStart(); + + width = range.getLength(); + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.h new file mode 100644 index 0000000..b5f9eea --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.h @@ -0,0 +1,177 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TEXTLAYOUT_H_INCLUDED +#define JUCE_TEXTLAYOUT_H_INCLUDED + + +//============================================================================== +/** + A Pre-formatted piece of text, which may contain multiple fonts and colours. + + A TextLayout is created from an AttributedString, and once created can be + quickly drawn into a Graphics context. + + @see AttributedString +*/ +class JUCE_API TextLayout +{ +public: + /** Creates an empty layout. + Having created a TextLayout, you can populate it using createLayout() or + createLayoutWithBalancedLineLengths(). + */ + TextLayout(); + TextLayout (const TextLayout&); + TextLayout& operator= (const TextLayout&); + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + TextLayout (TextLayout&& other) noexcept; + TextLayout& operator= (TextLayout&&) noexcept; + #endif + + /** Destructor. */ + ~TextLayout(); + + //============================================================================== + /** Creates a layout from the given attributed string. + This will replace any data that is currently stored in the layout. + */ + void createLayout (const AttributedString& text, float maxWidth); + + /** Creates a layout, attempting to choose a width which results in lines + of a similar length. + + This will be slower than the normal createLayout method, but produces a + tidier result. + */ + void createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth); + + /** Draws the layout within the specified area. + The position of the text within the rectangle is controlled by the justification + flags set in the original AttributedString that was used to create this layout. + */ + void draw (Graphics& g, const Rectangle& area) const; + + //============================================================================== + /** A positioned glyph. */ + class JUCE_API Glyph + { + public: + Glyph (int glyphCode, Point anchor, float width) noexcept; + Glyph (const Glyph&) noexcept; + Glyph& operator= (const Glyph&) noexcept; + ~Glyph() noexcept; + + /** The code number of this glyph. */ + int glyphCode; + + /** The glyph's anchor point - this is relative to the line's origin. + @see TextLayout::Line::lineOrigin + */ + Point anchor; + + float width; + + private: + JUCE_LEAK_DETECTOR (Glyph) + }; + + //============================================================================== + /** A sequence of glyphs with a common font and colour. */ + class JUCE_API Run + { + public: + Run() noexcept; + Run (const Run&); + Run (Range stringRange, int numGlyphsToPreallocate); + ~Run() noexcept; + + Font font; /**< The run's font. */ + Colour colour; /**< The run's colour. */ + Array glyphs; /**< The glyphs in this run. */ + Range stringRange; /**< The character range that this run represents in the + original string that was used to create it. */ + private: + Run& operator= (const Run&); + JUCE_LEAK_DETECTOR (Run) + }; + + //============================================================================== + /** A line containing a sequence of glyph-runs. */ + class JUCE_API Line + { + public: + Line() noexcept; + Line (const Line&); + Line (Range stringRange, Point lineOrigin, + float ascent, float descent, float leading, int numRunsToPreallocate); + ~Line() noexcept; + + /** Returns the X position range which contains all the glyphs in this line. */ + Range getLineBoundsX() const noexcept; + + OwnedArray runs; /**< The glyph-runs in this line. */ + Range stringRange; /**< The character range that this line represents in the + original string that was used to create it. */ + Point lineOrigin; /**< The line's baseline origin. */ + float ascent, descent, leading; + + private: + Line& operator= (const Line&); + JUCE_LEAK_DETECTOR (Line) + }; + + //============================================================================== + /** Returns the maximum width of the content. */ + float getWidth() const noexcept { return width; } + + /** Returns the maximum height of the content. */ + float getHeight() const noexcept; + + /** Returns the number of lines in the layout. */ + int getNumLines() const noexcept { return lines.size(); } + + /** Returns one of the lines. */ + Line& getLine (int index) const; + + /** Adds a line to the layout. The layout will take ownership of this line object + and will delete it when it is no longer needed. */ + void addLine (Line* line); + + /** Pre-allocates space for the specified number of lines. */ + void ensureStorageAllocated (int numLinesNeeded); + +private: + OwnedArray lines; + float width; + Justification justification; + + void createStandardLayout (const AttributedString&); + bool createNativeLayout (const AttributedString&); + void recalculateWidth (const AttributedString&); + + JUCE_LEAK_DETECTOR (TextLayout) +}; + +#endif // JUCE_TEXTLAYOUT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp new file mode 100644 index 0000000..4eda6b0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -0,0 +1,260 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct FontStyleHelpers +{ + static const char* getStyleName (const bool bold, + const bool italic) noexcept + { + if (bold && italic) return "Bold Italic"; + if (bold) return "Bold"; + if (italic) return "Italic"; + return "Regular"; + } + + static const char* getStyleName (const int styleFlags) noexcept + { + return getStyleName ((styleFlags & Font::bold) != 0, + (styleFlags & Font::italic) != 0); + } + + static bool isBold (const String& style) noexcept + { + return style.containsWholeWordIgnoreCase ("Bold"); + } + + static bool isItalic (const String& style) noexcept + { + return style.containsWholeWordIgnoreCase ("Italic") + || style.containsWholeWordIgnoreCase ("Oblique"); + } + + static bool isPlaceholderFamilyName (const String& family) + { + return family == Font::getDefaultSansSerifFontName() + || family == Font::getDefaultSerifFontName() + || family == Font::getDefaultMonospacedFontName(); + } + + struct ConcreteFamilyNames + { + ConcreteFamilyNames() + : sans (findName (Font::getDefaultSansSerifFontName())), + serif (findName (Font::getDefaultSerifFontName())), + mono (findName (Font::getDefaultMonospacedFontName())) + { + } + + String lookUp (const String& placeholder) + { + if (placeholder == Font::getDefaultSansSerifFontName()) return sans; + if (placeholder == Font::getDefaultSerifFontName()) return serif; + if (placeholder == Font::getDefaultMonospacedFontName()) return mono; + + return findName (placeholder); + } + + private: + static String findName (const String& placeholder) + { + const Font f (placeholder, Font::getDefaultStyle(), 15.0f); + return Font::getDefaultTypefaceForFont (f)->getName(); + } + + String sans, serif, mono; + }; + + static String getConcreteFamilyNameFromPlaceholder (const String& placeholder) + { + static ConcreteFamilyNames names; + return names.lookUp (placeholder); + } + + static String getConcreteFamilyName (const Font& font) + { + const String& family = font.getTypefaceName(); + + return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family) + : family; + } +}; + +//============================================================================== +Typeface::Typeface (const String& faceName, const String& styleName) noexcept + : name (faceName), style (styleName) +{ +} + +Typeface::~Typeface() +{ +} + +Typeface::Ptr Typeface::getFallbackTypeface() +{ + const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f); + return fallbackFont.getTypeface(); +} + +EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) +{ + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + { + applyVerticalHintingTransform (fontHeight, path); + + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + } + + return nullptr; +} + +//============================================================================== +struct Typeface::HintingParams +{ + HintingParams (Typeface& t) + : cachedSize (0), top (0), middle (0), bottom (0) + { + Font font (&t); + font = font.withHeight ((float) standardHeight); + + top = getAverageY (font, "BDEFPRTZOQ", true); + middle = getAverageY (font, "acegmnopqrsuvwxy", true); + bottom = getAverageY (font, "BDELZOC", false); + } + + void applyVerticalHintingTransform (float fontSize, Path& path) + { + if (cachedSize != fontSize) + { + cachedSize = fontSize; + cachedScale = Scaling (top, middle, bottom, fontSize); + } + + if (bottom < top + 3.0f / fontSize) + return; + + Path result; + + for (Path::Iterator i (path); i.next();) + { + switch (i.elementType) + { + case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break; + case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break; + case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1), + i.x2, cachedScale.apply (i.y2)); break; + case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1), + i.x2, cachedScale.apply (i.y2), + i.x3, cachedScale.apply (i.y3)); break; + case Path::Iterator::closePath: result.closeSubPath(); break; + default: jassertfalse; break; + } + } + + result.swapWithPath (path); + } + +private: + struct Scaling + { + Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {} + + Scaling (float t, float m, float b, float fontSize) noexcept : middle (m) + { + const float newT = std::floor (fontSize * t + 0.5f) / fontSize; + const float newB = std::floor (fontSize * b + 0.5f) / fontSize; + const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters + // are more likely to become taller than shorter. + upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t)); + lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m)); + + upperOffset = newM - m * upperScale; + lowerOffset = newB - b * lowerScale; + } + + float apply (float y) const noexcept + { + return y < middle ? (y * upperScale + upperOffset) + : (y * lowerScale + lowerOffset); + } + + float middle, upperScale, upperOffset, lowerScale, lowerOffset; + }; + + float cachedSize; + Scaling cachedScale; + + static float getAverageY (const Font& font, const char* chars, bool getTop) + { + GlyphArrangement ga; + ga.addLineOfText (font, chars, 0, 0); + + Array y; + DefaultElementComparator sorter; + + for (int i = 0; i < ga.getNumGlyphs(); ++i) + { + Path p; + ga.getGlyph (i).createPath (p); + Rectangle bounds (p.getBounds()); + + if (! p.isEmpty()) + y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom()); + } + + float median = y[y.size() / 2]; + + float total = 0; + int num = 0; + + for (int i = 0; i < y.size(); ++i) + { + if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight) + { + total += y.getUnchecked(i); + ++num; + } + } + + return num < 4 ? 0.0f : total / (num * (float) standardHeight); + } + + enum { standardHeight = 100 }; + float top, middle, bottom; +}; + +void Typeface::applyVerticalHintingTransform (float fontSize, Path& path) +{ + if (fontSize > 3.0f && fontSize < 25.0f) + { + ScopedLock sl (hintingLock); + + if (hintingParams == nullptr) + hintingParams = new HintingParams (*this); + + return hintingParams->applyVerticalHintingTransform (fontSize, path); + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h new file mode 100644 index 0000000..a392fb7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h @@ -0,0 +1,161 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TYPEFACE_H_INCLUDED +#define JUCE_TYPEFACE_H_INCLUDED + + +//============================================================================== +/** + A typeface represents a size-independent font. + + This base class is abstract, but calling createSystemTypefaceFor() will return + a platform-specific subclass that can be used. + + The CustomTypeface subclass allow you to build your own typeface, and to + load and save it in the Juce typeface format. + + Normally you should never need to deal directly with Typeface objects - the Font + class does everything you typically need for rendering text. + + @see CustomTypeface, Font +*/ +class JUCE_API Typeface : public ReferenceCountedObject +{ +public: + //============================================================================== + /** A handy typedef for a pointer to a typeface. */ + typedef ReferenceCountedObjectPtr Ptr; + + //============================================================================== + /** Returns the font family of the typeface. + @see Font::getTypefaceName + */ + const String& getName() const noexcept { return name; } + + //============================================================================== + /** Returns the font style of the typeface. + @see Font::getTypefaceStyle + */ + const String& getStyle() const noexcept { return style; } + + //============================================================================== + /** Creates a new system typeface. */ + static Ptr createSystemTypefaceFor (const Font& font); + + /** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image). + The system will take its own internal copy of the data, so you can free the block once + this method has returned. + */ + static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize); + + //============================================================================== + /** Destructor. */ + virtual ~Typeface(); + + /** Returns true if this typeface can be used to render the specified font. + When called, the font will already have been checked to make sure that its name and + style flags match the typeface. + */ + virtual bool isSuitableForFont (const Font&) const { return true; } + + /** Returns the ascent of the font, as a proportion of its height. + The height is considered to always be normalised as 1.0, so this will be a + value less that 1.0, indicating the proportion of the font that lies above + its baseline. + */ + virtual float getAscent() const = 0; + + /** Returns the descent of the font, as a proportion of its height. + The height is considered to always be normalised as 1.0, so this will be a + value less that 1.0, indicating the proportion of the font that lies below + its baseline. + */ + virtual float getDescent() const = 0; + + /** Returns the value by which you should multiply a juce font-height value to + convert it to the equivalent point-size. + */ + virtual float getHeightToPointsFactor() const = 0; + + /** Measures the width of a line of text. + The distance returned is based on the font having an normalised height of 1.0. + You should never need to call this directly! Use Font::getStringWidth() instead! + */ + virtual float getStringWidth (const String& text) = 0; + + /** Converts a line of text into its glyph numbers and their positions. + The distances returned are based on the font having an normalised height of 1.0. + You should never need to call this directly! Use Font::getGlyphPositions() instead! + */ + virtual void getGlyphPositions (const String& text, Array & glyphs, Array& xOffsets) = 0; + + /** Returns the outline for a glyph. + The path returned will be normalised to a font height of 1.0. + */ + virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0; + + /** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */ + virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight); + + /** Returns true if the typeface uses hinting. */ + virtual bool isHinted() const { return false; } + + //============================================================================== + /** Changes the number of fonts that are cached in memory. */ + static void setTypefaceCacheSize (int numFontsToCache); + + /** Clears any fonts that are currently cached in memory. */ + static void clearTypefaceCache(); + + /** On some platforms, this allows a specific path to be scanned. + Currently only available when using FreeType. + */ + static void scanFolderForFonts (const File& folder); + + /** Makes an attempt at performing a good overall distortion that will scale a font of + the given size to align vertically with the pixel grid. The path should be an unscaled + (i.e. normalised to height of 1.0) path for a glyph. + */ + void applyVerticalHintingTransform (float fontHeight, Path& path); + +protected: + //============================================================================== + String name, style; + + Typeface (const String& name, const String& style) noexcept; + + static Ptr getFallbackTypeface(); + +private: + struct HintingParams; + friend struct ContainerDeletePolicy; + ScopedPointer hintingParams; + CriticalSection hintingLock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface) +}; + + +#endif // JUCE_TYPEFACE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp new file mode 100644 index 0000000..85d6f4e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp @@ -0,0 +1,262 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +AffineTransform::AffineTransform() noexcept + : mat00 (1.0f), mat01 (0), mat02 (0), + mat10 (0), mat11 (1.0f), mat12 (0) +{ +} + +AffineTransform::AffineTransform (const AffineTransform& other) noexcept + : mat00 (other.mat00), mat01 (other.mat01), mat02 (other.mat02), + mat10 (other.mat10), mat11 (other.mat11), mat12 (other.mat12) +{ +} + +AffineTransform::AffineTransform (const float m00, const float m01, const float m02, + const float m10, const float m11, const float m12) noexcept + : mat00 (m00), mat01 (m01), mat02 (m02), + mat10 (m10), mat11 (m11), mat12 (m12) +{ +} + +AffineTransform& AffineTransform::operator= (const AffineTransform& other) noexcept +{ + mat00 = other.mat00; + mat01 = other.mat01; + mat02 = other.mat02; + mat10 = other.mat10; + mat11 = other.mat11; + mat12 = other.mat12; + + return *this; +} + +bool AffineTransform::operator== (const AffineTransform& other) const noexcept +{ + return mat00 == other.mat00 + && mat01 == other.mat01 + && mat02 == other.mat02 + && mat10 == other.mat10 + && mat11 == other.mat11 + && mat12 == other.mat12; +} + +bool AffineTransform::operator!= (const AffineTransform& other) const noexcept +{ + return ! operator== (other); +} + +//============================================================================== +bool AffineTransform::isIdentity() const noexcept +{ + return (mat01 == 0) + && (mat02 == 0) + && (mat10 == 0) + && (mat12 == 0) + && (mat00 == 1.0f) + && (mat11 == 1.0f); +} + +const AffineTransform AffineTransform::identity; + +//============================================================================== +AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept +{ + return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10, + other.mat00 * mat01 + other.mat01 * mat11, + other.mat00 * mat02 + other.mat01 * mat12 + other.mat02, + other.mat10 * mat00 + other.mat11 * mat10, + other.mat10 * mat01 + other.mat11 * mat11, + other.mat10 * mat02 + other.mat11 * mat12 + other.mat12); +} + +AffineTransform AffineTransform::translated (const float dx, const float dy) const noexcept +{ + return AffineTransform (mat00, mat01, mat02 + dx, + mat10, mat11, mat12 + dy); +} + +AffineTransform AffineTransform::translation (const float dx, const float dy) noexcept +{ + return AffineTransform (1.0f, 0, dx, + 0, 1.0f, dy); +} + +AffineTransform AffineTransform::withAbsoluteTranslation (const float tx, const float ty) const noexcept +{ + return AffineTransform (mat00, mat01, tx, + mat10, mat11, ty); +} + +AffineTransform AffineTransform::rotated (const float rad) const noexcept +{ + const float cosRad = std::cos (rad); + const float sinRad = std::sin (rad); + + return AffineTransform (cosRad * mat00 + -sinRad * mat10, + cosRad * mat01 + -sinRad * mat11, + cosRad * mat02 + -sinRad * mat12, + sinRad * mat00 + cosRad * mat10, + sinRad * mat01 + cosRad * mat11, + sinRad * mat02 + cosRad * mat12); +} + +AffineTransform AffineTransform::rotation (const float rad) noexcept +{ + const float cosRad = std::cos (rad); + const float sinRad = std::sin (rad); + + return AffineTransform (cosRad, -sinRad, 0, + sinRad, cosRad, 0); +} + +AffineTransform AffineTransform::rotation (const float rad, const float pivotX, const float pivotY) noexcept +{ + const float cosRad = std::cos (rad); + const float sinRad = std::sin (rad); + + return AffineTransform (cosRad, -sinRad, -cosRad * pivotX + sinRad * pivotY + pivotX, + sinRad, cosRad, -sinRad * pivotX + -cosRad * pivotY + pivotY); +} + +AffineTransform AffineTransform::rotated (const float angle, const float pivotX, const float pivotY) const noexcept +{ + return followedBy (rotation (angle, pivotX, pivotY)); +} + +AffineTransform AffineTransform::scaled (const float factorX, const float factorY) const noexcept +{ + return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02, + factorY * mat10, factorY * mat11, factorY * mat12); +} + +AffineTransform AffineTransform::scaled (const float factor) const noexcept +{ + return AffineTransform (factor * mat00, factor * mat01, factor * mat02, + factor * mat10, factor * mat11, factor * mat12); +} + +AffineTransform AffineTransform::scale (const float factorX, const float factorY) noexcept +{ + return AffineTransform (factorX, 0, 0, 0, factorY, 0); +} + +AffineTransform AffineTransform::scale (const float factor) noexcept +{ + return AffineTransform (factor, 0, 0, 0, factor, 0); +} + +AffineTransform AffineTransform::scaled (const float factorX, const float factorY, + const float pivotX, const float pivotY) const noexcept +{ + return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02 + pivotX * (1.0f - factorX), + factorY * mat10, factorY * mat11, factorY * mat12 + pivotY * (1.0f - factorY)); +} + +AffineTransform AffineTransform::scale (const float factorX, const float factorY, + const float pivotX, const float pivotY) noexcept +{ + return AffineTransform (factorX, 0, pivotX * (1.0f - factorX), + 0, factorY, pivotY * (1.0f - factorY)); +} + +AffineTransform AffineTransform::shear (float shearX, float shearY) noexcept +{ + return AffineTransform (1.0f, shearX, 0, + shearY, 1.0f, 0); +} + +AffineTransform AffineTransform::sheared (const float shearX, const float shearY) const noexcept +{ + return AffineTransform (mat00 + shearX * mat10, + mat01 + shearX * mat11, + mat02 + shearX * mat12, + mat10 + shearY * mat00, + mat11 + shearY * mat01, + mat12 + shearY * mat02); +} + +AffineTransform AffineTransform::verticalFlip (const float height) noexcept +{ + return AffineTransform (1.0f, 0, 0, 0, -1.0f, height); +} + +AffineTransform AffineTransform::inverted() const noexcept +{ + double determinant = (mat00 * mat11 - mat10 * mat01); + + if (determinant != 0.0) + { + determinant = 1.0 / determinant; + + const float dst00 = (float) ( mat11 * determinant); + const float dst10 = (float) (-mat10 * determinant); + const float dst01 = (float) (-mat01 * determinant); + const float dst11 = (float) ( mat00 * determinant); + + return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01, + dst10, dst11, -mat02 * dst10 - mat12 * dst11); + } + else + { + // singularity.. + return *this; + } +} + +bool AffineTransform::isSingularity() const noexcept +{ + return (mat00 * mat11 - mat10 * mat01) == 0; +} + +AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00, + const float x10, const float y10, + const float x01, const float y01) noexcept +{ + return AffineTransform (x10 - x00, x01 - x00, x00, + y10 - y00, y01 - y00, y00); +} + +AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, + const float sx2, const float sy2, const float tx2, const float ty2, + const float sx3, const float sy3, const float tx3, const float ty3) noexcept +{ + return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) + .inverted() + .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); +} + +bool AffineTransform::isOnlyTranslation() const noexcept +{ + return (mat01 == 0) + && (mat10 == 0) + && (mat00 == 1.0f) + && (mat11 == 1.0f); +} + +float AffineTransform::getScaleFactor() const noexcept +{ + return (std::abs (mat00) + std::abs (mat11)) / 2.0f; +} diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.h new file mode 100644 index 0000000..f30fdb8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.h @@ -0,0 +1,285 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_AFFINETRANSFORM_H_INCLUDED +#define JUCE_AFFINETRANSFORM_H_INCLUDED + + +//============================================================================== +/** + Represents a 2D affine-transformation matrix. + + An affine transformation is a transformation such as a rotation, scale, shear, + resize or translation. + + These are used for various 2D transformation tasks, e.g. with Path objects. + + @see Path, Point, Line +*/ +class JUCE_API AffineTransform +{ +public: + //============================================================================== + /** Creates an identity transform. */ + AffineTransform() noexcept; + + /** Creates a copy of another transform. */ + AffineTransform (const AffineTransform& other) noexcept; + + /** Creates a transform from a set of raw matrix values. + + The resulting matrix is: + + (mat00 mat01 mat02) + (mat10 mat11 mat12) + ( 0 0 1 ) + */ + AffineTransform (float mat00, float mat01, float mat02, + float mat10, float mat11, float mat12) noexcept; + + /** Copies from another AffineTransform object */ + AffineTransform& operator= (const AffineTransform& other) noexcept; + + /** Compares two transforms. */ + bool operator== (const AffineTransform& other) const noexcept; + + /** Compares two transforms. */ + bool operator!= (const AffineTransform& other) const noexcept; + + /** A ready-to-use identity transform, which you can use to append other + transformations to. + + e.g. @code + AffineTransform myTransform = AffineTransform::identity.rotated (.5f) + .scaled (2.0f); + @endcode + */ + static const AffineTransform identity; + + //============================================================================== + /** Transforms a 2D coordinate using this matrix. */ + template + void transformPoint (ValueType& x, ValueType& y) const noexcept + { + const ValueType oldX = x; + x = static_cast (mat00 * oldX + mat01 * y + mat02); + y = static_cast (mat10 * oldX + mat11 * y + mat12); + } + + /** Transforms two 2D coordinates using this matrix. + This is just a shortcut for calling transformPoint() on each of these pairs of + coordinates in turn. (And putting all the calculations into one function hopefully + also gives the compiler a bit more scope for pipelining it). + */ + template + void transformPoints (ValueType& x1, ValueType& y1, + ValueType& x2, ValueType& y2) const noexcept + { + const ValueType oldX1 = x1, oldX2 = x2; + x1 = static_cast (mat00 * oldX1 + mat01 * y1 + mat02); + y1 = static_cast (mat10 * oldX1 + mat11 * y1 + mat12); + x2 = static_cast (mat00 * oldX2 + mat01 * y2 + mat02); + y2 = static_cast (mat10 * oldX2 + mat11 * y2 + mat12); + } + + /** Transforms three 2D coordinates using this matrix. + This is just a shortcut for calling transformPoint() on each of these pairs of + coordinates in turn. (And putting all the calculations into one function hopefully + also gives the compiler a bit more scope for pipelining it). + */ + template + void transformPoints (ValueType& x1, ValueType& y1, + ValueType& x2, ValueType& y2, + ValueType& x3, ValueType& y3) const noexcept + { + const ValueType oldX1 = x1, oldX2 = x2, oldX3 = x3; + x1 = static_cast (mat00 * oldX1 + mat01 * y1 + mat02); + y1 = static_cast (mat10 * oldX1 + mat11 * y1 + mat12); + x2 = static_cast (mat00 * oldX2 + mat01 * y2 + mat02); + y2 = static_cast (mat10 * oldX2 + mat11 * y2 + mat12); + x3 = static_cast (mat00 * oldX3 + mat01 * y3 + mat02); + y3 = static_cast (mat10 * oldX3 + mat11 * y3 + mat12); + } + + //============================================================================== + /** Returns a new transform which is the same as this one followed by a translation. */ + AffineTransform translated (float deltaX, + float deltaY) const noexcept; + + /** Returns a new transform which is the same as this one followed by a translation. */ + template + AffineTransform translated (PointType delta) const noexcept + { + return translated ((float) delta.x, (float) delta.y); + } + + /** Returns a new transform which is a translation. */ + static AffineTransform translation (float deltaX, + float deltaY) noexcept; + + /** Returns a new transform which is a translation. */ + template + static AffineTransform translation (PointType delta) noexcept + { + return translation ((float) delta.x, (float) delta.y); + } + + /** Returns a copy of this transform with the specified translation matrix values. */ + AffineTransform withAbsoluteTranslation (float translationX, + float translationY) const noexcept; + + /** Returns a transform which is the same as this one followed by a rotation. + + The rotation is specified by a number of radians to rotate clockwise, centred around + the origin (0, 0). + */ + AffineTransform rotated (float angleInRadians) const noexcept; + + /** Returns a transform which is the same as this one followed by a rotation about a given point. + + The rotation is specified by a number of radians to rotate clockwise, centred around + the coordinates passed in. + */ + AffineTransform rotated (float angleInRadians, + float pivotX, + float pivotY) const noexcept; + + /** Returns a new transform which is a rotation about (0, 0). */ + static AffineTransform rotation (float angleInRadians) noexcept; + + /** Returns a new transform which is a rotation about a given point. */ + static AffineTransform rotation (float angleInRadians, + float pivotX, + float pivotY) noexcept; + + /** Returns a transform which is the same as this one followed by a re-scaling. + The scaling is centred around the origin (0, 0). + */ + AffineTransform scaled (float factorX, + float factorY) const noexcept; + + /** Returns a transform which is the same as this one followed by a re-scaling. + The scaling is centred around the origin (0, 0). + */ + AffineTransform scaled (float factor) const noexcept; + + /** Returns a transform which is the same as this one followed by a re-scaling. + The scaling is centred around the origin provided. + */ + AffineTransform scaled (float factorX, float factorY, + float pivotX, float pivotY) const noexcept; + + /** Returns a new transform which is a re-scale about the origin. */ + static AffineTransform scale (float factorX, + float factorY) noexcept; + + /** Returns a new transform which is a re-scale about the origin. */ + static AffineTransform scale (float factor) noexcept; + + /** Returns a new transform which is a re-scale centred around the point provided. */ + static AffineTransform scale (float factorX, float factorY, + float pivotX, float pivotY) noexcept; + + /** Returns a transform which is the same as this one followed by a shear. + The shear is centred around the origin (0, 0). + */ + AffineTransform sheared (float shearX, float shearY) const noexcept; + + /** Returns a shear transform, centred around the origin (0, 0). */ + static AffineTransform shear (float shearX, float shearY) noexcept; + + /** Returns a transform that will flip coordinates vertically within a window of the given height. + This is handy for converting between upside-down coordinate systems such as OpenGL or CoreGraphics. + */ + static AffineTransform verticalFlip (float height) noexcept; + + /** Returns a matrix which is the inverse operation of this one. + + Some matrices don't have an inverse - in this case, the method will just return + an identity transform. + */ + AffineTransform inverted() const noexcept; + + /** Returns the transform that will map three known points onto three coordinates + that are supplied. + + This returns the transform that will transform (0, 0) into (x00, y00), + (1, 0) to (x10, y10), and (0, 1) to (x01, y01). + */ + static AffineTransform fromTargetPoints (float x00, float y00, + float x10, float y10, + float x01, float y01) noexcept; + + /** Returns the transform that will map three specified points onto three target points. */ + static AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1, + float sourceX2, float sourceY2, float targetX2, float targetY2, + float sourceX3, float sourceY3, float targetX3, float targetY3) noexcept; + + //============================================================================== + /** Returns the result of concatenating another transformation after this one. */ + AffineTransform followedBy (const AffineTransform& other) const noexcept; + + /** Returns true if this transform has no effect on points. */ + bool isIdentity() const noexcept; + + /** Returns true if this transform maps to a singularity - i.e. if it has no inverse. */ + bool isSingularity() const noexcept; + + /** Returns true if the transform only translates, and doesn't scale or rotate the + points. */ + bool isOnlyTranslation() const noexcept; + + /** If this transform is only a translation, this returns the X offset. + @see isOnlyTranslation + */ + float getTranslationX() const noexcept { return mat02; } + + /** If this transform is only a translation, this returns the X offset. + @see isOnlyTranslation + */ + float getTranslationY() const noexcept { return mat12; } + + /** Returns the approximate scale factor by which lengths will be transformed. + Obviously a length may be scaled by entirely different amounts depending on its + direction, so this is only appropriate as a rough guide. + */ + float getScaleFactor() const noexcept; + + //============================================================================== + /* The transform matrix is: + + (mat00 mat01 mat02) + (mat10 mat11 mat12) + ( 0 0 1 ) + */ + float mat00, mat01, mat02; + float mat10, mat11, mat12; + + +private: + //============================================================================== + JUCE_LEAK_DETECTOR (AffineTransform) +}; + +#endif // JUCE_AFFINETRANSFORM_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_BorderSize.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_BorderSize.h new file mode 100644 index 0000000..b6a6686 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_BorderSize.h @@ -0,0 +1,153 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_BORDERSIZE_H_INCLUDED +#define JUCE_BORDERSIZE_H_INCLUDED + + +//============================================================================== +/** + Specifies a set of gaps to be left around the sides of a rectangle. + + This is basically the size of the spaces at the top, bottom, left and right of + a rectangle. It's used by various component classes to specify borders. + + @see Rectangle +*/ +template +class BorderSize +{ +public: + //============================================================================== + /** Creates a null border. + All sizes are left as 0. + */ + BorderSize() noexcept + : top(), left(), bottom(), right() + { + } + + /** Creates a copy of another border. */ + BorderSize (const BorderSize& other) noexcept + : top (other.top), left (other.left), bottom (other.bottom), right (other.right) + { + } + + /** Creates a border with the given gaps. */ + BorderSize (ValueType topGap, ValueType leftGap, ValueType bottomGap, ValueType rightGap) noexcept + : top (topGap), left (leftGap), bottom (bottomGap), right (rightGap) + { + } + + /** Creates a border with the given gap on all sides. */ + explicit BorderSize (ValueType allGaps) noexcept + : top (allGaps), left (allGaps), bottom (allGaps), right (allGaps) + { + } + + //============================================================================== + /** Returns the gap that should be left at the top of the region. */ + ValueType getTop() const noexcept { return top; } + + /** Returns the gap that should be left at the top of the region. */ + ValueType getLeft() const noexcept { return left; } + + /** Returns the gap that should be left at the top of the region. */ + ValueType getBottom() const noexcept { return bottom; } + + /** Returns the gap that should be left at the top of the region. */ + ValueType getRight() const noexcept { return right; } + + /** Returns the sum of the top and bottom gaps. */ + ValueType getTopAndBottom() const noexcept { return top + bottom; } + + /** Returns the sum of the left and right gaps. */ + ValueType getLeftAndRight() const noexcept { return left + right; } + + /** Returns true if this border has no thickness along any edge. */ + bool isEmpty() const noexcept { return left + right + top + bottom == ValueType(); } + + //============================================================================== + /** Changes the top gap. */ + void setTop (ValueType newTopGap) noexcept { top = newTopGap; } + + /** Changes the left gap. */ + void setLeft (ValueType newLeftGap) noexcept { left = newLeftGap; } + + /** Changes the bottom gap. */ + void setBottom (ValueType newBottomGap) noexcept { bottom = newBottomGap; } + + /** Changes the right gap. */ + void setRight (ValueType newRightGap) noexcept { right = newRightGap; } + + //============================================================================== + /** Returns a rectangle with these borders removed from it. */ + Rectangle subtractedFrom (const Rectangle& original) const noexcept + { + return Rectangle (original.getX() + left, + original.getY() + top, + original.getWidth() - (left + right), + original.getHeight() - (top + bottom)); + } + + /** Removes this border from a given rectangle. */ + void subtractFrom (Rectangle& rectangle) const noexcept + { + rectangle = subtractedFrom (rectangle); + } + + /** Returns a rectangle with these borders added around it. */ + Rectangle addedTo (const Rectangle& original) const noexcept + { + return Rectangle (original.getX() - left, + original.getY() - top, + original.getWidth() + (left + right), + original.getHeight() + (top + bottom)); + } + + + /** Adds this border around a given rectangle. */ + void addTo (Rectangle& rectangle) const noexcept + { + rectangle = addedTo (rectangle); + } + + //============================================================================== + bool operator== (const BorderSize& other) const noexcept + { + return top == other.top && left == other.left && bottom == other.bottom && right == other.right; + } + + bool operator!= (const BorderSize& other) const noexcept + { + return ! operator== (other); + } + +private: + //============================================================================== + ValueType top, left, bottom, right; +}; + + +#endif // JUCE_BORDERSIZE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp new file mode 100644 index 0000000..73ee1c3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp @@ -0,0 +1,831 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +const int juce_edgeTableDefaultEdgesPerLine = 32; + +//============================================================================== +EdgeTable::EdgeTable (const Rectangle& area, + const Path& path, const AffineTransform& transform) + : bounds (area), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), + needToCheckEmptiness (true) +{ + allocate(); + int* t = table; + + for (int i = bounds.getHeight(); --i >= 0;) + { + *t = 0; + t += lineStrideElements; + } + + const int leftLimit = bounds.getX() << 8; + const int topLimit = bounds.getY() << 8; + const int rightLimit = bounds.getRight() << 8; + const int heightLimit = bounds.getHeight() << 8; + + PathFlatteningIterator iter (path, transform); + + while (iter.next()) + { + int y1 = roundToInt (iter.y1 * 256.0f); + int y2 = roundToInt (iter.y2 * 256.0f); + + if (y1 != y2) + { + y1 -= topLimit; + y2 -= topLimit; + + const int startY = y1; + int direction = -1; + + if (y1 > y2) + { + std::swap (y1, y2); + direction = 1; + } + + if (y1 < 0) + y1 = 0; + + if (y2 > heightLimit) + y2 = heightLimit; + + if (y1 < y2) + { + const double startX = 256.0f * iter.x1; + const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); + const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); + + do + { + const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); + int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); + + if (x < leftLimit) + x = leftLimit; + else if (x >= rightLimit) + x = rightLimit - 1; + + addEdgePoint (x, y1 >> 8, direction * step); + y1 += step; + } + while (y1 < y2); + } + } + } + + sanitiseLevels (path.isUsingNonZeroWinding()); +} + +EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) + : bounds (rectangleToAdd), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), + needToCheckEmptiness (true) +{ + allocate(); + table[0] = 0; + + const int x1 = rectangleToAdd.getX() << 8; + const int x2 = rectangleToAdd.getRight() << 8; + + int* t = table; + for (int i = rectangleToAdd.getHeight(); --i >= 0;) + { + t[0] = 2; + t[1] = x1; + t[2] = 255; + t[3] = x2; + t[4] = 0; + t += lineStrideElements; + } +} + +EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) + : bounds (rectanglesToAdd.getBounds()), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), + needToCheckEmptiness (true) +{ + allocate(); + clearLineSizes(); + + for (const Rectangle* r = rectanglesToAdd.begin(), * const e = rectanglesToAdd.end(); r != e; ++r) + { + const int x1 = r->getX() << 8; + const int x2 = r->getRight() << 8; + int y = r->getY() - bounds.getY(); + + for (int j = r->getHeight(); --j >= 0;) + addEdgePointPair (x1, x2, y++, 255); + } + + sanitiseLevels (true); +} + +EdgeTable::EdgeTable (const RectangleList& rectanglesToAdd) + : bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()), + maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2), + lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1), + needToCheckEmptiness (true) +{ + bounds.setHeight (bounds.getHeight() + 1); + allocate(); + clearLineSizes(); + + for (const Rectangle* r = rectanglesToAdd.begin(), * const e = rectanglesToAdd.end(); r != e; ++r) + { + const int x1 = roundToInt (r->getX() * 256.0f); + const int x2 = roundToInt (r->getRight() * 256.0f); + + const int y1 = roundToInt (r->getY() * 256.0f) - (bounds.getY() << 8); + const int y2 = roundToInt (r->getBottom() * 256.0f) - (bounds.getY() << 8); + + if (x2 <= x1 || y2 <= y1) + continue; + + int y = y1 >> 8; + const int lastLine = y2 >> 8; + + if (y == lastLine) + { + addEdgePointPair (x1, x2, y, y2 - y1); + } + else + { + addEdgePointPair (x1, x2, y++, 255 - (y1 & 255)); + + while (y < lastLine) + addEdgePointPair (x1, x2, y++, 255); + + jassert (y < bounds.getHeight()); + addEdgePointPair (x1, x2, y, y2 & 255); + } + } + + sanitiseLevels (true); +} + +EdgeTable::EdgeTable (const Rectangle& rectangleToAdd) + : bounds (Rectangle ((int) std::floor (rectangleToAdd.getX()), + roundToInt (rectangleToAdd.getY() * 256.0f) >> 8, + 2 + (int) rectangleToAdd.getWidth(), + 2 + (int) rectangleToAdd.getHeight())), + maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), + lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), + needToCheckEmptiness (true) +{ + jassert (! rectangleToAdd.isEmpty()); + allocate(); + table[0] = 0; + + const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f); + const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f); + + int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8); + jassert (y1 < 256); + int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8); + + if (x2 <= x1 || y2 <= y1) + { + bounds.setHeight (0); + return; + } + + int lineY = 0; + int* t = table; + + if ((y1 >> 8) == (y2 >> 8)) + { + t[0] = 2; + t[1] = x1; + t[2] = y2 - y1; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + } + else + { + t[0] = 2; + t[1] = x1; + t[2] = 255 - (y1 & 255); + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + + while (lineY < (y2 >> 8)) + { + t[0] = 2; + t[1] = x1; + t[2] = 255; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + } + + jassert (lineY < bounds.getHeight()); + t[0] = 2; + t[1] = x1; + t[2] = y2 & 255; + t[3] = x2; + t[4] = 0; + ++lineY; + t += lineStrideElements; + } + + while (lineY < bounds.getHeight()) + { + t[0] = 0; + t += lineStrideElements; + ++lineY; + } +} + +EdgeTable::EdgeTable (const EdgeTable& other) +{ + operator= (other); +} + +EdgeTable& EdgeTable::operator= (const EdgeTable& other) +{ + bounds = other.bounds; + maxEdgesPerLine = other.maxEdgesPerLine; + lineStrideElements = other.lineStrideElements; + needToCheckEmptiness = other.needToCheckEmptiness; + + allocate(); + copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); + return *this; +} + +EdgeTable::~EdgeTable() +{ +} + +//============================================================================== +static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept +{ + // (leave an extra line at the end for use as scratch space) + return (size_t) (lineStride * (2 + jmax (0, height))); +} + +void EdgeTable::allocate() +{ + table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight())); +} + +void EdgeTable::clearLineSizes() noexcept +{ + int* t = table; + for (int i = bounds.getHeight(); --i >= 0;) + { + *t = 0; + t += lineStrideElements; + } +} + +void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) noexcept +{ + while (--numLines >= 0) + { + memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int)); + src += srcLineStride; + dest += destLineStride; + } +} + +void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept +{ + // Convert the table from relative windings to absolute levels.. + int* lineStart = table; + + for (int y = bounds.getHeight(); --y >= 0;) + { + int num = lineStart[0]; + + if (num > 0) + { + LineItem* items = reinterpret_cast (lineStart + 1); + LineItem* const itemsEnd = items + num; + + // sort the X coords + std::sort (items, itemsEnd); + + const LineItem* src = items; + int correctedNum = num; + int level = 0; + + while (src < itemsEnd) + { + level += src->level; + const int x = src->x; + ++src; + + while (src < itemsEnd && src->x == x) + { + level += src->level; + ++src; + --correctedNum; + } + + int corrected = std::abs (level); + + if (corrected >> 8) + { + if (useNonZeroWinding) + { + corrected = 255; + } + else + { + corrected &= 511; + if (corrected >> 8) + corrected = 511 - corrected; + } + } + + items->x = x; + items->level = corrected; + ++items; + } + + lineStart[0] = correctedNum; + (items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table + } + + lineStart += lineStrideElements; + } +} + +void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) +{ + if (newNumEdgesPerLine != maxEdgesPerLine) + { + maxEdgesPerLine = newNumEdgesPerLine; + + jassert (bounds.getHeight() > 0); + const int newLineStrideElements = maxEdgesPerLine * 2 + 1; + + HeapBlock newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight())); + + copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); + + table.swapWith (newTable); + lineStrideElements = newLineStrideElements; + } +} + +void EdgeTable::optimiseTable() +{ + int maxLineElements = 0; + + for (int i = bounds.getHeight(); --i >= 0;) + maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]); + + remapTableForNumEdges (maxLineElements); +} + +void EdgeTable::addEdgePoint (const int x, const int y, const int winding) +{ + jassert (y >= 0 && y < bounds.getHeight()); + + int* line = table + lineStrideElements * y; + const int numPoints = line[0]; + + if (numPoints >= maxEdgesPerLine) + { + remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); + jassert (numPoints < maxEdgesPerLine); + line = table + lineStrideElements * y; + } + + line[0]++; + int n = numPoints << 1; + line [n + 1] = x; + line [n + 2] = winding; +} + +void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding) +{ + jassert (y >= 0 && y < bounds.getHeight()); + + int* line = table + lineStrideElements * y; + const int numPoints = line[0]; + + if (numPoints + 1 >= maxEdgesPerLine) + { + remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); + jassert (numPoints < maxEdgesPerLine); + line = table + lineStrideElements * y; + } + + line[0] = numPoints + 2; + line += numPoints << 1; + line[1] = x1; + line[2] = winding; + line[3] = x2; + line[4] = -winding; +} + +void EdgeTable::translate (float dx, const int dy) noexcept +{ + bounds.translate ((int) std::floor (dx), dy); + + int* lineStart = table; + const int intDx = (int) (dx * 256.0f); + + for (int i = bounds.getHeight(); --i >= 0;) + { + int* line = lineStart; + lineStart += lineStrideElements; + int num = *line++; + + while (--num >= 0) + { + *line += intDx; + line += 2; + } + } +} + +void EdgeTable::multiplyLevels (float amount) +{ + int* lineStart = table; + const int multiplier = (int) (amount * 256.0f); + + for (int y = 0; y < bounds.getHeight(); ++y) + { + int numPoints = lineStart[0]; + LineItem* item = reinterpret_cast (lineStart + 1); + lineStart += lineStrideElements; + + while (--numPoints > 0) + { + item->level = jmin (255, (item->level * multiplier) >> 8); + ++item; + } + } +} + +void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine) +{ + jassert (y >= 0 && y < bounds.getHeight()); + + int* srcLine = table + lineStrideElements * y; + int srcNum1 = *srcLine; + + if (srcNum1 == 0) + return; + + int srcNum2 = *otherLine; + + if (srcNum2 == 0) + { + *srcLine = 0; + return; + } + + const int right = bounds.getRight() << 8; + + // optimise for the common case where our line lies entirely within a + // single pair of points, as happens when clipping to a simple rect. + if (srcNum2 == 2 && otherLine[2] >= 255) + { + clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3])); + return; + } + + bool isUsingTempSpace = false; + + const int* src1 = srcLine + 1; + int x1 = *src1++; + + const int* src2 = otherLine + 1; + int x2 = *src2++; + + int destIndex = 0, destTotal = 0; + int level1 = 0, level2 = 0; + int lastX = std::numeric_limits::min(), lastLevel = 0; + + while (srcNum1 > 0 && srcNum2 > 0) + { + int nextX; + + if (x1 <= x2) + { + if (x1 == x2) + { + level2 = *src2++; + x2 = *src2++; + --srcNum2; + } + + nextX = x1; + level1 = *src1++; + x1 = *src1++; + --srcNum1; + } + else + { + nextX = x2; + level2 = *src2++; + x2 = *src2++; + --srcNum2; + } + + if (nextX > lastX) + { + if (nextX >= right) + break; + + lastX = nextX; + + const int nextLevel = (level1 * (level2 + 1)) >> 8; + jassert (isPositiveAndBelow (nextLevel, (int) 256)); + + if (nextLevel != lastLevel) + { + if (destTotal >= maxEdgesPerLine) + { + srcLine[0] = destTotal; + + if (isUsingTempSpace) + { + const size_t tempSize = (size_t) srcNum1 * 2 * sizeof (int); + int* const oldTemp = static_cast (alloca (tempSize)); + memcpy (oldTemp, src1, tempSize); + + remapTableForNumEdges (jmax (256, destTotal * 2)); + srcLine = table + lineStrideElements * y; + + int* const newTemp = table + lineStrideElements * bounds.getHeight(); + memcpy (newTemp, oldTemp, tempSize); + src1 = newTemp; + } + else + { + remapTableForNumEdges (jmax (256, destTotal * 2)); + srcLine = table + lineStrideElements * y; + } + } + + ++destTotal; + lastLevel = nextLevel; + + if (! isUsingTempSpace) + { + isUsingTempSpace = true; + int* const temp = table + lineStrideElements * bounds.getHeight(); + memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int)); + src1 = temp; + } + + srcLine[++destIndex] = nextX; + srcLine[++destIndex] = nextLevel; + } + } + } + + if (lastLevel > 0) + { + if (destTotal >= maxEdgesPerLine) + { + srcLine[0] = destTotal; + remapTableForNumEdges (jmax (256, destTotal * 2)); + srcLine = table + lineStrideElements * y; + } + + ++destTotal; + srcLine[++destIndex] = right; + srcLine[++destIndex] = 0; + } + + srcLine[0] = destTotal; +} + +void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept +{ + int* lastItem = dest + (dest[0] * 2 - 1); + + if (x2 < lastItem[0]) + { + if (x2 <= dest[1]) + { + dest[0] = 0; + return; + } + + while (x2 < lastItem[-2]) + { + --(dest[0]); + lastItem -= 2; + } + + lastItem[0] = x2; + lastItem[1] = 0; + } + + if (x1 > dest[1]) + { + while (lastItem[0] > x1) + lastItem -= 2; + + const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2; + + if (itemsRemoved > 0) + { + dest[0] -= itemsRemoved; + memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2)); + } + + dest[1] = x1; + } +} + + +//============================================================================== +void EdgeTable::clipToRectangle (const Rectangle& r) +{ + const Rectangle clipped (r.getIntersection (bounds)); + + if (clipped.isEmpty()) + { + needToCheckEmptiness = false; + bounds.setHeight (0); + } + else + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); + + if (bottom < bounds.getHeight()) + bounds.setHeight (bottom); + + for (int i = top; --i >= 0;) + table [lineStrideElements * i] = 0; + + if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight()) + { + const int x1 = clipped.getX() << 8; + const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8; + int* line = table + lineStrideElements * top; + + for (int i = bottom - top; --i >= 0;) + { + if (line[0] != 0) + clipEdgeTableLineToRange (line, x1, x2); + + line += lineStrideElements; + } + } + + needToCheckEmptiness = true; + } +} + +void EdgeTable::excludeRectangle (const Rectangle& r) +{ + const Rectangle clipped (r.getIntersection (bounds)); + + if (! clipped.isEmpty()) + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); + + const int rectLine[] = { 4, std::numeric_limits::min(), 255, + clipped.getX() << 8, 0, + clipped.getRight() << 8, 255, + std::numeric_limits::max(), 0 }; + + for (int i = top; i < bottom; ++i) + intersectWithEdgeTableLine (i, rectLine); + + needToCheckEmptiness = true; + } +} + +void EdgeTable::clipToEdgeTable (const EdgeTable& other) +{ + const Rectangle clipped (other.bounds.getIntersection (bounds)); + + if (clipped.isEmpty()) + { + needToCheckEmptiness = false; + bounds.setHeight (0); + } + else + { + const int top = clipped.getY() - bounds.getY(); + const int bottom = clipped.getBottom() - bounds.getY(); + + if (bottom < bounds.getHeight()) + bounds.setHeight (bottom); + + if (clipped.getRight() < bounds.getRight()) + bounds.setRight (clipped.getRight()); + + for (int i = 0; i < top; ++i) + table [lineStrideElements * i] = 0; + + const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); + + for (int i = top; i < bottom; ++i) + { + intersectWithEdgeTableLine (i, otherLine); + otherLine += other.lineStrideElements; + } + + needToCheckEmptiness = true; + } +} + +void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) +{ + y -= bounds.getY(); + + if (y < 0 || y >= bounds.getHeight()) + return; + + needToCheckEmptiness = true; + + if (numPixels <= 0) + { + table [lineStrideElements * y] = 0; + return; + } + + int* tempLine = static_cast (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int))); + int destIndex = 0, lastLevel = 0; + + while (--numPixels >= 0) + { + const int alpha = *mask; + mask += maskStride; + + if (alpha != lastLevel) + { + tempLine[++destIndex] = (x << 8); + tempLine[++destIndex] = alpha; + lastLevel = alpha; + } + + ++x; + } + + if (lastLevel > 0) + { + tempLine[++destIndex] = (x << 8); + tempLine[++destIndex] = 0; + } + + tempLine[0] = destIndex >> 1; + + intersectWithEdgeTableLine (y, tempLine); +} + +bool EdgeTable::isEmpty() noexcept +{ + if (needToCheckEmptiness) + { + needToCheckEmptiness = false; + int* t = table; + + for (int i = bounds.getHeight(); --i >= 0;) + { + if (t[0] > 1) + return false; + + t += lineStrideElements; + } + + bounds.setHeight (0); + } + + return bounds.getHeight() == 0; +} diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.h new file mode 100644 index 0000000..d8b6d66 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.h @@ -0,0 +1,220 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_EDGETABLE_H_INCLUDED +#define JUCE_EDGETABLE_H_INCLUDED + + +//============================================================================== +/** + A table of horizontal scan-line segments - used for rasterising Paths. + + @see Path, Graphics +*/ +class JUCE_API EdgeTable +{ +public: + //============================================================================== + /** Creates an edge table containing a path. + + A table is created with a fixed vertical range, and only sections of the path + which lie within this range will be added to the table. + + @param clipLimits only the region of the path that lies within this area will be added + @param pathToAdd the path to add to the table + @param transform a transform to apply to the path being added + */ + EdgeTable (const Rectangle& clipLimits, + const Path& pathToAdd, + const AffineTransform& transform); + + /** Creates an edge table containing a rectangle. */ + explicit EdgeTable (const Rectangle& rectangleToAdd); + + /** Creates an edge table containing a rectangle list. */ + explicit EdgeTable (const RectangleList& rectanglesToAdd); + + /** Creates an edge table containing a rectangle list. */ + explicit EdgeTable (const RectangleList& rectanglesToAdd); + + /** Creates an edge table containing a rectangle. */ + explicit EdgeTable (const Rectangle& rectangleToAdd); + + /** Creates a copy of another edge table. */ + EdgeTable (const EdgeTable&); + + /** Copies from another edge table. */ + EdgeTable& operator= (const EdgeTable&); + + /** Destructor. */ + ~EdgeTable(); + + //============================================================================== + void clipToRectangle (const Rectangle& r); + void excludeRectangle (const Rectangle& r); + void clipToEdgeTable (const EdgeTable&); + void clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels); + bool isEmpty() noexcept; + const Rectangle& getMaximumBounds() const noexcept { return bounds; } + void translate (float dx, int dy) noexcept; + + /** Scales all the alpha-levels in the table by the given multiplier. */ + void multiplyLevels (float factor); + + /** Reduces the amount of space the table has allocated. + + This will shrink the table down to use as little memory as possible - useful for + read-only tables that get stored and re-used for rendering. + */ + void optimiseTable(); + + + //============================================================================== + /** Iterates the lines in the table, for rendering. + + This function will iterate each line in the table, and call a user-defined class + to render each pixel or continuous line of pixels that the table contains. + + @param iterationCallback this templated class must contain the following methods: + @code + inline void setEdgeTableYPos (int y); + inline void handleEdgeTablePixel (int x, int alphaLevel) const; + inline void handleEdgeTablePixelFull (int x) const; + inline void handleEdgeTableLine (int x, int width, int alphaLevel) const; + inline void handleEdgeTableLineFull (int x, int width) const; + @endcode + (these don't necessarily have to be 'const', but it might help it go faster) + */ + template + void iterate (EdgeTableIterationCallback& iterationCallback) const noexcept + { + const int* lineStart = table; + + for (int y = 0; y < bounds.getHeight(); ++y) + { + const int* line = lineStart; + lineStart += lineStrideElements; + int numPoints = line[0]; + + if (--numPoints > 0) + { + int x = *++line; + jassert ((x >> 8) >= bounds.getX() && (x >> 8) < bounds.getRight()); + int levelAccumulator = 0; + + iterationCallback.setEdgeTableYPos (bounds.getY() + y); + + while (--numPoints >= 0) + { + const int level = *++line; + jassert (isPositiveAndBelow (level, (int) 256)); + const int endX = *++line; + jassert (endX >= x); + const int endOfRun = (endX >> 8); + + if (endOfRun == (x >> 8)) + { + // small segment within the same pixel, so just save it for the next + // time round.. + levelAccumulator += (endX - x) * level; + } + else + { + // plot the fist pixel of this segment, including any accumulated + // levels from smaller segments that haven't been drawn yet + levelAccumulator += (0x100 - (x & 0xff)) * level; + levelAccumulator >>= 8; + x >>= 8; + + if (levelAccumulator > 0) + { + if (levelAccumulator >= 255) + iterationCallback.handleEdgeTablePixelFull (x); + else + iterationCallback.handleEdgeTablePixel (x, levelAccumulator); + } + + // if there's a run of similar pixels, do it all in one go.. + if (level > 0) + { + jassert (endOfRun <= bounds.getRight()); + const int numPix = endOfRun - ++x; + + if (numPix > 0) + iterationCallback.handleEdgeTableLine (x, numPix, level); + } + + // save the bit at the end to be drawn next time round the loop. + levelAccumulator = (endX & 0xff) * level; + } + + x = endX; + } + + levelAccumulator >>= 8; + + if (levelAccumulator > 0) + { + x >>= 8; + jassert (x >= bounds.getX() && x < bounds.getRight()); + + if (levelAccumulator >= 255) + iterationCallback.handleEdgeTablePixelFull (x); + else + iterationCallback.handleEdgeTablePixel (x, levelAccumulator); + } + } + } + } + +private: + //============================================================================== + // table line format: number of points; point0 x, point0 levelDelta, point1 x, point1 levelDelta, etc + struct LineItem + { + int x, level; + + bool operator< (const LineItem& other) const noexcept { return x < other.x; } + }; + + HeapBlock table; + Rectangle bounds; + int maxEdgesPerLine, lineStrideElements; + bool needToCheckEmptiness; + + void allocate(); + void clearLineSizes() noexcept; + void addEdgePoint (int x, int y, int winding); + void addEdgePointPair (int x1, int x2, int y, int winding); + void remapTableForNumEdges (int newNumEdgesPerLine); + void intersectWithEdgeTableLine (int y, const int* otherLine); + void clipEdgeTableLineToRange (int* line, int x1, int x2) noexcept; + void sanitiseLevels (bool useNonZeroWinding) noexcept; + static void copyEdgeTableData (int* dest, int destLineStride, const int* src, int srcLineStride, int numLines) noexcept; + + JUCE_LEAK_DETECTOR (EdgeTable) +}; + + +#endif // JUCE_EDGETABLE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h new file mode 100644 index 0000000..95665c5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h @@ -0,0 +1,416 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_LINE_H_INCLUDED +#define JUCE_LINE_H_INCLUDED + + +//============================================================================== +/** + Represents a line. + + This class contains a bunch of useful methods for various geometric + tasks. + + The ValueType template parameter should be a primitive type - float or double + are what it's designed for. Integer types will work in a basic way, but some methods + that perform mathematical operations may not compile, or they may not produce + sensible results. + + @see Point, Rectangle, Path, Graphics::drawLine +*/ +template +class Line +{ +public: + //============================================================================== + /** Creates a line, using (0, 0) as its start and end points. */ + Line() noexcept {} + + /** Creates a copy of another line. */ + Line (const Line& other) noexcept + : start (other.start), + end (other.end) + { + } + + /** Creates a line based on the coordinates of its start and end points. */ + Line (ValueType startX, ValueType startY, ValueType endX, ValueType endY) noexcept + : start (startX, startY), + end (endX, endY) + { + } + + /** Creates a line from its start and end points. */ + Line (const Point startPoint, + const Point endPoint) noexcept + : start (startPoint), + end (endPoint) + { + } + + /** Copies a line from another one. */ + Line& operator= (const Line& other) noexcept + { + start = other.start; + end = other.end; + return *this; + } + + /** Destructor. */ + ~Line() noexcept {} + + //============================================================================== + /** Returns the x coordinate of the line's start point. */ + inline ValueType getStartX() const noexcept { return start.x; } + + /** Returns the y coordinate of the line's start point. */ + inline ValueType getStartY() const noexcept { return start.y; } + + /** Returns the x coordinate of the line's end point. */ + inline ValueType getEndX() const noexcept { return end.x; } + + /** Returns the y coordinate of the line's end point. */ + inline ValueType getEndY() const noexcept { return end.y; } + + /** Returns the line's start point. */ + inline Point getStart() const noexcept { return start; } + + /** Returns the line's end point. */ + inline Point getEnd() const noexcept { return end; } + + /** Changes this line's start point */ + void setStart (ValueType newStartX, ValueType newStartY) noexcept { start.setXY (newStartX, newStartY); } + + /** Changes this line's end point */ + void setEnd (ValueType newEndX, ValueType newEndY) noexcept { end.setXY (newEndX, newEndY); } + + /** Changes this line's start point */ + void setStart (const Point newStart) noexcept { start = newStart; } + + /** Changes this line's end point */ + void setEnd (const Point newEnd) noexcept { end = newEnd; } + + /** Returns a line that is the same as this one, but with the start and end reversed, */ + const Line reversed() const noexcept { return Line (end, start); } + + /** Applies an affine transform to the line's start and end points. */ + void applyTransform (const AffineTransform& transform) noexcept + { + start.applyTransform (transform); + end.applyTransform (transform); + } + + //============================================================================== + /** Returns the length of the line. */ + ValueType getLength() const noexcept { return start.getDistanceFrom (end); } + + /** Returns true if the line's start and end x coordinates are the same. */ + bool isVertical() const noexcept { return start.x == end.x; } + + /** Returns true if the line's start and end y coordinates are the same. */ + bool isHorizontal() const noexcept { return start.y == end.y; } + + /** Returns the line's angle. + + This value is the number of radians clockwise from the 12 o'clock direction, + where the line's start point is considered to be at the centre. + */ + typename Point::FloatType getAngle() const noexcept { return start.getAngleToPoint (end); } + + /** Casts this line to float coordinates. */ + Line toFloat() const noexcept { return Line (start.toFloat(), end.toFloat()); } + + /** Casts this line to double coordinates. */ + Line toDouble() const noexcept { return Line (start.toDouble(), end.toDouble()); } + + //============================================================================== + /** Compares two lines. */ + bool operator== (const Line& other) const noexcept { return start == other.start && end == other.end; } + + /** Compares two lines. */ + bool operator!= (const Line& other) const noexcept { return start != other.start || end != other.end; } + + //============================================================================== + /** Finds the intersection between two lines. + + @param line the line to intersect with + @returns the point at which the lines intersect, even if this lies beyond the end of the lines + */ + Point getIntersection (const Line& line) const noexcept + { + Point p; + findIntersection (start, end, line.start, line.end, p); + return p; + } + + /** Finds the intersection between two lines. + + @param line the other line + @param intersection the position of the point where the lines meet (or + where they would meet if they were infinitely long) + the intersection (if the lines intersect). If the lines + are parallel, this will just be set to the position + of one of the line's endpoints. + @returns true if the line segments intersect; false if they dont. Even if they + don't intersect, the intersection coordinates returned will still + be valid + */ + bool intersects (const Line& line, Point& intersection) const noexcept + { + return findIntersection (start, end, line.start, line.end, intersection); + } + + /** Returns true if this line intersects another. */ + bool intersects (const Line& other) const noexcept + { + Point ignored; + return findIntersection (start, end, other.start, other.end, ignored); + } + + //============================================================================== + /** Returns the location of the point which is a given distance along this line. + + @param distanceFromStart the distance to move along the line from its + start point. This value can be negative or longer + than the line itself + @see getPointAlongLineProportionally + */ + Point getPointAlongLine (ValueType distanceFromStart) const noexcept + { + return start + (end - start) * (distanceFromStart / getLength()); + } + + /** Returns a point which is a certain distance along and to the side of this line. + + This effectively moves a given distance along the line, then another distance + perpendicularly to this, and returns the resulting position. + + @param distanceFromStart the distance to move along the line from its + start point. This value can be negative or longer + than the line itself + @param perpendicularDistance how far to move sideways from the line. If you're + looking along the line from its start towards its + end, then a positive value here will move to the + right, negative value move to the left. + */ + Point getPointAlongLine (ValueType distanceFromStart, + ValueType perpendicularDistance) const noexcept + { + const Point delta (end - start); + const double length = juce_hypot ((double) delta.x, + (double) delta.y); + if (length <= 0) + return start; + + return Point (start.x + static_cast ((delta.x * distanceFromStart - delta.y * perpendicularDistance) / length), + start.y + static_cast ((delta.y * distanceFromStart + delta.x * perpendicularDistance) / length)); + } + + /** Returns the location of the point which is a given distance along this line + proportional to the line's length. + + @param proportionOfLength the distance to move along the line from its + start point, in multiples of the line's length. + So a value of 0.0 will return the line's start point + and a value of 1.0 will return its end point. (This value + can be negative or greater than 1.0). + @see getPointAlongLine + */ + Point getPointAlongLineProportionally (ValueType proportionOfLength) const noexcept + { + return start + (end - start) * proportionOfLength; + } + + /** Returns the smallest distance between this line segment and a given point. + + So if the point is close to the line, this will return the perpendicular + distance from the line; if the point is a long way beyond one of the line's + end-point's, it'll return the straight-line distance to the nearest end-point. + + pointOnLine receives the position of the point that is found. + + @returns the point's distance from the line + @see getPositionAlongLineOfNearestPoint + */ + ValueType getDistanceFromPoint (const Point targetPoint, + Point& pointOnLine) const noexcept + { + const Point delta (end - start); + const double length = delta.x * delta.x + delta.y * delta.y; + + if (length > 0) + { + const double prop = ((targetPoint.x - start.x) * delta.x + + (targetPoint.y - start.y) * delta.y) / length; + + if (prop >= 0 && prop <= 1.0) + { + pointOnLine = start + delta * static_cast (prop); + return targetPoint.getDistanceFrom (pointOnLine); + } + } + + const float fromStart = targetPoint.getDistanceFrom (start); + const float fromEnd = targetPoint.getDistanceFrom (end); + + if (fromStart < fromEnd) + { + pointOnLine = start; + return fromStart; + } + else + { + pointOnLine = end; + return fromEnd; + } + } + + /** Finds the point on this line which is nearest to a given point, and + returns its position as a proportional position along the line. + + @returns a value 0 to 1.0 which is the distance along this line from the + line's start to the point which is nearest to the point passed-in. To + turn this number into a position, use getPointAlongLineProportionally(). + @see getDistanceFromPoint, getPointAlongLineProportionally + */ + ValueType findNearestProportionalPositionTo (const Point point) const noexcept + { + const Point delta (end - start); + const double length = delta.x * delta.x + delta.y * delta.y; + + return length <= 0 ? 0 + : jlimit (ValueType(), static_cast (1), + static_cast ((((point.x - start.x) * delta.x + + (point.y - start.y) * delta.y) / length))); + } + + /** Finds the point on this line which is nearest to a given point. + @see getDistanceFromPoint, findNearestProportionalPositionTo + */ + Point findNearestPointTo (const Point point) const noexcept + { + return getPointAlongLineProportionally (findNearestProportionalPositionTo (point)); + } + + /** Returns true if the given point lies above this line. + + The return value is true if the point's y coordinate is less than the y + coordinate of this line at the given x (assuming the line extends infinitely + in both directions). + */ + bool isPointAbove (const Point point) const noexcept + { + return start.x != end.x + && point.y < ((end.y - start.y) + * (point.x - start.x)) / (end.x - start.x) + start.y; + } + + //============================================================================== + /** Returns a shortened copy of this line. + + This will chop off part of the start of this line by a certain amount, (leaving the + end-point the same), and return the new line. + */ + Line withShortenedStart (ValueType distanceToShortenBy) const noexcept + { + return Line (getPointAlongLine (jmin (distanceToShortenBy, getLength())), end); + } + + /** Returns a shortened copy of this line. + + This will chop off part of the end of this line by a certain amount, (leaving the + start-point the same), and return the new line. + */ + Line withShortenedEnd (ValueType distanceToShortenBy) const noexcept + { + const ValueType length = getLength(); + return Line (start, getPointAlongLine (length - jmin (distanceToShortenBy, length))); + } + +private: + //============================================================================== + Point start, end; + + static bool findIntersection (const Point p1, const Point p2, + const Point p3, const Point p4, + Point& intersection) noexcept + { + if (p2 == p3) + { + intersection = p2; + return true; + } + + const Point d1 (p2 - p1); + const Point d2 (p4 - p3); + const ValueType divisor = d1.x * d2.y - d2.x * d1.y; + + if (divisor == 0) + { + if (! (d1.isOrigin() || d2.isOrigin())) + { + if (d1.y == 0 && d2.y != 0) + { + const ValueType along = (p1.y - p3.y) / d2.y; + intersection = p1.withX (p3.x + along * d2.x); + return along >= 0 && along <= static_cast (1); + } + else if (d2.y == 0 && d1.y != 0) + { + const ValueType along = (p3.y - p1.y) / d1.y; + intersection = p3.withX (p1.x + along * d1.x); + return along >= 0 && along <= static_cast (1); + } + else if (d1.x == 0 && d2.x != 0) + { + const ValueType along = (p1.x - p3.x) / d2.x; + intersection = p1.withY (p3.y + along * d2.y); + return along >= 0 && along <= static_cast (1); + } + else if (d2.x == 0 && d1.x != 0) + { + const ValueType along = (p3.x - p1.x) / d1.x; + intersection = p3.withY (p1.y + along * d1.y); + return along >= 0 && along <= static_cast (1); + } + } + + intersection = (p2 + p3) / static_cast (2); + return false; + } + + const ValueType along1 = ((p1.y - p3.y) * d2.x - (p1.x - p3.x) * d2.y) / divisor; + intersection = p1 + d1 * along1; + + if (along1 < 0 || along1 > static_cast (1)) + return false; + + const ValueType along2 = ((p1.y - p3.y) * d1.x - (p1.x - p3.x) * d1.y) / divisor; + return along2 >= 0 && along2 <= static_cast (1); + } +}; + + +#endif // JUCE_LINE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp new file mode 100644 index 0000000..65c211f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp @@ -0,0 +1,1615 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +// tests that some coordinates aren't NaNs +#define JUCE_CHECK_COORDS_ARE_VALID(x, y) \ + jassert (x == x && y == y); + +//============================================================================== +namespace PathHelpers +{ + const float ellipseAngularIncrement = 0.05f; + + static String nextToken (String::CharPointerType& t) + { + t = t.findEndOfWhitespace(); + + String::CharPointerType start (t); + size_t numChars = 0; + + while (! (t.isEmpty() || t.isWhitespace())) + { + ++t; + ++numChars; + } + + return String (start, numChars); + } + + inline double lengthOf (float x1, float y1, float x2, float y2) noexcept + { + return juce_hypot ((double) (x1 - x2), (double) (y1 - y2)); + } +} + +//============================================================================== +const float Path::lineMarker = 100001.0f; +const float Path::moveMarker = 100002.0f; +const float Path::quadMarker = 100003.0f; +const float Path::cubicMarker = 100004.0f; +const float Path::closeSubPathMarker = 100005.0f; + +//============================================================================== +Path::PathBounds::PathBounds() noexcept + : pathXMin (0), pathXMax (0), pathYMin (0), pathYMax (0) +{ +} + +Rectangle Path::PathBounds::getRectangle() const noexcept +{ + return Rectangle (pathXMin, pathYMin, pathXMax - pathXMin, pathYMax - pathYMin); +} + +void Path::PathBounds::reset() noexcept +{ + pathXMin = pathYMin = pathYMax = pathXMax = 0; +} + +void Path::PathBounds::reset (const float x, const float y) noexcept +{ + pathXMin = pathXMax = x; + pathYMin = pathYMax = y; +} + +void Path::PathBounds::extend (const float x, const float y) noexcept +{ + pathXMin = jmin (pathXMin, x); + pathXMax = jmax (pathXMax, x); + pathYMin = jmin (pathYMin, y); + pathYMax = jmax (pathYMax, y); +} + +void Path::PathBounds::extend (const float x1, const float y1, const float x2, const float y2) noexcept +{ + if (x1 < x2) + { + pathXMin = jmin (pathXMin, x1); + pathXMax = jmax (pathXMax, x2); + } + else + { + pathXMin = jmin (pathXMin, x2); + pathXMax = jmax (pathXMax, x1); + } + + if (y1 < y2) + { + pathYMin = jmin (pathYMin, y1); + pathYMax = jmax (pathYMax, y2); + } + else + { + pathYMin = jmin (pathYMin, y2); + pathYMax = jmax (pathYMax, y1); + } +} + +//============================================================================== +Path::Path() + : numElements (0), useNonZeroWinding (true) +{ +} + +Path::~Path() +{ +} + +Path::Path (const Path& other) + : numElements (other.numElements), + bounds (other.bounds), + useNonZeroWinding (other.useNonZeroWinding) +{ + if (numElements > 0) + { + data.setAllocatedSize ((int) numElements); + memcpy (data.elements, other.data.elements, numElements * sizeof (float)); + } +} + +Path& Path::operator= (const Path& other) +{ + if (this != &other) + { + data.ensureAllocatedSize ((int) other.numElements); + + numElements = other.numElements; + bounds = other.bounds; + useNonZeroWinding = other.useNonZeroWinding; + + if (numElements > 0) + memcpy (data.elements, other.data.elements, numElements * sizeof (float)); + } + + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Path::Path (Path&& other) noexcept + : data (static_cast &&> (other.data)), + numElements (other.numElements), + bounds (other.bounds), + useNonZeroWinding (other.useNonZeroWinding) +{ +} + +Path& Path::operator= (Path&& other) noexcept +{ + data = static_cast &&> (other.data); + numElements = other.numElements; + bounds = other.bounds; + useNonZeroWinding = other.useNonZeroWinding; + return *this; +} +#endif + +bool Path::operator== (const Path& other) const noexcept +{ + return ! operator!= (other); +} + +bool Path::operator!= (const Path& other) const noexcept +{ + if (numElements != other.numElements || useNonZeroWinding != other.useNonZeroWinding) + return true; + + for (size_t i = 0; i < numElements; ++i) + if (data.elements[i] != other.data.elements[i]) + return true; + + return false; +} + +void Path::clear() noexcept +{ + numElements = 0; + bounds.reset(); +} + +void Path::swapWithPath (Path& other) noexcept +{ + data.swapWith (other.data); + std::swap (numElements, other.numElements); + std::swap (bounds.pathXMin, other.bounds.pathXMin); + std::swap (bounds.pathXMax, other.bounds.pathXMax); + std::swap (bounds.pathYMin, other.bounds.pathYMin); + std::swap (bounds.pathYMax, other.bounds.pathYMax); + std::swap (useNonZeroWinding, other.useNonZeroWinding); +} + +//============================================================================== +void Path::setUsingNonZeroWinding (const bool isNonZero) noexcept +{ + useNonZeroWinding = isNonZero; +} + +void Path::scaleToFit (const float x, const float y, const float w, const float h, + const bool preserveProportions) noexcept +{ + applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions)); +} + +//============================================================================== +bool Path::isEmpty() const noexcept +{ + size_t i = 0; + + while (i < numElements) + { + const float type = data.elements [i++]; + + if (type == moveMarker) + { + i += 2; + } + else if (type == lineMarker + || type == quadMarker + || type == cubicMarker) + { + return false; + } + } + + return true; +} + +Rectangle Path::getBounds() const noexcept +{ + return bounds.getRectangle(); +} + +Rectangle Path::getBoundsTransformed (const AffineTransform& transform) const noexcept +{ + return getBounds().transformedBy (transform); +} + +//============================================================================== +void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor) +{ + data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor); +} + +void Path::startNewSubPath (const float x, const float y) +{ + JUCE_CHECK_COORDS_ARE_VALID (x, y); + + if (numElements == 0) + bounds.reset (x, y); + else + bounds.extend (x, y); + + preallocateSpace (3); + + data.elements [numElements++] = moveMarker; + data.elements [numElements++] = x; + data.elements [numElements++] = y; +} + +void Path::startNewSubPath (const Point start) +{ + startNewSubPath (start.x, start.y); +} + +void Path::lineTo (const float x, const float y) +{ + JUCE_CHECK_COORDS_ARE_VALID (x, y); + + if (numElements == 0) + startNewSubPath (0, 0); + + preallocateSpace (3); + + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x; + data.elements [numElements++] = y; + + bounds.extend (x, y); +} + +void Path::lineTo (const Point end) +{ + lineTo (end.x, end.y); +} + +void Path::quadraticTo (const float x1, const float y1, + const float x2, const float y2) +{ + JUCE_CHECK_COORDS_ARE_VALID (x1, y1); + JUCE_CHECK_COORDS_ARE_VALID (x2, y2); + + if (numElements == 0) + startNewSubPath (0, 0); + + preallocateSpace (5); + + data.elements [numElements++] = quadMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + + bounds.extend (x1, y1, x2, y2); +} + +void Path::quadraticTo (const Point controlPoint, + const Point endPoint) +{ + quadraticTo (controlPoint.x, controlPoint.y, + endPoint.x, endPoint.y); +} + +void Path::cubicTo (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3) +{ + JUCE_CHECK_COORDS_ARE_VALID (x1, y1); + JUCE_CHECK_COORDS_ARE_VALID (x2, y2); + JUCE_CHECK_COORDS_ARE_VALID (x3, y3); + + if (numElements == 0) + startNewSubPath (0, 0); + + preallocateSpace (7); + + data.elements [numElements++] = cubicMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + data.elements [numElements++] = x3; + data.elements [numElements++] = y3; + + bounds.extend (x1, y1, x2, y2); + bounds.extend (x3, y3); +} + +void Path::cubicTo (const Point controlPoint1, + const Point controlPoint2, + const Point endPoint) +{ + cubicTo (controlPoint1.x, controlPoint1.y, + controlPoint2.x, controlPoint2.y, + endPoint.x, endPoint.y); +} + +void Path::closeSubPath() +{ + if (numElements > 0 + && data.elements [numElements - 1] != closeSubPathMarker) + { + preallocateSpace (1); + data.elements [numElements++] = closeSubPathMarker; + } +} + +Point Path::getCurrentPosition() const +{ + int i = (int) numElements - 1; + + if (i > 0 && data.elements[i] == closeSubPathMarker) + { + while (i >= 0) + { + if (data.elements[i] == moveMarker) + { + i += 2; + break; + } + + --i; + } + } + + if (i > 0) + return Point (data.elements [i - 1], data.elements [i]); + + return Point(); +} + +void Path::addRectangle (const float x, const float y, + const float w, const float h) +{ + float x1 = x, y1 = y, x2 = x + w, y2 = y + h; + + if (w < 0) std::swap (x1, x2); + if (h < 0) std::swap (y1, y2); + + preallocateSpace (13); + + if (numElements == 0) + { + bounds.pathXMin = x1; + bounds.pathXMax = x2; + bounds.pathYMin = y1; + bounds.pathYMax = y2; + } + else + { + bounds.pathXMin = jmin (bounds.pathXMin, x1); + bounds.pathXMax = jmax (bounds.pathXMax, x2); + bounds.pathYMin = jmin (bounds.pathYMin, y1); + bounds.pathYMax = jmax (bounds.pathYMax, y2); + } + + data.elements [numElements++] = moveMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y2; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x1; + data.elements [numElements++] = y1; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x2; + data.elements [numElements++] = y1; + data.elements [numElements++] = lineMarker; + data.elements [numElements++] = x2; + data.elements [numElements++] = y2; + data.elements [numElements++] = closeSubPathMarker; +} + +void Path::addRoundedRectangle (float x, float y, float w, float h, float csx, float csy) +{ + addRoundedRectangle (x, y, w, h, csx, csy, true, true, true, true); +} + +void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, + float csx, float csy, + const bool curveTopLeft, const bool curveTopRight, + const bool curveBottomLeft, const bool curveBottomRight) +{ + csx = jmin (csx, w * 0.5f); + csy = jmin (csy, h * 0.5f); + const float cs45x = csx * 0.45f; + const float cs45y = csy * 0.45f; + const float x2 = x + w; + const float y2 = y + h; + + if (curveTopLeft) + { + startNewSubPath (x, y + csy); + cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); + } + else + { + startNewSubPath (x, y); + } + + if (curveTopRight) + { + lineTo (x2 - csx, y); + cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); + } + else + { + lineTo (x2, y); + } + + if (curveBottomRight) + { + lineTo (x2, y2 - csy); + cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); + } + else + { + lineTo (x2, y2); + } + + if (curveBottomLeft) + { + lineTo (x + csx, y2); + cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); + } + else + { + lineTo (x, y2); + } + + closeSubPath(); +} + +void Path::addRoundedRectangle (float x, float y, float w, float h, float cs) +{ + addRoundedRectangle (x, y, w, h, cs, cs); +} + +void Path::addTriangle (float x1, float y1, + float x2, float y2, + float x3, float y3) +{ + addTriangle (Point (x1, y1), + Point (x2, y2), + Point (x3, y3)); +} + +void Path::addTriangle (Point p1, Point p2, Point p3) +{ + startNewSubPath (p1); + lineTo (p2); + lineTo (p3); + closeSubPath(); +} + +void Path::addQuadrilateral (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4) +{ + startNewSubPath (x1, y1); + lineTo (x2, y2); + lineTo (x3, y3); + lineTo (x4, y4); + closeSubPath(); +} + +void Path::addEllipse (float x, float y, float w, float h) +{ + addEllipse (Rectangle (x, y, w, h)); +} + +void Path::addEllipse (Rectangle area) +{ + const float hw = area.getWidth() * 0.5f; + const float hw55 = hw * 0.55f; + const float hh = area.getHeight() * 0.5f; + const float hh55 = hh * 0.55f; + const float cx = area.getX() + hw; + const float cy = area.getY() + hh; + + startNewSubPath (cx, cy - hh); + cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); + cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh); + cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy); + cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh); + closeSubPath(); +} + +void Path::addArc (const float x, const float y, + const float w, const float h, + const float fromRadians, + const float toRadians, + const bool startAsNewSubPath) +{ + const float radiusX = w / 2.0f; + const float radiusY = h / 2.0f; + + addCentredArc (x + radiusX, + y + radiusY, + radiusX, radiusY, + 0.0f, + fromRadians, toRadians, + startAsNewSubPath); +} + +void Path::addCentredArc (const float centreX, const float centreY, + const float radiusX, const float radiusY, + const float rotationOfEllipse, + const float fromRadians, + float toRadians, + const bool startAsNewSubPath) +{ + if (radiusX > 0.0f && radiusY > 0.0f) + { + const Point centre (centreX, centreY); + const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); + float angle = fromRadians; + + if (startAsNewSubPath) + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + + if (fromRadians < toRadians) + { + if (startAsNewSubPath) + angle += PathHelpers::ellipseAngularIncrement; + + while (angle < toRadians) + { + lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + angle += PathHelpers::ellipseAngularIncrement; + } + } + else + { + if (startAsNewSubPath) + angle -= PathHelpers::ellipseAngularIncrement; + + while (angle > toRadians) + { + lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); + angle -= PathHelpers::ellipseAngularIncrement; + } + } + + lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation)); + } +} + +void Path::addPieSegment (const float x, const float y, + const float width, const float height, + const float fromRadians, + const float toRadians, + const float innerCircleProportionalSize) +{ + float radiusX = width * 0.5f; + float radiusY = height * 0.5f; + const Point centre (x + radiusX, y + radiusY); + + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians)); + addArc (x, y, width, height, fromRadians, toRadians); + + if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f) + { + closeSubPath(); + + if (innerCircleProportionalSize > 0) + { + radiusX *= innerCircleProportionalSize; + radiusY *= innerCircleProportionalSize; + + startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians)); + addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); + } + } + else + { + if (innerCircleProportionalSize > 0) + { + radiusX *= innerCircleProportionalSize; + radiusY *= innerCircleProportionalSize; + + addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); + } + else + { + lineTo (centre); + } + } + + closeSubPath(); +} + +//============================================================================== +void Path::addLineSegment (const Line& line, float lineThickness) +{ + const Line reversed (line.reversed()); + lineThickness *= 0.5f; + + startNewSubPath (line.getPointAlongLine (0, lineThickness)); + lineTo (line.getPointAlongLine (0, -lineThickness)); + lineTo (reversed.getPointAlongLine (0, lineThickness)); + lineTo (reversed.getPointAlongLine (0, -lineThickness)); + closeSubPath(); +} + +void Path::addArrow (const Line& line, float lineThickness, + float arrowheadWidth, float arrowheadLength) +{ + const Line reversed (line.reversed()); + lineThickness *= 0.5f; + arrowheadWidth *= 0.5f; + arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength()); + + startNewSubPath (line.getPointAlongLine (0, lineThickness)); + lineTo (line.getPointAlongLine (0, -lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); + lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); + lineTo (line.getEnd()); + lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); + lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness)); + closeSubPath(); +} + +void Path::addPolygon (const Point centre, const int numberOfSides, + const float radius, const float startAngle) +{ + jassert (numberOfSides > 1); // this would be silly. + + if (numberOfSides > 1) + { + const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; + + for (int i = 0; i < numberOfSides; ++i) + { + const float angle = startAngle + i * angleBetweenPoints; + const Point p (centre.getPointOnCircumference (radius, angle)); + + if (i == 0) + startNewSubPath (p); + else + lineTo (p); + } + + closeSubPath(); + } +} + +void Path::addStar (const Point centre, const int numberOfPoints, + const float innerRadius, const float outerRadius, const float startAngle) +{ + jassert (numberOfPoints > 1); // this would be silly. + + if (numberOfPoints > 1) + { + const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints; + + for (int i = 0; i < numberOfPoints; ++i) + { + const float angle = startAngle + i * angleBetweenPoints; + const Point p (centre.getPointOnCircumference (outerRadius, angle)); + + if (i == 0) + startNewSubPath (p); + else + lineTo (p); + + lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f)); + } + + closeSubPath(); + } +} + +void Path::addBubble (const Rectangle& bodyArea, + const Rectangle& maximumArea, + const Point arrowTip, + const float cornerSize, + const float arrowBaseWidth) +{ + const float halfW = bodyArea.getWidth() / 2.0f; + const float halfH = bodyArea.getHeight() / 2.0f; + const float cornerSizeW = jmin (cornerSize, halfW); + const float cornerSizeH = jmin (cornerSize, halfH); + const float cornerSizeW2 = 2.0f * cornerSizeW; + const float cornerSizeH2 = 2.0f * cornerSizeH; + + startNewSubPath (bodyArea.getX() + cornerSizeW, bodyArea.getY()); + + const Rectangle targetLimit (bodyArea.reduced (jmin (halfW - 1.0f, cornerSizeW + arrowBaseWidth), + jmin (halfH - 1.0f, cornerSizeH + arrowBaseWidth))); + + if (Rectangle (targetLimit.getX(), maximumArea.getY(), + targetLimit.getWidth(), bodyArea.getY() - maximumArea.getY()).contains (arrowTip)) + { + lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getY()); + lineTo (arrowTip.x, arrowTip.y); + lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getY()); + } + + lineTo (bodyArea.getRight() - cornerSizeW, bodyArea.getY()); + addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getY(), cornerSizeW2, cornerSizeH2, 0, float_Pi * 0.5f); + + if (Rectangle (bodyArea.getRight(), targetLimit.getY(), + maximumArea.getRight() - bodyArea.getRight(), targetLimit.getHeight()).contains (arrowTip)) + { + lineTo (bodyArea.getRight(), arrowTip.y - arrowBaseWidth); + lineTo (arrowTip.x, arrowTip.y); + lineTo (bodyArea.getRight(), arrowTip.y + arrowBaseWidth); + } + + lineTo (bodyArea.getRight(), bodyArea.getBottom() - cornerSizeH); + addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi * 0.5f, float_Pi); + + if (Rectangle (targetLimit.getX(), bodyArea.getBottom(), + targetLimit.getWidth(), maximumArea.getBottom() - bodyArea.getBottom()).contains (arrowTip)) + { + lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getBottom()); + lineTo (arrowTip.x, arrowTip.y); + lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getBottom()); + } + + lineTo (bodyArea.getX() + cornerSizeW, bodyArea.getBottom()); + addArc (bodyArea.getX(), bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi, float_Pi * 1.5f); + + if (Rectangle (maximumArea.getX(), targetLimit.getY(), + bodyArea.getX() - maximumArea.getX(), targetLimit.getHeight()).contains (arrowTip)) + { + lineTo (bodyArea.getX(), arrowTip.y + arrowBaseWidth); + lineTo (arrowTip.x, arrowTip.y); + lineTo (bodyArea.getX(), arrowTip.y - arrowBaseWidth); + } + + lineTo (bodyArea.getX(), bodyArea.getY() + cornerSizeH); + addArc (bodyArea.getX(), bodyArea.getY(), cornerSizeW2, cornerSizeH2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f); + + closeSubPath(); +} + +void Path::addPath (const Path& other) +{ + size_t i = 0; + const float* const d = other.data.elements; + + while (i < other.numElements) + { + const float type = d[i++]; + + if (type == moveMarker) + { + startNewSubPath (d[i], d[i + 1]); + i += 2; + } + else if (type == lineMarker) + { + lineTo (d[i], d[i + 1]); + i += 2; + } + else if (type == quadMarker) + { + quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]); + i += 4; + } + else if (type == cubicMarker) + { + cubicTo (d[i], d[i + 1], d[i + 2], d[i + 3], d[i + 4], d[i + 5]); + i += 6; + } + else if (type == closeSubPathMarker) + { + closeSubPath(); + } + else + { + // something's gone wrong with the element list! + jassertfalse; + } + } +} + +void Path::addPath (const Path& other, + const AffineTransform& transformToApply) +{ + size_t i = 0; + const float* const d = other.data.elements; + + while (i < other.numElements) + { + const float type = d [i++]; + + if (type == closeSubPathMarker) + { + closeSubPath(); + } + else + { + float x = d[i++]; + float y = d[i++]; + transformToApply.transformPoint (x, y); + + if (type == moveMarker) + { + startNewSubPath (x, y); + } + else if (type == lineMarker) + { + lineTo (x, y); + } + else if (type == quadMarker) + { + float x2 = d [i++]; + float y2 = d [i++]; + transformToApply.transformPoint (x2, y2); + + quadraticTo (x, y, x2, y2); + } + else if (type == cubicMarker) + { + float x2 = d [i++]; + float y2 = d [i++]; + float x3 = d [i++]; + float y3 = d [i++]; + transformToApply.transformPoints (x2, y2, x3, y3); + + cubicTo (x, y, x2, y2, x3, y3); + } + else + { + // something's gone wrong with the element list! + jassertfalse; + } + } + } +} + +//============================================================================== +void Path::applyTransform (const AffineTransform& transform) noexcept +{ + bounds.reset(); + bool firstPoint = true; + float* d = data.elements; + float* const end = d + numElements; + + while (d < end) + { + const float type = *d++; + + if (type == moveMarker) + { + transform.transformPoint (d[0], d[1]); + + if (firstPoint) + { + firstPoint = false; + bounds.reset (d[0], d[1]); + } + else + { + bounds.extend (d[0], d[1]); + } + + d += 2; + } + else if (type == lineMarker) + { + transform.transformPoint (d[0], d[1]); + bounds.extend (d[0], d[1]); + d += 2; + } + else if (type == quadMarker) + { + transform.transformPoints (d[0], d[1], d[2], d[3]); + bounds.extend (d[0], d[1], d[2], d[3]); + d += 4; + } + else if (type == cubicMarker) + { + transform.transformPoints (d[0], d[1], d[2], d[3], d[4], d[5]); + bounds.extend (d[0], d[1], d[2], d[3]); + bounds.extend (d[4], d[5]); + d += 6; + } + } +} + + +//============================================================================== +AffineTransform Path::getTransformToScaleToFit (const Rectangle& area, + bool preserveProportions, Justification justification) const +{ + return getTransformToScaleToFit (area.getX(), area.getY(), area.getWidth(), area.getHeight(), + preserveProportions, justification); +} + +AffineTransform Path::getTransformToScaleToFit (const float x, const float y, + const float w, const float h, + const bool preserveProportions, + Justification justification) const +{ + Rectangle boundsRect (getBounds()); + + if (preserveProportions) + { + if (w <= 0 || h <= 0 || boundsRect.isEmpty()) + return AffineTransform::identity; + + float newW, newH; + const float srcRatio = boundsRect.getHeight() / boundsRect.getWidth(); + + if (srcRatio > h / w) + { + newW = h / srcRatio; + newH = h; + } + else + { + newW = w; + newH = w * srcRatio; + } + + float newXCentre = x; + float newYCentre = y; + + if (justification.testFlags (Justification::left)) newXCentre += newW * 0.5f; + else if (justification.testFlags (Justification::right)) newXCentre += w - newW * 0.5f; + else newXCentre += w * 0.5f; + + if (justification.testFlags (Justification::top)) newYCentre += newH * 0.5f; + else if (justification.testFlags (Justification::bottom)) newYCentre += h - newH * 0.5f; + else newYCentre += h * 0.5f; + + return AffineTransform::translation (boundsRect.getWidth() * -0.5f - boundsRect.getX(), + boundsRect.getHeight() * -0.5f - boundsRect.getY()) + .scaled (newW / boundsRect.getWidth(), + newH / boundsRect.getHeight()) + .translated (newXCentre, newYCentre); + } + else + { + return AffineTransform::translation (-boundsRect.getX(), -boundsRect.getY()) + .scaled (w / boundsRect.getWidth(), + h / boundsRect.getHeight()) + .translated (x, y); + } +} + +//============================================================================== +bool Path::contains (const float x, const float y, const float tolerance) const +{ + if (x <= bounds.pathXMin || x >= bounds.pathXMax + || y <= bounds.pathYMin || y >= bounds.pathYMax) + return false; + + PathFlatteningIterator i (*this, AffineTransform::identity, tolerance); + + int positiveCrossings = 0; + int negativeCrossings = 0; + + while (i.next()) + { + if ((i.y1 <= y && i.y2 > y) || (i.y2 <= y && i.y1 > y)) + { + const float intersectX = i.x1 + (i.x2 - i.x1) * (y - i.y1) / (i.y2 - i.y1); + + if (intersectX <= x) + { + if (i.y1 < i.y2) + ++positiveCrossings; + else + ++negativeCrossings; + } + } + } + + return useNonZeroWinding ? (negativeCrossings != positiveCrossings) + : ((negativeCrossings + positiveCrossings) & 1) != 0; +} + +bool Path::contains (const Point point, const float tolerance) const +{ + return contains (point.x, point.y, tolerance); +} + +bool Path::intersectsLine (const Line& line, const float tolerance) +{ + PathFlatteningIterator i (*this, AffineTransform::identity, tolerance); + Point intersection; + + while (i.next()) + if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) + return true; + + return false; +} + +Line Path::getClippedLine (const Line& line, const bool keepSectionOutsidePath) const +{ + Line result (line); + const bool startInside = contains (line.getStart()); + const bool endInside = contains (line.getEnd()); + + if (startInside == endInside) + { + if (keepSectionOutsidePath == startInside) + result = Line(); + } + else + { + PathFlatteningIterator i (*this, AffineTransform::identity); + Point intersection; + + while (i.next()) + { + if (line.intersects (Line (i.x1, i.y1, i.x2, i.y2), intersection)) + { + if ((startInside && keepSectionOutsidePath) || (endInside && ! keepSectionOutsidePath)) + result.setStart (intersection); + else + result.setEnd (intersection); + } + } + } + + return result; +} + +float Path::getLength (const AffineTransform& transform) const +{ + float length = 0; + PathFlatteningIterator i (*this, transform); + + while (i.next()) + length += Line (i.x1, i.y1, i.x2, i.y2).getLength(); + + return length; +} + +Point Path::getPointAlongPath (float distanceFromStart, const AffineTransform& transform) const +{ + PathFlatteningIterator i (*this, transform); + + while (i.next()) + { + const Line line (i.x1, i.y1, i.x2, i.y2); + const float lineLength = line.getLength(); + + if (distanceFromStart <= lineLength) + return line.getPointAlongLine (distanceFromStart); + + distanceFromStart -= lineLength; + } + + return Point (i.x2, i.y2); +} + +float Path::getNearestPoint (const Point targetPoint, Point& pointOnPath, + const AffineTransform& transform) const +{ + PathFlatteningIterator i (*this, transform); + float bestPosition = 0, bestDistance = std::numeric_limits::max(); + float length = 0; + Point pointOnLine; + + while (i.next()) + { + const Line line (i.x1, i.y1, i.x2, i.y2); + const float distance = line.getDistanceFromPoint (targetPoint, pointOnLine); + + if (distance < bestDistance) + { + bestDistance = distance; + bestPosition = length + pointOnLine.getDistanceFrom (line.getStart()); + pointOnPath = pointOnLine; + } + + length += line.getLength(); + } + + return bestPosition; +} + +//============================================================================== +Path Path::createPathWithRoundedCorners (const float cornerRadius) const +{ + if (cornerRadius <= 0.01f) + return *this; + + size_t indexOfPathStart = 0, indexOfPathStartThis = 0; + size_t n = 0; + bool lastWasLine = false, firstWasLine = false; + Path p; + + while (n < numElements) + { + const float type = data.elements [n++]; + + if (type == moveMarker) + { + indexOfPathStart = p.numElements; + indexOfPathStartThis = n - 1; + const float x = data.elements [n++]; + const float y = data.elements [n++]; + p.startNewSubPath (x, y); + lastWasLine = false; + firstWasLine = (data.elements [n] == lineMarker); + } + else if (type == lineMarker || type == closeSubPathMarker) + { + float startX = 0, startY = 0, joinX = 0, joinY = 0, endX, endY; + + if (type == lineMarker) + { + endX = data.elements [n++]; + endY = data.elements [n++]; + + if (n > 8) + { + startX = data.elements [n - 8]; + startY = data.elements [n - 7]; + joinX = data.elements [n - 5]; + joinY = data.elements [n - 4]; + } + } + else + { + endX = data.elements [indexOfPathStartThis + 1]; + endY = data.elements [indexOfPathStartThis + 2]; + + if (n > 6) + { + startX = data.elements [n - 6]; + startY = data.elements [n - 5]; + joinX = data.elements [n - 3]; + joinY = data.elements [n - 2]; + } + } + + if (lastWasLine) + { + const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY); + + if (len1 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len1); + + p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); + p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); + } + + const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY); + + if (len2 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len2); + + p.quadraticTo (joinX, joinY, + (float) (joinX + (endX - joinX) * propNeeded), + (float) (joinY + (endY - joinY) * propNeeded)); + } + + p.lineTo (endX, endY); + } + else if (type == lineMarker) + { + p.lineTo (endX, endY); + lastWasLine = true; + } + + if (type == closeSubPathMarker) + { + if (firstWasLine) + { + startX = data.elements [n - 3]; + startY = data.elements [n - 2]; + joinX = endX; + joinY = endY; + endX = data.elements [indexOfPathStartThis + 4]; + endY = data.elements [indexOfPathStartThis + 5]; + + const double len1 = PathHelpers::lengthOf (startX, startY, joinX, joinY); + + if (len1 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len1); + + p.data.elements [p.numElements - 2] = (float) (joinX - (joinX - startX) * propNeeded); + p.data.elements [p.numElements - 1] = (float) (joinY - (joinY - startY) * propNeeded); + } + + const double len2 = PathHelpers::lengthOf (endX, endY, joinX, joinY); + + if (len2 > 0) + { + const double propNeeded = jmin (0.5, cornerRadius / len2); + + endX = (float) (joinX + (endX - joinX) * propNeeded); + endY = (float) (joinY + (endY - joinY) * propNeeded); + + p.quadraticTo (joinX, joinY, endX, endY); + + p.data.elements [indexOfPathStart + 1] = endX; + p.data.elements [indexOfPathStart + 2] = endY; + } + } + + p.closeSubPath(); + } + } + else if (type == quadMarker) + { + lastWasLine = false; + const float x1 = data.elements [n++]; + const float y1 = data.elements [n++]; + const float x2 = data.elements [n++]; + const float y2 = data.elements [n++]; + p.quadraticTo (x1, y1, x2, y2); + } + else if (type == cubicMarker) + { + lastWasLine = false; + const float x1 = data.elements [n++]; + const float y1 = data.elements [n++]; + const float x2 = data.elements [n++]; + const float y2 = data.elements [n++]; + const float x3 = data.elements [n++]; + const float y3 = data.elements [n++]; + p.cubicTo (x1, y1, x2, y2, x3, y3); + } + } + + return p; +} + +//============================================================================== +void Path::loadPathFromStream (InputStream& source) +{ + while (! source.isExhausted()) + { + switch (source.readByte()) + { + case 'm': + { + const float x = source.readFloat(); + const float y = source.readFloat(); + startNewSubPath (x, y); + break; + } + + case 'l': + { + const float x = source.readFloat(); + const float y = source.readFloat(); + lineTo (x, y); + break; + } + + case 'q': + { + const float x1 = source.readFloat(); + const float y1 = source.readFloat(); + const float x2 = source.readFloat(); + const float y2 = source.readFloat(); + quadraticTo (x1, y1, x2, y2); + break; + } + + case 'b': + { + const float x1 = source.readFloat(); + const float y1 = source.readFloat(); + const float x2 = source.readFloat(); + const float y2 = source.readFloat(); + const float x3 = source.readFloat(); + const float y3 = source.readFloat(); + cubicTo (x1, y1, x2, y2, x3, y3); + break; + } + + case 'c': + closeSubPath(); + break; + + case 'n': + useNonZeroWinding = true; + break; + + case 'z': + useNonZeroWinding = false; + break; + + case 'e': + return; // end of path marker + + default: + jassertfalse; // illegal char in the stream + break; + } + } +} + +void Path::loadPathFromData (const void* const pathData, const size_t numberOfBytes) +{ + MemoryInputStream in (pathData, numberOfBytes, false); + loadPathFromStream (in); +} + +void Path::writePathToStream (OutputStream& dest) const +{ + dest.writeByte (useNonZeroWinding ? 'n' : 'z'); + + size_t i = 0; + while (i < numElements) + { + const float type = data.elements [i++]; + + if (type == moveMarker) + { + dest.writeByte ('m'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == lineMarker) + { + dest.writeByte ('l'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == quadMarker) + { + dest.writeByte ('q'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == cubicMarker) + { + dest.writeByte ('b'); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + dest.writeFloat (data.elements [i++]); + } + else if (type == closeSubPathMarker) + { + dest.writeByte ('c'); + } + } + + dest.writeByte ('e'); // marks the end-of-path +} + +String Path::toString() const +{ + MemoryOutputStream s (2048); + if (! useNonZeroWinding) + s << 'a'; + + size_t i = 0; + float lastMarker = 0.0f; + + while (i < numElements) + { + const float marker = data.elements [i++]; + char markerChar = 0; + int numCoords = 0; + + if (marker == moveMarker) + { + markerChar = 'm'; + numCoords = 2; + } + else if (marker == lineMarker) + { + markerChar = 'l'; + numCoords = 2; + } + else if (marker == quadMarker) + { + markerChar = 'q'; + numCoords = 4; + } + else if (marker == cubicMarker) + { + markerChar = 'c'; + numCoords = 6; + } + else + { + jassert (marker == closeSubPathMarker); + markerChar = 'z'; + } + + if (marker != lastMarker) + { + if (s.getDataSize() != 0) + s << ' '; + + s << markerChar; + lastMarker = marker; + } + + while (--numCoords >= 0 && i < numElements) + { + String coord (data.elements [i++], 3); + + while (coord.endsWithChar ('0') && coord != "0") + coord = coord.dropLastCharacters (1); + + if (coord.endsWithChar ('.')) + coord = coord.dropLastCharacters (1); + + if (s.getDataSize() != 0) + s << ' '; + + s << coord; + } + } + + return s.toUTF8(); +} + +void Path::restoreFromString (StringRef stringVersion) +{ + clear(); + setUsingNonZeroWinding (true); + + String::CharPointerType t (stringVersion.text); + juce_wchar marker = 'm'; + int numValues = 2; + float values [6]; + + for (;;) + { + const String token (PathHelpers::nextToken (t)); + const juce_wchar firstChar = token[0]; + int startNum = 0; + + if (firstChar == 0) + break; + + if (firstChar == 'm' || firstChar == 'l') + { + marker = firstChar; + numValues = 2; + } + else if (firstChar == 'q') + { + marker = firstChar; + numValues = 4; + } + else if (firstChar == 'c') + { + marker = firstChar; + numValues = 6; + } + else if (firstChar == 'z') + { + marker = firstChar; + numValues = 0; + } + else if (firstChar == 'a') + { + setUsingNonZeroWinding (false); + continue; + } + else + { + ++startNum; + values [0] = token.getFloatValue(); + } + + for (int i = startNum; i < numValues; ++i) + values [i] = PathHelpers::nextToken (t).getFloatValue(); + + switch (marker) + { + case 'm': startNewSubPath (values[0], values[1]); break; + case 'l': lineTo (values[0], values[1]); break; + case 'q': quadraticTo (values[0], values[1], values[2], values[3]); break; + case 'c': cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break; + case 'z': closeSubPath(); break; + default: jassertfalse; break; // illegal string format? + } + } +} + +//============================================================================== +Path::Iterator::Iterator (const Path& path_) + : path (path_), + index (0) +{ +} + +Path::Iterator::~Iterator() +{ +} + +bool Path::Iterator::next() +{ + const float* const elements = path.data.elements; + + if (index < path.numElements) + { + const float type = elements [index++]; + + if (type == moveMarker) + { + elementType = startNewSubPath; + x1 = elements [index++]; + y1 = elements [index++]; + } + else if (type == lineMarker) + { + elementType = lineTo; + x1 = elements [index++]; + y1 = elements [index++]; + } + else if (type == quadMarker) + { + elementType = quadraticTo; + x1 = elements [index++]; + y1 = elements [index++]; + x2 = elements [index++]; + y2 = elements [index++]; + } + else if (type == cubicMarker) + { + elementType = cubicTo; + x1 = elements [index++]; + y1 = elements [index++]; + x2 = elements [index++]; + y2 = elements [index++]; + x3 = elements [index++]; + y3 = elements [index++]; + } + else if (type == closeSubPathMarker) + { + elementType = closePath; + } + + return true; + } + + return false; +} + +#undef JUCE_CHECK_COORDS_ARE_VALID diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h new file mode 100644 index 0000000..c680089 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h @@ -0,0 +1,804 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_PATH_H_INCLUDED +#define JUCE_PATH_H_INCLUDED + + +//============================================================================== +/** + A path is a sequence of lines and curves that may either form a closed shape + or be open-ended. + + To use a path, you can create an empty one, then add lines and curves to it + to create shapes, then it can be rendered by a Graphics context or used + for geometric operations. + + e.g. @code + Path myPath; + + myPath.startNewSubPath (10.0f, 10.0f); // move the current position to (10, 10) + myPath.lineTo (100.0f, 200.0f); // draw a line from here to (100, 200) + myPath.quadraticTo (0.0f, 150.0f, 5.0f, 50.0f); // draw a curve that ends at (5, 50) + myPath.closeSubPath(); // close the subpath with a line back to (10, 10) + + // add an ellipse as well, which will form a second sub-path within the path.. + myPath.addEllipse (50.0f, 50.0f, 40.0f, 30.0f); + + // double the width of the whole thing.. + myPath.applyTransform (AffineTransform::scale (2.0f, 1.0f)); + + // and draw it to a graphics context with a 5-pixel thick outline. + g.strokePath (myPath, PathStrokeType (5.0f)); + + @endcode + + A path object can actually contain multiple sub-paths, which may themselves + be open or closed. + + @see PathFlatteningIterator, PathStrokeType, Graphics +*/ +class JUCE_API Path +{ +public: + //============================================================================== + /** Creates an empty path. */ + Path(); + + /** Creates a copy of another path. */ + Path (const Path&); + + /** Destructor. */ + ~Path(); + + /** Copies this path from another one. */ + Path& operator= (const Path&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Path (Path&&) noexcept; + Path& operator= (Path&&) noexcept; + #endif + + bool operator== (const Path&) const noexcept; + bool operator!= (const Path&) const noexcept; + + //============================================================================== + /** Returns true if the path doesn't contain any lines or curves. */ + bool isEmpty() const noexcept; + + /** Returns the smallest rectangle that contains all points within the path. + */ + Rectangle getBounds() const noexcept; + + /** Returns the smallest rectangle that contains all points within the path + after it's been transformed with the given tranasform matrix. + */ + Rectangle getBoundsTransformed (const AffineTransform& transform) const noexcept; + + /** Checks whether a point lies within the path. + + This is only relevent for closed paths (see closeSubPath()), and + may produce false results if used on a path which has open sub-paths. + + The path's winding rule is taken into account by this method. + + The tolerance parameter is the maximum error allowed when flattening the path, + so this method could return a false positive when your point is up to this distance + outside the path's boundary. + + @see closeSubPath, setUsingNonZeroWinding + */ + bool contains (float x, float y, + float tolerance = 1.0f) const; + + /** Checks whether a point lies within the path. + + This is only relevent for closed paths (see closeSubPath()), and + may produce false results if used on a path which has open sub-paths. + + The path's winding rule is taken into account by this method. + + The tolerance parameter is the maximum error allowed when flattening the path, + so this method could return a false positive when your point is up to this distance + outside the path's boundary. + + @see closeSubPath, setUsingNonZeroWinding + */ + bool contains (const Point point, + float tolerance = 1.0f) const; + + /** Checks whether a line crosses the path. + + This will return positive if the line crosses any of the paths constituent + lines or curves. It doesn't take into account whether the line is inside + or outside the path, or whether the path is open or closed. + + The tolerance parameter is the maximum error allowed when flattening the path, + so this method could return a false positive when your point is up to this distance + outside the path's boundary. + */ + bool intersectsLine (const Line& line, + float tolerance = 1.0f); + + /** Cuts off parts of a line to keep the parts that are either inside or + outside this path. + + Note that this isn't smart enough to cope with situations where the + line would need to be cut into multiple pieces to correctly clip against + a re-entrant shape. + + @param line the line to clip + @param keepSectionOutsidePath if true, it's the section outside the path + that will be kept; if false its the section inside + the path + */ + Line getClippedLine (const Line& line, bool keepSectionOutsidePath) const; + + /** Returns the length of the path. + @see getPointAlongPath + */ + float getLength (const AffineTransform& transform = AffineTransform::identity) const; + + /** Returns a point that is the specified distance along the path. + If the distance is greater than the total length of the path, this will return the + end point. + @see getLength + */ + Point getPointAlongPath (float distanceFromStart, + const AffineTransform& transform = AffineTransform::identity) const; + + /** Finds the point along the path which is nearest to a given position. + This sets pointOnPath to the nearest point, and returns the distance of this point from the start + of the path. + */ + float getNearestPoint (const Point targetPoint, + Point& pointOnPath, + const AffineTransform& transform = AffineTransform::identity) const; + + //============================================================================== + /** Removes all lines and curves, resetting the path completely. */ + void clear() noexcept; + + /** Begins a new subpath with a given starting position. + + This will move the path's current position to the coordinates passed in and + make it ready to draw lines or curves starting from this position. + + After adding whatever lines and curves are needed, you can either + close the current sub-path using closeSubPath() or call startNewSubPath() + to move to a new sub-path, leaving the old one open-ended. + + @see lineTo, quadraticTo, cubicTo, closeSubPath + */ + void startNewSubPath (float startX, float startY); + + /** Begins a new subpath with a given starting position. + + This will move the path's current position to the coordinates passed in and + make it ready to draw lines or curves starting from this position. + + After adding whatever lines and curves are needed, you can either + close the current sub-path using closeSubPath() or call startNewSubPath() + to move to a new sub-path, leaving the old one open-ended. + + @see lineTo, quadraticTo, cubicTo, closeSubPath + */ + void startNewSubPath (const Point start); + + /** Closes a the current sub-path with a line back to its start-point. + + When creating a closed shape such as a triangle, don't use 3 lineTo() + calls - instead use two lineTo() calls, followed by a closeSubPath() + to join the final point back to the start. + + This ensures that closes shapes are recognised as such, and this is + important for tasks like drawing strokes, which needs to know whether to + draw end-caps or not. + + @see startNewSubPath, lineTo, quadraticTo, cubicTo, closeSubPath + */ + void closeSubPath(); + + /** Adds a line from the shape's last position to a new end-point. + + This will connect the end-point of the last line or curve that was added + to a new point, using a straight line. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, quadraticTo, cubicTo, closeSubPath + */ + void lineTo (float endX, float endY); + + /** Adds a line from the shape's last position to a new end-point. + + This will connect the end-point of the last line or curve that was added + to a new point, using a straight line. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, quadraticTo, cubicTo, closeSubPath + */ + void lineTo (const Point end); + + /** Adds a quadratic bezier curve from the shape's last position to a new position. + + This will connect the end-point of the last line or curve that was added + to a new point, using a quadratic spline with one control-point. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, lineTo, cubicTo, closeSubPath + */ + void quadraticTo (float controlPointX, + float controlPointY, + float endPointX, + float endPointY); + + /** Adds a quadratic bezier curve from the shape's last position to a new position. + + This will connect the end-point of the last line or curve that was added + to a new point, using a quadratic spline with one control-point. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, lineTo, cubicTo, closeSubPath + */ + void quadraticTo (const Point controlPoint, + const Point endPoint); + + /** Adds a cubic bezier curve from the shape's last position to a new position. + + This will connect the end-point of the last line or curve that was added + to a new point, using a cubic spline with two control-points. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, lineTo, quadraticTo, closeSubPath + */ + void cubicTo (float controlPoint1X, + float controlPoint1Y, + float controlPoint2X, + float controlPoint2Y, + float endPointX, + float endPointY); + + /** Adds a cubic bezier curve from the shape's last position to a new position. + + This will connect the end-point of the last line or curve that was added + to a new point, using a cubic spline with two control-points. + + See the class description for an example of how to add lines and curves to a path. + + @see startNewSubPath, lineTo, quadraticTo, closeSubPath + */ + void cubicTo (const Point controlPoint1, + const Point controlPoint2, + const Point endPoint); + + /** Returns the last point that was added to the path by one of the drawing methods. + */ + Point getCurrentPosition() const; + + //============================================================================== + /** Adds a rectangle to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRoundedRectangle, addTriangle + */ + void addRectangle (float x, float y, float width, float height); + + /** Adds a rectangle to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRoundedRectangle, addTriangle + */ + template + void addRectangle (const Rectangle& rectangle) + { + addRectangle (static_cast (rectangle.getX()), static_cast (rectangle.getY()), + static_cast (rectangle.getWidth()), static_cast (rectangle.getHeight())); + } + + /** Adds a rectangle with rounded corners to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRectangle, addTriangle + */ + void addRoundedRectangle (float x, float y, float width, float height, + float cornerSize); + + /** Adds a rectangle with rounded corners to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRectangle, addTriangle + */ + void addRoundedRectangle (float x, float y, float width, float height, + float cornerSizeX, + float cornerSizeY); + + /** Adds a rectangle with rounded corners to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRectangle, addTriangle + */ + void addRoundedRectangle (float x, float y, float width, float height, + float cornerSizeX, float cornerSizeY, + bool curveTopLeft, bool curveTopRight, + bool curveBottomLeft, bool curveBottomRight); + + /** Adds a rectangle with rounded corners to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRectangle, addTriangle + */ + template + void addRoundedRectangle (const Rectangle& rectangle, float cornerSizeX, float cornerSizeY) + { + addRoundedRectangle (static_cast (rectangle.getX()), static_cast (rectangle.getY()), + static_cast (rectangle.getWidth()), static_cast (rectangle.getHeight()), + cornerSizeX, cornerSizeY); + } + + /** Adds a rectangle with rounded corners to the path. + The rectangle is added as a new sub-path. (Any currently open paths will be left open). + @see addRectangle, addTriangle + */ + template + void addRoundedRectangle (const Rectangle& rectangle, float cornerSize) + { + addRoundedRectangle (rectangle, cornerSize, cornerSize); + } + + /** Adds a triangle to the path. + + The triangle is added as a new closed sub-path. (Any currently open paths will be left open). + + Note that whether the vertices are specified in clockwise or anticlockwise + order will affect how the triangle is filled when it overlaps other + shapes (the winding order setting will affect this of course). + */ + void addTriangle (float x1, float y1, + float x2, float y2, + float x3, float y3); + + /** Adds a triangle to the path. + + The triangle is added as a new closed sub-path. (Any currently open paths will be left open). + + Note that whether the vertices are specified in clockwise or anticlockwise + order will affect how the triangle is filled when it overlaps other + shapes (the winding order setting will affect this of course). + */ + void addTriangle (Point point1, + Point point2, + Point point3); + + /** Adds a quadrilateral to the path. + + The quad is added as a new closed sub-path. (Any currently open paths will be left open). + + Note that whether the vertices are specified in clockwise or anticlockwise + order will affect how the quad is filled when it overlaps other + shapes (the winding order setting will affect this of course). + */ + void addQuadrilateral (float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4); + + /** Adds an ellipse to the path. + The shape is added as a new sub-path. (Any currently open paths will be left open). + @see addArc + */ + void addEllipse (float x, float y, float width, float height); + + /** Adds an ellipse to the path. + The shape is added as a new sub-path. (Any currently open paths will be left open). + @see addArc + */ + void addEllipse (Rectangle area); + + /** Adds an elliptical arc to the current path. + + Note that when specifying the start and end angles, the curve will be drawn either clockwise + or anti-clockwise according to whether the end angle is greater than the start. This means + that sometimes you may need to use values greater than 2*Pi for the end angle. + + @param x the left-hand edge of the rectangle in which the elliptical outline fits + @param y the top edge of the rectangle in which the elliptical outline fits + @param width the width of the rectangle in which the elliptical outline fits + @param height the height of the rectangle in which the elliptical outline fits + @param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the + top-centre of the ellipse) + @param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the + top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to + draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via + 12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points. + @param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false, + it will be added to the current sub-path, continuing from the current postition + + @see addCentredArc, arcTo, addPieSegment, addEllipse + */ + void addArc (float x, float y, float width, float height, + float fromRadians, + float toRadians, + bool startAsNewSubPath = false); + + /** Adds an arc which is centred at a given point, and can have a rotation specified. + + Note that when specifying the start and end angles, the curve will be drawn either clockwise + or anti-clockwise according to whether the end angle is greater than the start. This means + that sometimes you may need to use values greater than 2*Pi for the end angle. + + @param centreX the centre x of the ellipse + @param centreY the centre y of the ellipse + @param radiusX the horizontal radius of the ellipse + @param radiusY the vertical radius of the ellipse + @param rotationOfEllipse an angle by which the whole ellipse should be rotated about its centre, in radians (clockwise) + @param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the + top-centre of the ellipse) + @param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the + top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to + draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via + 12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points. + @param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false, + it will be added to the current sub-path, continuing from the current postition + + @see addArc, arcTo + */ + void addCentredArc (float centreX, float centreY, + float radiusX, float radiusY, + float rotationOfEllipse, + float fromRadians, + float toRadians, + bool startAsNewSubPath = false); + + /** Adds a "pie-chart" shape to the path. + + The shape is added as a new sub-path. (Any currently open paths will be + left open). + + Note that when specifying the start and end angles, the curve will be drawn either clockwise + or anti-clockwise according to whether the end angle is greater than the start. This means + that sometimes you may need to use values greater than 2*Pi for the end angle. + + @param x the left-hand edge of the rectangle in which the elliptical outline fits + @param y the top edge of the rectangle in which the elliptical outline fits + @param width the width of the rectangle in which the elliptical outline fits + @param height the height of the rectangle in which the elliptical outline fits + @param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the + top-centre of the ellipse) + @param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the + top-centre of the ellipse) + @param innerCircleProportionalSize if this is > 0, then the pie will be drawn as a curved band around a hollow + ellipse at its centre, where this value indicates the inner ellipse's size with + respect to the outer one. + + @see addArc + */ + void addPieSegment (float x, float y, + float width, float height, + float fromRadians, + float toRadians, + float innerCircleProportionalSize); + + /** Adds a line with a specified thickness. + + The line is added as a new closed sub-path. (Any currently open paths will be + left open). + + @see addArrow + */ + void addLineSegment (const Line& line, float lineThickness); + + /** Adds a line with an arrowhead on the end. + The arrow is added as a new closed sub-path. (Any currently open paths will be left open). + @see PathStrokeType::createStrokeWithArrowheads + */ + void addArrow (const Line& line, + float lineThickness, + float arrowheadWidth, + float arrowheadLength); + + /** Adds a polygon shape to the path. + @see addStar + */ + void addPolygon (const Point centre, + int numberOfSides, + float radius, + float startAngle = 0.0f); + + /** Adds a star shape to the path. + @see addPolygon + */ + void addStar (const Point centre, + int numberOfPoints, + float innerRadius, + float outerRadius, + float startAngle = 0.0f); + + /** Adds a speech-bubble shape to the path. + + @param bodyArea the area of the body of the bubble shape + @param maximumArea an area which encloses the body area and defines the limits within which + the arrow tip can be drawn - if the tip lies outside this area, the bubble + will be drawn without an arrow + @param arrowTipPosition the location of the tip of the arrow + @param cornerSize the size of the rounded corners + @param arrowBaseWidth the width of the base of the arrow where it joins the main rectangle + */ + void addBubble (const Rectangle& bodyArea, + const Rectangle& maximumArea, + const Point arrowTipPosition, + const float cornerSize, + const float arrowBaseWidth); + + /** Adds another path to this one. + + The new path is added as a new sub-path. (Any currently open paths in this + path will be left open). + + @param pathToAppend the path to add + */ + void addPath (const Path& pathToAppend); + + /** Adds another path to this one, transforming it on the way in. + + The new path is added as a new sub-path, its points being transformed by the given + matrix before being added. + + @param pathToAppend the path to add + @param transformToApply an optional transform to apply to the incoming vertices + */ + void addPath (const Path& pathToAppend, + const AffineTransform& transformToApply); + + /** Swaps the contents of this path with another one. + + The internal data of the two paths is swapped over, so this is much faster than + copying it to a temp variable and back. + */ + void swapWithPath (Path&) noexcept; + + //============================================================================== + /** Preallocates enough space for adding the given number of coordinates to the path. + If you're about to add a large number of lines or curves to the path, it can make + the task much more efficient to call this first and avoid costly reallocations + as the structure grows. + The actual value to pass is a bit tricky to calculate because the space required + depends on what you're adding - e.g. each lineTo() or startNewSubPath() will + require 3 coords (x, y and a type marker). Each quadraticTo() will need 5, and + a cubicTo() will require 7. Closing a sub-path will require 1. + */ + void preallocateSpace (int numExtraCoordsToMakeSpaceFor); + + //============================================================================== + /** Applies a 2D transform to all the vertices in the path. + + @see AffineTransform, scaleToFit, getTransformToScaleToFit + */ + void applyTransform (const AffineTransform& transform) noexcept; + + /** Rescales this path to make it fit neatly into a given space. + + This is effectively a quick way of calling + applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions)) + + @param x the x position of the rectangle to fit the path inside + @param y the y position of the rectangle to fit the path inside + @param width the width of the rectangle to fit the path inside + @param height the height of the rectangle to fit the path inside + @param preserveProportions if true, it will fit the path into the space without altering its + horizontal/vertical scale ratio; if false, it will distort the + path to fill the specified ratio both horizontally and vertically + + @see applyTransform, getTransformToScaleToFit + */ + void scaleToFit (float x, float y, float width, float height, + bool preserveProportions) noexcept; + + /** Returns a transform that can be used to rescale the path to fit into a given space. + + @param x the x position of the rectangle to fit the path inside + @param y the y position of the rectangle to fit the path inside + @param width the width of the rectangle to fit the path inside + @param height the height of the rectangle to fit the path inside + @param preserveProportions if true, it will fit the path into the space without altering its + horizontal/vertical scale ratio; if false, it will distort the + path to fill the specified ratio both horizontally and vertically + @param justificationType if the proportions are preseved, the resultant path may be smaller + than the available rectangle, so this describes how it should be + positioned within the space. + @returns an appropriate transformation + + @see applyTransform, scaleToFit + + */ + AffineTransform getTransformToScaleToFit (float x, float y, float width, float height, + bool preserveProportions, + Justification justificationType = Justification::centred) const; + + /** Returns a transform that can be used to rescale the path to fit into a given space. + + @param area the rectangle to fit the path inside + @param preserveProportions if true, it will fit the path into the space without altering its + horizontal/vertical scale ratio; if false, it will distort the + path to fill the specified ratio both horizontally and vertically + @param justificationType if the proportions are preseved, the resultant path may be smaller + than the available rectangle, so this describes how it should be + positioned within the space. + @returns an appropriate transformation + + @see applyTransform, scaleToFit + + */ + AffineTransform getTransformToScaleToFit (const Rectangle& area, + bool preserveProportions, + Justification justificationType = Justification::centred) const; + + /** Creates a version of this path where all sharp corners have been replaced by curves. + + Wherever two lines meet at an angle, this will replace the corner with a curve + of the given radius. + */ + Path createPathWithRoundedCorners (float cornerRadius) const; + + //============================================================================== + /** Changes the winding-rule to be used when filling the path. + + If set to true (which is the default), then the path uses a non-zero-winding rule + to determine which points are inside the path. If set to false, it uses an + alternate-winding rule. + + The winding-rule comes into play when areas of the shape overlap other + areas, and determines whether the overlapping regions are considered to be + inside or outside. + + Changing this value just sets a flag - it doesn't affect the contents of the + path. + + @see isUsingNonZeroWinding + */ + void setUsingNonZeroWinding (bool isNonZeroWinding) noexcept; + + /** Returns the flag that indicates whether the path should use a non-zero winding rule. + + The default for a new path is true. + + @see setUsingNonZeroWinding + */ + bool isUsingNonZeroWinding() const { return useNonZeroWinding; } + + + //============================================================================== + /** Iterates the lines and curves that a path contains. + + @see Path, PathFlatteningIterator + */ + class JUCE_API Iterator + { + public: + //============================================================================== + Iterator (const Path& path); + ~Iterator(); + + //============================================================================== + /** Moves onto the next element in the path. + + If this returns false, there are no more elements. If it returns true, + the elementType variable will be set to the type of the current element, + and some of the x and y variables will be filled in with values. + */ + bool next(); + + //============================================================================== + enum PathElementType + { + startNewSubPath, /**< For this type, x1 and y1 will be set to indicate the first point in the subpath. */ + lineTo, /**< For this type, x1 and y1 indicate the end point of the line. */ + quadraticTo, /**< For this type, x1, y1, x2, y2 indicate the control point and endpoint of a quadratic curve. */ + cubicTo, /**< For this type, x1, y1, x2, y2, x3, y3 indicate the two control points and the endpoint of a cubic curve. */ + closePath /**< Indicates that the sub-path is being closed. None of the x or y values are valid in this case. */ + }; + + PathElementType elementType; + + float x1, y1, x2, y2, x3, y3; + + //============================================================================== + private: + const Path& path; + size_t index; + + JUCE_DECLARE_NON_COPYABLE (Iterator) + }; + + //============================================================================== + /** Loads a stored path from a data stream. + + The data in the stream must have been written using writePathToStream(). + + Note that this will append the stored path to whatever is currently in + this path, so you might need to call clear() beforehand. + + @see loadPathFromData, writePathToStream + */ + void loadPathFromStream (InputStream& source); + + /** Loads a stored path from a block of data. + + This is similar to loadPathFromStream(), but just reads from a block + of data. Useful if you're including stored shapes in your code as a + block of static data. + + @see loadPathFromStream, writePathToStream + */ + void loadPathFromData (const void* data, size_t numberOfBytes); + + /** Stores the path by writing it out to a stream. + + After writing out a path, you can reload it using loadPathFromStream(). + + @see loadPathFromStream, loadPathFromData + */ + void writePathToStream (OutputStream& destination) const; + + //============================================================================== + /** Creates a string containing a textual representation of this path. + @see restoreFromString + */ + String toString() const; + + /** Restores this path from a string that was created with the toString() method. + @see toString() + */ + void restoreFromString (StringRef stringVersion); + +private: + //============================================================================== + friend class PathFlatteningIterator; + friend class Path::Iterator; + ArrayAllocationBase data; + size_t numElements; + + struct PathBounds + { + PathBounds() noexcept; + Rectangle getRectangle() const noexcept; + void reset() noexcept; + void reset (float, float) noexcept; + void extend (float, float) noexcept; + void extend (float, float, float, float) noexcept; + + float pathXMin, pathXMax, pathYMin, pathYMax; + }; + + PathBounds bounds; + bool useNonZeroWinding; + + static const float lineMarker; + static const float moveMarker; + static const float quadMarker; + static const float cubicMarker; + static const float closeSubPathMarker; + + JUCE_LEAK_DETECTOR (Path) +}; + +#endif // JUCE_PATH_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp new file mode 100644 index 0000000..0a15581 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp @@ -0,0 +1,286 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("t", on) +#endif + +const float PathFlatteningIterator::defaultTolerance = 0.6f; + +//============================================================================== +PathFlatteningIterator::PathFlatteningIterator (const Path& path_, + const AffineTransform& transform_, + const float tolerance) + : x2 (0), + y2 (0), + closesSubPath (false), + subPathIndex (-1), + path (path_), + transform (transform_), + points (path_.data.elements), + toleranceSquared (tolerance * tolerance), + subPathCloseX (0), + subPathCloseY (0), + isIdentityTransform (transform_.isIdentity()), + stackBase (32), + index (0), + stackSize (32) +{ + stackPos = stackBase; +} + +PathFlatteningIterator::~PathFlatteningIterator() +{ +} + +bool PathFlatteningIterator::isLastInSubpath() const noexcept +{ + return stackPos == stackBase.getData() + && (index >= path.numElements || points [index] == Path::moveMarker); +} + +bool PathFlatteningIterator::next() +{ + x1 = x2; + y1 = y2; + + float x3 = 0; + float y3 = 0; + float x4 = 0; + float y4 = 0; + + for (;;) + { + float type; + + if (stackPos == stackBase) + { + if (index >= path.numElements) + return false; + + type = points [index++]; + + if (type != Path::closeSubPathMarker) + { + x2 = points [index++]; + y2 = points [index++]; + + if (type == Path::quadMarker) + { + x3 = points [index++]; + y3 = points [index++]; + + if (! isIdentityTransform) + transform.transformPoints (x2, y2, x3, y3); + } + else if (type == Path::cubicMarker) + { + x3 = points [index++]; + y3 = points [index++]; + x4 = points [index++]; + y4 = points [index++]; + + if (! isIdentityTransform) + transform.transformPoints (x2, y2, x3, y3, x4, y4); + } + else + { + if (! isIdentityTransform) + transform.transformPoint (x2, y2); + } + } + } + else + { + type = *--stackPos; + + if (type != Path::closeSubPathMarker) + { + x2 = *--stackPos; + y2 = *--stackPos; + + if (type == Path::quadMarker) + { + x3 = *--stackPos; + y3 = *--stackPos; + } + else if (type == Path::cubicMarker) + { + x3 = *--stackPos; + y3 = *--stackPos; + x4 = *--stackPos; + y4 = *--stackPos; + } + } + } + + if (type == Path::lineMarker) + { + ++subPathIndex; + + closesSubPath = (stackPos == stackBase) + && (index < path.numElements) + && (points [index] == Path::closeSubPathMarker) + && x2 == subPathCloseX + && y2 == subPathCloseY; + + return true; + } + + if (type == Path::quadMarker) + { + const size_t offset = (size_t) (stackPos - stackBase); + + if (offset >= stackSize - 10) + { + stackSize <<= 1; + stackBase.realloc (stackSize); + stackPos = stackBase + offset; + } + + const float m1x = (x1 + x2) * 0.5f; + const float m1y = (y1 + y2) * 0.5f; + const float m2x = (x2 + x3) * 0.5f; + const float m2y = (y2 + y3) * 0.5f; + const float m3x = (m1x + m2x) * 0.5f; + const float m3y = (m1y + m2y) * 0.5f; + + const float errorX = m3x - x2; + const float errorY = m3y - y2; + + if (errorX * errorX + errorY * errorY > toleranceSquared) + { + *stackPos++ = y3; + *stackPos++ = x3; + *stackPos++ = m2y; + *stackPos++ = m2x; + *stackPos++ = Path::quadMarker; + + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = m1y; + *stackPos++ = m1x; + *stackPos++ = Path::quadMarker; + } + else + { + *stackPos++ = y3; + *stackPos++ = x3; + *stackPos++ = Path::lineMarker; + + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = Path::lineMarker; + } + + jassert (stackPos < stackBase + stackSize); + } + else if (type == Path::cubicMarker) + { + const size_t offset = (size_t) (stackPos - stackBase); + + if (offset >= stackSize - 16) + { + stackSize <<= 1; + stackBase.realloc (stackSize); + stackPos = stackBase + offset; + } + + const float m1x = (x1 + x2) * 0.5f; + const float m1y = (y1 + y2) * 0.5f; + const float m2x = (x3 + x2) * 0.5f; + const float m2y = (y3 + y2) * 0.5f; + const float m3x = (x3 + x4) * 0.5f; + const float m3y = (y3 + y4) * 0.5f; + const float m4x = (m1x + m2x) * 0.5f; + const float m4y = (m1y + m2y) * 0.5f; + const float m5x = (m3x + m2x) * 0.5f; + const float m5y = (m3y + m2y) * 0.5f; + + const float error1X = m4x - x2; + const float error1Y = m4y - y2; + const float error2X = m5x - x3; + const float error2Y = m5y - y3; + + if (error1X * error1X + error1Y * error1Y > toleranceSquared + || error2X * error2X + error2Y * error2Y > toleranceSquared) + { + *stackPos++ = y4; + *stackPos++ = x4; + *stackPos++ = m3y; + *stackPos++ = m3x; + *stackPos++ = m5y; + *stackPos++ = m5x; + *stackPos++ = Path::cubicMarker; + + *stackPos++ = (m4y + m5y) * 0.5f; + *stackPos++ = (m4x + m5x) * 0.5f; + *stackPos++ = m4y; + *stackPos++ = m4x; + *stackPos++ = m1y; + *stackPos++ = m1x; + *stackPos++ = Path::cubicMarker; + } + else + { + *stackPos++ = y4; + *stackPos++ = x4; + *stackPos++ = Path::lineMarker; + + *stackPos++ = m5y; + *stackPos++ = m5x; + *stackPos++ = Path::lineMarker; + + *stackPos++ = m4y; + *stackPos++ = m4x; + *stackPos++ = Path::lineMarker; + } + } + else if (type == Path::closeSubPathMarker) + { + if (x2 != subPathCloseX || y2 != subPathCloseY) + { + x1 = x2; + y1 = y2; + x2 = subPathCloseX; + y2 = subPathCloseY; + closesSubPath = true; + + return true; + } + } + else + { + jassert (type == Path::moveMarker); + + subPathIndex = -1; + subPathCloseX = x1 = x2; + subPathCloseY = y1 = y2; + } + } +} + +#if JUCE_MSVC && JUCE_DEBUG + #pragma optimize ("", on) // resets optimisations to the project defaults +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.h new file mode 100644 index 0000000..dcb8b90 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.h @@ -0,0 +1,113 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_PATHITERATOR_H_INCLUDED +#define JUCE_PATHITERATOR_H_INCLUDED + + +//============================================================================== +/** + Flattens a Path object into a series of straight-line sections. + + Use one of these to iterate through a Path object, and it will convert + all the curves into line sections so it's easy to render or perform + geometric operations on. + + @see Path +*/ +class JUCE_API PathFlatteningIterator +{ +public: + //============================================================================== + /** Creates a PathFlatteningIterator. + + After creation, use the next() method to initialise the fields in the + object with the first line's position. + + @param path the path to iterate along + @param transform a transform to apply to each point in the path being iterated + @param tolerance the amount by which the curves are allowed to deviate from the lines + into which they are being broken down - a higher tolerance contains + less lines, so can be generated faster, but will be less smooth. + */ + PathFlatteningIterator (const Path& path, + const AffineTransform& transform = AffineTransform::identity, + float tolerance = defaultTolerance); + + /** Destructor. */ + ~PathFlatteningIterator(); + + //============================================================================== + /** Fetches the next line segment from the path. + + This will update the member variables x1, y1, x2, y2, subPathIndex and closesSubPath + so that they describe the new line segment. + + @returns false when there are no more lines to fetch. + */ + bool next(); + + float x1; /**< The x position of the start of the current line segment. */ + float y1; /**< The y position of the start of the current line segment. */ + float x2; /**< The x position of the end of the current line segment. */ + float y2; /**< The y position of the end of the current line segment. */ + + /** Indicates whether the current line segment is closing a sub-path. + + If the current line is the one that connects the end of a sub-path + back to the start again, this will be true. + */ + bool closesSubPath; + + /** The index of the current line within the current sub-path. + + E.g. you can use this to see whether the line is the first one in the + subpath by seeing if it's 0. + */ + int subPathIndex; + + /** Returns true if the current segment is the last in the current sub-path. */ + bool isLastInSubpath() const noexcept; + + /** This is the default value that should be used for the tolerance value (see the constructor parameters). */ + static const float defaultTolerance; + +private: + //============================================================================== + const Path& path; + const AffineTransform transform; + float* points; + const float toleranceSquared; + float subPathCloseX, subPathCloseY; + const bool isIdentityTransform; + + HeapBlock stackBase; + float* stackPos; + size_t index, stackSize; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathFlatteningIterator) +}; + + +#endif // JUCE_PATHITERATOR_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp new file mode 100644 index 0000000..bdac333 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp @@ -0,0 +1,740 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +PathStrokeType::PathStrokeType (float strokeThickness) noexcept + : thickness (strokeThickness), jointStyle (mitered), endStyle (butt) +{ +} + +PathStrokeType::PathStrokeType (float strokeThickness, JointStyle joint, EndCapStyle end) noexcept + : thickness (strokeThickness), jointStyle (joint), endStyle (end) +{ +} + +PathStrokeType::PathStrokeType (const PathStrokeType& other) noexcept + : thickness (other.thickness), + jointStyle (other.jointStyle), + endStyle (other.endStyle) +{ +} + +PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other) noexcept +{ + thickness = other.thickness; + jointStyle = other.jointStyle; + endStyle = other.endStyle; + return *this; +} + +PathStrokeType::~PathStrokeType() noexcept +{ +} + +bool PathStrokeType::operator== (const PathStrokeType& other) const noexcept +{ + return thickness == other.thickness + && jointStyle == other.jointStyle + && endStyle == other.endStyle; +} + +bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept +{ + return ! operator== (other); +} + +//============================================================================== +namespace PathStrokeHelpers +{ + static bool lineIntersection (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4, + float& intersectionX, + float& intersectionY, + float& distanceBeyondLine1EndSquared) noexcept + { + if (x2 != x3 || y2 != y3) + { + const float dx1 = x2 - x1; + const float dy1 = y2 - y1; + const float dx2 = x4 - x3; + const float dy2 = y4 - y3; + const float divisor = dx1 * dy2 - dx2 * dy1; + + if (divisor == 0) + { + if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0))) + { + if (dy1 == 0 && dy2 != 0) + { + const float along = (y1 - y3) / dy2; + intersectionX = x3 + along * dx2; + intersectionY = y1; + + distanceBeyondLine1EndSquared = intersectionX - x2; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if ((x2 > x1) == (intersectionX < x2)) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + + return along >= 0 && along <= 1.0f; + } + else if (dy2 == 0 && dy1 != 0) + { + const float along = (y3 - y1) / dy1; + intersectionX = x1 + along * dx1; + intersectionY = y3; + + distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if (along < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + + return along >= 0 && along <= 1.0f; + } + else if (dx1 == 0 && dx2 != 0) + { + const float along = (x1 - x3) / dx2; + intersectionX = x1; + intersectionY = y3 + along * dy2; + + distanceBeyondLine1EndSquared = intersectionY - y2; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + + if ((y2 > y1) == (intersectionY < y2)) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + + return along >= 0 && along <= 1.0f; + } + else if (dx2 == 0 && dx1 != 0) + { + const float along = (x3 - x1) / dx1; + intersectionX = x3; + intersectionY = y1 + along * dy1; + + distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + if (along < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + + return along >= 0 && along <= 1.0f; + } + } + + intersectionX = 0.5f * (x2 + x3); + intersectionY = 0.5f * (y2 + y3); + + distanceBeyondLine1EndSquared = 0.0f; + return false; + } + else + { + const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; + + intersectionX = x1 + along1 * dx1; + intersectionY = y1 + along1 * dy1; + + if (along1 >= 0 && along1 <= 1.0f) + { + const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); + + if (along2 >= 0 && along2 <= divisor) + { + distanceBeyondLine1EndSquared = 0.0f; + return true; + } + } + + distanceBeyondLine1EndSquared = along1 - 1.0f; + distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; + distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); + + if (along1 < 1.0f) + distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + + return false; + } + } + + intersectionX = x2; + intersectionY = y2; + + distanceBeyondLine1EndSquared = 0.0f; + return true; + } + + static void addEdgeAndJoint (Path& destPath, + const PathStrokeType::JointStyle style, + const float maxMiterExtensionSquared, const float width, + const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4, + const float midX, const float midY) + { + if (style == PathStrokeType::beveled + || (x3 == x4 && y3 == y4) + || (x1 == x2 && y1 == y2)) + { + destPath.lineTo (x2, y2); + destPath.lineTo (x3, y3); + } + else + { + float jx, jy, distanceBeyondLine1EndSquared; + + // if they intersect, use this point.. + if (lineIntersection (x1, y1, x2, y2, + x3, y3, x4, y4, + jx, jy, distanceBeyondLine1EndSquared)) + { + destPath.lineTo (jx, jy); + } + else + { + if (style == PathStrokeType::mitered) + { + if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared + && distanceBeyondLine1EndSquared > 0.0f) + { + destPath.lineTo (jx, jy); + } + else + { + // the end sticks out too far, so just use a blunt joint + destPath.lineTo (x2, y2); + destPath.lineTo (x3, y3); + } + } + else + { + // curved joints + float angle1 = std::atan2 (x2 - midX, y2 - midY); + float angle2 = std::atan2 (x3 - midX, y3 - midY); + const float angleIncrement = 0.1f; + + destPath.lineTo (x2, y2); + + if (std::abs (angle1 - angle2) > angleIncrement) + { + if (angle2 > angle1 + float_Pi + || (angle2 < angle1 && angle2 >= angle1 - float_Pi)) + { + if (angle2 > angle1) + angle2 -= float_Pi * 2.0f; + + jassert (angle1 <= angle2 + float_Pi); + + angle1 -= angleIncrement; + while (angle1 > angle2) + { + destPath.lineTo (midX + width * std::sin (angle1), + midY + width * std::cos (angle1)); + + angle1 -= angleIncrement; + } + } + else + { + if (angle1 > angle2) + angle1 -= float_Pi * 2.0f; + + jassert (angle1 >= angle2 - float_Pi); + + angle1 += angleIncrement; + while (angle1 < angle2) + { + destPath.lineTo (midX + width * std::sin (angle1), + midY + width * std::cos (angle1)); + + angle1 += angleIncrement; + } + } + } + + destPath.lineTo (x3, y3); + } + } + } + } + + static void addLineEnd (Path& destPath, + const PathStrokeType::EndCapStyle style, + const float x1, const float y1, + const float x2, const float y2, + const float width) + { + if (style == PathStrokeType::butt) + { + destPath.lineTo (x2, y2); + } + else + { + float offx1, offy1, offx2, offy2; + + float dx = x2 - x1; + float dy = y2 - y1; + const float len = juce_hypot (dx, dy); + + if (len == 0) + { + offx1 = offx2 = x1; + offy1 = offy2 = y1; + } + else + { + const float offset = width / len; + dx *= offset; + dy *= offset; + + offx1 = x1 + dy; + offy1 = y1 - dx; + offx2 = x2 + dy; + offy2 = y2 - dx; + } + + if (style == PathStrokeType::square) + { + // sqaure ends + destPath.lineTo (offx1, offy1); + destPath.lineTo (offx2, offy2); + destPath.lineTo (x2, y2); + } + else + { + // rounded ends + const float midx = (offx1 + offx2) * 0.5f; + const float midy = (offy1 + offy2) * 0.5f; + + destPath.cubicTo (x1 + (offx1 - x1) * 0.55f, y1 + (offy1 - y1) * 0.55f, + offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f, + midx, midy); + + destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f, + offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f, + x2, y2); + } + } + } + + struct Arrowhead + { + float startWidth, startLength; + float endWidth, endLength; + }; + + static void addArrowhead (Path& destPath, + const float x1, const float y1, + const float x2, const float y2, + const float tipX, const float tipY, + const float width, + const float arrowheadWidth) + { + Line line (x1, y1, x2, y2); + destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0)); + destPath.lineTo (tipX, tipY); + destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0)); + destPath.lineTo (x2, y2); + } + + struct LineSection + { + float x1, y1, x2, y2; // original line + float lx1, ly1, lx2, ly2; // the left-hand stroke + float rx1, ry1, rx2, ry2; // the right-hand stroke + }; + + static void shortenSubPath (Array& subPath, float amountAtStart, float amountAtEnd) + { + while (amountAtEnd > 0 && subPath.size() > 0) + { + LineSection& l = subPath.getReference (subPath.size() - 1); + float dx = l.rx2 - l.rx1; + float dy = l.ry2 - l.ry1; + const float len = juce_hypot (dx, dy); + + if (len <= amountAtEnd && subPath.size() > 1) + { + LineSection& prev = subPath.getReference (subPath.size() - 2); + prev.x2 = l.x2; + prev.y2 = l.y2; + subPath.removeLast(); + amountAtEnd -= len; + } + else + { + const float prop = jmin (0.9999f, amountAtEnd / len); + dx *= prop; + dy *= prop; + l.rx1 += dx; + l.ry1 += dy; + l.lx2 += dx; + l.ly2 += dy; + break; + } + } + + while (amountAtStart > 0 && subPath.size() > 0) + { + LineSection& l = subPath.getReference (0); + float dx = l.rx2 - l.rx1; + float dy = l.ry2 - l.ry1; + const float len = juce_hypot (dx, dy); + + if (len <= amountAtStart && subPath.size() > 1) + { + LineSection& next = subPath.getReference (1); + next.x1 = l.x1; + next.y1 = l.y1; + subPath.remove (0); + amountAtStart -= len; + } + else + { + const float prop = jmin (0.9999f, amountAtStart / len); + dx *= prop; + dy *= prop; + l.rx2 -= dx; + l.ry2 -= dy; + l.lx1 -= dx; + l.ly1 -= dy; + break; + } + } + } + + static void addSubPath (Path& destPath, Array& subPath, + const bool isClosed, const float width, const float maxMiterExtensionSquared, + const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle, + const Arrowhead* const arrowhead) + { + jassert (subPath.size() > 0); + + if (arrowhead != nullptr) + shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength); + + const LineSection& firstLine = subPath.getReference (0); + + float lastX1 = firstLine.lx1; + float lastY1 = firstLine.ly1; + float lastX2 = firstLine.lx2; + float lastY2 = firstLine.ly2; + + if (isClosed) + { + destPath.startNewSubPath (lastX1, lastY1); + } + else + { + destPath.startNewSubPath (firstLine.rx2, firstLine.ry2); + + if (arrowhead != nullptr) + addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1, + width, arrowhead->startWidth); + else + addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width); + } + + for (int i = 1; i < subPath.size(); ++i) + { + const LineSection& l = subPath.getReference (i); + + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.lx1, l.ly1, l.lx2, l.ly2, + l.x1, l.y1); + + lastX1 = l.lx1; + lastY1 = l.ly1; + lastX2 = l.lx2; + lastY2 = l.ly2; + } + + const LineSection& lastLine = subPath.getReference (subPath.size() - 1); + + if (isClosed) + { + const LineSection& l = subPath.getReference (0); + + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.lx1, l.ly1, l.lx2, l.ly2, + l.x1, l.y1); + + destPath.closeSubPath(); + destPath.startNewSubPath (lastLine.rx1, lastLine.ry1); + } + else + { + destPath.lineTo (lastX2, lastY2); + + if (arrowhead != nullptr) + addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2, + width, arrowhead->endWidth); + else + addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width); + } + + lastX1 = lastLine.rx1; + lastY1 = lastLine.ry1; + lastX2 = lastLine.rx2; + lastY2 = lastLine.ry2; + + for (int i = subPath.size() - 1; --i >= 0;) + { + const LineSection& l = subPath.getReference (i); + + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + l.rx1, l.ry1, l.rx2, l.ry2, + l.x2, l.y2); + + lastX1 = l.rx1; + lastY1 = l.ry1; + lastX2 = l.rx2; + lastY2 = l.ry2; + } + + if (isClosed) + { + addEdgeAndJoint (destPath, jointStyle, + maxMiterExtensionSquared, width, + lastX1, lastY1, lastX2, lastY2, + lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2, + lastLine.x2, lastLine.y2); + } + else + { + // do the last line + destPath.lineTo (lastX2, lastY2); + } + + destPath.closeSubPath(); + } + + static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle, + const PathStrokeType::EndCapStyle endStyle, + Path& destPath, const Path& source, + const AffineTransform& transform, + const float extraAccuracy, const Arrowhead* const arrowhead) + { + jassert (extraAccuracy > 0); + + if (thickness <= 0) + { + destPath.clear(); + return; + } + + const Path* sourcePath = &source; + Path temp; + + if (sourcePath == &destPath) + { + destPath.swapWithPath (temp); + sourcePath = &temp; + } + else + { + destPath.clear(); + } + + destPath.setUsingNonZeroWinding (true); + + const float maxMiterExtensionSquared = 9.0f * thickness * thickness; + const float width = 0.5f * thickness; + + // Iterate the path, creating a list of the + // left/right-hand lines along either side of it... + PathFlatteningIterator it (*sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy); + + Array subPath; + subPath.ensureStorageAllocated (512); + LineSection l; + l.x1 = 0; + l.y1 = 0; + + const float minSegmentLength = 0.0001f; + + while (it.next()) + { + if (it.subPathIndex == 0) + { + if (subPath.size() > 0) + { + addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); + subPath.clearQuick(); + } + + l.x1 = it.x1; + l.y1 = it.y1; + } + + l.x2 = it.x2; + l.y2 = it.y2; + + float dx = l.x2 - l.x1; + float dy = l.y2 - l.y1; + + const float hypotSquared = dx*dx + dy*dy; + + if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath()) + { + const float len = std::sqrt (hypotSquared); + + if (len == 0) + { + l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; + l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; + } + else + { + const float offset = width / len; + dx *= offset; + dy *= offset; + + l.rx2 = l.x1 - dy; + l.ry2 = l.y1 + dx; + l.lx1 = l.x1 + dy; + l.ly1 = l.y1 - dx; + + l.lx2 = l.x2 + dy; + l.ly2 = l.y2 - dx; + l.rx1 = l.x2 - dy; + l.ry1 = l.y2 + dx; + } + + subPath.add (l); + + if (it.closesSubPath) + { + addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); + subPath.clearQuick(); + } + else + { + l.x1 = it.x2; + l.y1 = it.y2; + } + } + } + + if (subPath.size() > 0) + addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); + } +} + +void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath, + const AffineTransform& transform, const float extraAccuracy) const +{ + PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath, + transform, extraAccuracy, 0); +} + +void PathStrokeType::createDashedStroke (Path& destPath, + const Path& sourcePath, + const float* dashLengths, + int numDashLengths, + const AffineTransform& transform, + const float extraAccuracy) const +{ + jassert (extraAccuracy > 0); + + if (thickness <= 0) + return; + + // this should really be an even number.. + jassert ((numDashLengths & 1) == 0); + + Path newDestPath; + PathFlatteningIterator it (sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy); + + bool first = true; + int dashNum = 0; + float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f; + float dx = 0.0f, dy = 0.0f; + + for (;;) + { + const bool isSolid = ((dashNum & 1) == 0); + const float dashLen = dashLengths [dashNum++ % numDashLengths]; + + jassert (dashLen > 0); // must be a positive increment! + if (dashLen <= 0) + break; + + pos += dashLen; + + while (pos > lineEndPos) + { + if (! it.next()) + { + if (isSolid && ! first) + newDestPath.lineTo (it.x2, it.y2); + + createStrokedPath (destPath, newDestPath, AffineTransform::identity, extraAccuracy); + return; + } + + if (isSolid && ! first) + newDestPath.lineTo (it.x1, it.y1); + else + newDestPath.startNewSubPath (it.x1, it.y1); + + dx = it.x2 - it.x1; + dy = it.y2 - it.y1; + lineLen = juce_hypot (dx, dy); + lineEndPos += lineLen; + first = it.closesSubPath; + } + + const float alpha = (pos - (lineEndPos - lineLen)) / lineLen; + + if (isSolid) + newDestPath.lineTo (it.x1 + dx * alpha, + it.y1 + dy * alpha); + else + newDestPath.startNewSubPath (it.x1 + dx * alpha, + it.y1 + dy * alpha); + } +} + +void PathStrokeType::createStrokeWithArrowheads (Path& destPath, + const Path& sourcePath, + const float arrowheadStartWidth, const float arrowheadStartLength, + const float arrowheadEndWidth, const float arrowheadEndLength, + const AffineTransform& transform, + const float extraAccuracy) const +{ + PathStrokeHelpers::Arrowhead head; + head.startWidth = arrowheadStartWidth; + head.startLength = arrowheadStartLength; + head.endWidth = arrowheadEndWidth; + head.endLength = arrowheadEndLength; + + PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, + destPath, sourcePath, transform, extraAccuracy, &head); +} diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.h new file mode 100644 index 0000000..6374741 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.h @@ -0,0 +1,204 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_PATHSTROKETYPE_H_INCLUDED +#define JUCE_PATHSTROKETYPE_H_INCLUDED + + +//============================================================================== +/** + Describes a type of stroke used to render a solid outline along a path. + + A PathStrokeType object can be used directly to create the shape of an outline + around a path, and is used by Graphics::strokePath to specify the type of + stroke to draw. + + @see Path, Graphics::strokePath +*/ +class JUCE_API PathStrokeType +{ +public: + //============================================================================== + /** The type of shape to use for the corners between two adjacent line segments. */ + enum JointStyle + { + mitered, /**< Indicates that corners should be drawn with sharp joints. + Note that for angles that curve back on themselves, drawing a + mitre could require extending the point too far away from the + path, so a mitre limit is imposed and any corners that exceed it + are drawn as bevelled instead. */ + curved, /**< Indicates that corners should be drawn as rounded-off. */ + beveled /**< Indicates that corners should be drawn with a line flattening their + outside edge. */ + }; + + /** The type shape to use for the ends of lines. */ + enum EndCapStyle + { + butt, /**< Ends of lines are flat and don't extend beyond the end point. */ + square, /**< Ends of lines are flat, but stick out beyond the end point for half + the thickness of the stroke. */ + rounded /**< Ends of lines are rounded-off with a circular shape. */ + }; + + //============================================================================== + /** Creates a stroke type with a given line-width, and default joint/end styles. */ + explicit PathStrokeType (float strokeThickness) noexcept; + + /** Creates a stroke type. + + @param strokeThickness the width of the line to use + @param jointStyle the type of joints to use for corners + @param endStyle the type of end-caps to use for the ends of open paths. + */ + PathStrokeType (float strokeThickness, + JointStyle jointStyle, + EndCapStyle endStyle = butt) noexcept; + + /** Creates a copy of another stroke type. */ + PathStrokeType (const PathStrokeType&) noexcept; + + /** Copies another stroke onto this one. */ + PathStrokeType& operator= (const PathStrokeType&) noexcept; + + /** Destructor. */ + ~PathStrokeType() noexcept; + + //============================================================================== + /** Applies this stroke type to a path and returns the resultant stroke as another Path. + + @param destPath the resultant stroked outline shape will be copied into this path. + Note that it's ok for the source and destination Paths to be + the same object, so you can easily turn a path into a stroked version + of itself. + @param sourcePath the path to use as the source + @param transform an optional transform to apply to the points from the source path + as they are being used + @param extraAccuracy if this is greater than 1.0, it will subdivide the path to + a higher resolution, which improves the quality if you'll later want + to enlarge the stroked path. So for example, if you're planning on drawing + the stroke at 3x the size that you're creating it, you should set this to 3. + + @see createDashedStroke + */ + void createStrokedPath (Path& destPath, + const Path& sourcePath, + const AffineTransform& transform = AffineTransform::identity, + float extraAccuracy = 1.0f) const; + + + //============================================================================== + /** Applies this stroke type to a path, creating a dashed line. + + This is similar to createStrokedPath, but uses the array passed in to + break the stroke up into a series of dashes. + + @param destPath the resultant stroked outline shape will be copied into this path. + Note that it's ok for the source and destination Paths to be + the same object, so you can easily turn a path into a stroked version + of itself. + @param sourcePath the path to use as the source + @param dashLengths An array of alternating on/off lengths. E.g. { 2, 3, 4, 5 } will create + a line of length 2, then skip a length of 3, then add a line of length 4, + skip 5, and keep repeating this pattern. + @param numDashLengths The number of lengths in the dashLengths array. This should really be + an even number, otherwise the pattern will get out of step as it + repeats. + @param transform an optional transform to apply to the points from the source path + as they are being used + @param extraAccuracy if this is greater than 1.0, it will subdivide the path to + a higher resolution, which improves the quality if you'll later want + to enlarge the stroked path. So for example, if you're planning on drawing + the stroke at 3x the size that you're creating it, you should set this to 3. + */ + void createDashedStroke (Path& destPath, + const Path& sourcePath, + const float* dashLengths, + int numDashLengths, + const AffineTransform& transform = AffineTransform::identity, + float extraAccuracy = 1.0f) const; + + //============================================================================== + /** Applies this stroke type to a path and returns the resultant stroke as another Path. + + @param destPath the resultant stroked outline shape will be copied into this path. + Note that it's ok for the source and destination Paths to be + the same object, so you can easily turn a path into a stroked version + of itself. + @param sourcePath the path to use as the source + @param arrowheadStartWidth the width of the arrowhead at the start of the path + @param arrowheadStartLength the length of the arrowhead at the start of the path + @param arrowheadEndWidth the width of the arrowhead at the end of the path + @param arrowheadEndLength the length of the arrowhead at the end of the path + @param transform an optional transform to apply to the points from the source path + as they are being used + @param extraAccuracy if this is greater than 1.0, it will subdivide the path to + a higher resolution, which improves the quality if you'll later want + to enlarge the stroked path. So for example, if you're planning on drawing + the stroke at 3x the size that you're creating it, you should set this to 3. + @see createDashedStroke + */ + void createStrokeWithArrowheads (Path& destPath, + const Path& sourcePath, + float arrowheadStartWidth, float arrowheadStartLength, + float arrowheadEndWidth, float arrowheadEndLength, + const AffineTransform& transform = AffineTransform::identity, + float extraAccuracy = 1.0f) const; + + //============================================================================== + /** Returns the stroke thickness. */ + float getStrokeThickness() const noexcept { return thickness; } + + /** Sets the stroke thickness. */ + void setStrokeThickness (float newThickness) noexcept { thickness = newThickness; } + + /** Returns the joint style. */ + JointStyle getJointStyle() const noexcept { return jointStyle; } + + /** Sets the joint style. */ + void setJointStyle (JointStyle newStyle) noexcept { jointStyle = newStyle; } + + /** Returns the end-cap style. */ + EndCapStyle getEndStyle() const noexcept { return endStyle; } + + /** Sets the end-cap style. */ + void setEndStyle (EndCapStyle newStyle) noexcept { endStyle = newStyle; } + + //============================================================================== + /** Compares the stroke thickness, joint and end styles of two stroke types. */ + bool operator== (const PathStrokeType&) const noexcept; + + /** Compares the stroke thickness, joint and end styles of two stroke types. */ + bool operator!= (const PathStrokeType&) const noexcept; + +private: + //============================================================================== + float thickness; + JointStyle jointStyle; + EndCapStyle endStyle; + + JUCE_LEAK_DETECTOR (PathStrokeType) +}; + +#endif // JUCE_PATHSTROKETYPE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h new file mode 100644 index 0000000..7cc5d49 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h @@ -0,0 +1,228 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_POINT_H_INCLUDED +#define JUCE_POINT_H_INCLUDED + + +//============================================================================== +/** + A pair of (x, y) coordinates. + + The ValueType template should be a primitive type such as int, float, double, + rather than a class. + + @see Line, Path, AffineTransform +*/ +template +class Point +{ +public: + /** Creates a point at the origin */ + Point() noexcept : x(), y() {} + + /** Creates a copy of another point. */ + Point (const Point& other) noexcept : x (other.x), y (other.y) {} + + /** Creates a point from an (x, y) position. */ + Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {} + + //============================================================================== + /** Copies this point from another one. */ + Point& operator= (const Point& other) noexcept { x = other.x; y = other.y; return *this; } + + inline bool operator== (Point other) const noexcept { return x == other.x && y == other.y; } + inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; } + + /** Returns true if the point is (0, 0). */ + bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); } + + /** Returns the point's x coordinate. */ + inline ValueType getX() const noexcept { return x; } + + /** Returns the point's y coordinate. */ + inline ValueType getY() const noexcept { return y; } + + /** Sets the point's x coordinate. */ + inline void setX (ValueType newX) noexcept { x = newX; } + + /** Sets the point's y coordinate. */ + inline void setY (ValueType newY) noexcept { y = newY; } + + /** Returns a point which has the same Y position as this one, but a new X. */ + Point withX (ValueType newX) const noexcept { return Point (newX, y); } + + /** Returns a point which has the same X position as this one, but a new Y. */ + Point withY (ValueType newY) const noexcept { return Point (x, newY); } + + /** Changes the point's x and y coordinates. */ + void setXY (ValueType newX, ValueType newY) noexcept { x = newX; y = newY; } + + /** Adds a pair of coordinates to this value. */ + void addXY (ValueType xToAdd, ValueType yToAdd) noexcept { x += xToAdd; y += yToAdd; } + + //============================================================================== + /** Returns a point with a given offset from this one. */ + Point translated (ValueType deltaX, ValueType deltaY) const noexcept { return Point (x + deltaX, y + deltaY); } + + /** Adds two points together */ + Point operator+ (Point other) const noexcept { return Point (x + other.x, y + other.y); } + + /** Adds another point's coordinates to this one */ + Point& operator+= (Point other) noexcept { x += other.x; y += other.y; return *this; } + + /** Subtracts one points from another */ + Point operator- (Point other) const noexcept { return Point (x - other.x, y - other.y); } + + /** Subtracts another point's coordinates to this one */ + Point& operator-= (Point other) noexcept { x -= other.x; y -= other.y; return *this; } + + /** Multiplies two points together */ + template + Point operator* (Point other) const noexcept { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); } + + /** Multiplies another point's coordinates to this one */ + template + Point& operator*= (Point other) noexcept { *this = *this * other; return *this; } + + /** Divides one point by another */ + template + Point operator/ (Point other) const noexcept { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); } + + /** Divides this point's coordinates by another */ + template + Point& operator/= (Point other) noexcept { *this = *this / other; return *this; } + + /** Returns a point whose coordinates are multiplied by a given scalar value. */ + template + Point operator* (FloatType multiplier) const noexcept { return Point ((ValueType) (x * multiplier), (ValueType) (y * multiplier)); } + + /** Returns a point whose coordinates are divided by a given scalar value. */ + template + Point operator/ (FloatType divisor) const noexcept { return Point ((ValueType) (x / divisor), (ValueType) (y / divisor)); } + + /** Multiplies the point's coordinates by a scalar value. */ + template + Point& operator*= (FloatType multiplier) noexcept { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; } + + /** Divides the point's coordinates by a scalar value. */ + template + Point& operator/= (FloatType divisor) noexcept { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; } + + /** Returns the inverse of this point. */ + Point operator-() const noexcept { return Point (-x, -y); } + + //============================================================================== + /** This type will be double if the Point's type is double, otherwise it will be float. */ + typedef typename TypeHelpers::SmallestFloatType::type FloatType; + + //============================================================================== + /** Returns the straight-line distance between this point and the origin. */ + ValueType getDistanceFromOrigin() const noexcept { return juce_hypot (x, y); } + + /** Returns the straight-line distance between this point and another one. */ + ValueType getDistanceFrom (Point other) const noexcept { return juce_hypot (x - other.x, y - other.y); } + + /** Returns the angle from this point to another one. + + The return value is the number of radians clockwise from the 12 o'clock direction, + where this point is the centre and the other point is on the circumference. + */ + FloatType getAngleToPoint (Point other) const noexcept + { + return static_cast (std::atan2 (static_cast (other.x - x), + static_cast (y - other.y))); + } + + /** Returns the point that would be reached by rotating this point clockwise + about the origin by the specified angle. + */ + Point rotatedAboutOrigin (ValueType angleRadians) const noexcept + { + return Point (x * std::cos (angleRadians) - y * std::sin (angleRadians), + x * std::sin (angleRadians) + y * std::cos (angleRadians)); + } + + /** Taking this point to be the centre of a circle, this returns a point on its circumference. + @param radius the radius of the circle. + @param angle the angle of the point, in radians clockwise from the 12 o'clock position. + */ + Point getPointOnCircumference (float radius, float angle) const noexcept + { + return Point (static_cast (x + radius * std::sin (angle)), + static_cast (y - radius * std::cos (angle))); + } + + /** Taking this point to be the centre of an ellipse, this returns a point on its circumference. + @param radiusX the horizontal radius of the circle. + @param radiusY the vertical radius of the circle. + @param angle the angle of the point, in radians clockwise from the 12 o'clock position. + */ + Point getPointOnCircumference (float radiusX, float radiusY, float angle) const noexcept + { + return Point (static_cast (x + radiusX * std::sin (angle)), + static_cast (y - radiusY * std::cos (angle))); + } + + /** Returns the dot-product of two points (x1 * x2 + y1 * y2). */ + FloatType getDotProduct (Point other) const noexcept { return x * other.x + y * other.y; } + + //============================================================================== + /** Uses a transform to change the point's coordinates. + This will only compile if ValueType = float! + + @see AffineTransform::transformPoint + */ + void applyTransform (const AffineTransform& transform) noexcept { transform.transformPoint (x, y); } + + /** Returns the position of this point, if it is transformed by a given AffineTransform. */ + Point transformedBy (const AffineTransform& transform) const noexcept + { + return Point (static_cast (transform.mat00 * x + transform.mat01 * y + transform.mat02), + static_cast (transform.mat10 * x + transform.mat11 * y + transform.mat12)); + } + + //============================================================================== + /** Casts this point to a Point object. */ + Point toInt() const noexcept { return Point (static_cast (x), static_cast (y)); } + + /** Casts this point to a Point object. */ + Point toFloat() const noexcept { return Point (static_cast (x), static_cast (y)); } + + /** Casts this point to a Point object. */ + Point toDouble() const noexcept { return Point (static_cast (x), static_cast (y)); } + + /** Casts this point to a Point object using roundToInt() to convert the values. */ + Point roundToInt() const noexcept { return Point (juce::roundToInt (x), juce::roundToInt (y)); } + + /** Returns the point as a string in the form "x, y". */ + String toString() const { return String (x) + ", " + String (y); } + + //============================================================================== + ValueType x; /**< The point's X coordinate. */ + ValueType y; /**< The point's Y coordinate. */ +}; + + +#endif // JUCE_POINT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h new file mode 100644 index 0000000..6191d1a --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h @@ -0,0 +1,917 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_RECTANGLE_H_INCLUDED +#define JUCE_RECTANGLE_H_INCLUDED + + +//============================================================================== +/** + Manages a rectangle and allows geometric operations to be performed on it. + + @see RectangleList, Path, Line, Point +*/ +template +class Rectangle +{ +public: + //============================================================================== + /** Creates a rectangle of zero size. + The default coordinates will be (0, 0, 0, 0). + */ + Rectangle() noexcept + : w(), h() + { + } + + /** Creates a copy of another rectangle. */ + Rectangle (const Rectangle& other) noexcept + : pos (other.pos), w (other.w), h (other.h) + { + } + + /** Creates a rectangle with a given position and size. */ + Rectangle (const ValueType initialX, const ValueType initialY, + const ValueType width, const ValueType height) noexcept + : pos (initialX, initialY), + w (width), h (height) + { + } + + /** Creates a rectangle with a given size, and a position of (0, 0). */ + Rectangle (const ValueType width, const ValueType height) noexcept + : w (width), h (height) + { + } + + /** Creates a Rectangle from the positions of two opposite corners. */ + Rectangle (const Point corner1, const Point corner2) noexcept + : pos (jmin (corner1.x, corner2.x), + jmin (corner1.y, corner2.y)), + w (corner1.x - corner2.x), + h (corner1.y - corner2.y) + { + if (w < ValueType()) w = -w; + if (h < ValueType()) h = -h; + } + + /** Creates a Rectangle from a set of left, right, top, bottom coordinates. + The right and bottom values must be larger than the left and top ones, or the resulting + rectangle will have a negative size. + */ + static Rectangle leftTopRightBottom (const ValueType left, const ValueType top, + const ValueType right, const ValueType bottom) noexcept + { + return Rectangle (left, top, right - left, bottom - top); + } + + Rectangle& operator= (const Rectangle& other) noexcept + { + pos = other.pos; + w = other.w; h = other.h; + return *this; + } + + /** Destructor. */ + ~Rectangle() noexcept {} + + //============================================================================== + /** Returns true if the rectangle's width or height are zero or less */ + bool isEmpty() const noexcept { return w <= ValueType() || h <= ValueType(); } + + /** Returns the x coordinate of the rectangle's left-hand-side. */ + inline ValueType getX() const noexcept { return pos.x; } + + /** Returns the y coordinate of the rectangle's top edge. */ + inline ValueType getY() const noexcept { return pos.y; } + + /** Returns the width of the rectangle. */ + inline ValueType getWidth() const noexcept { return w; } + + /** Returns the height of the rectangle. */ + inline ValueType getHeight() const noexcept { return h; } + + /** Returns the x coordinate of the rectangle's right-hand-side. */ + inline ValueType getRight() const noexcept { return pos.x + w; } + + /** Returns the y coordinate of the rectangle's bottom edge. */ + inline ValueType getBottom() const noexcept { return pos.y + h; } + + /** Returns the x coordinate of the rectangle's centre. */ + ValueType getCentreX() const noexcept { return pos.x + w / (ValueType) 2; } + + /** Returns the y coordinate of the rectangle's centre. */ + ValueType getCentreY() const noexcept { return pos.y + h / (ValueType) 2; } + + /** Returns the centre point of the rectangle. */ + Point getCentre() const noexcept { return Point (pos.x + w / (ValueType) 2, + pos.y + h / (ValueType) 2); } + + /** Returns the aspect ratio of the rectangle's width / height. + If widthOverHeight is true, it returns width / height; if widthOverHeight is false, + it returns height / width. */ + ValueType getAspectRatio (const bool widthOverHeight = true) const noexcept { return widthOverHeight ? w / h : h / w; } + + //============================================================================== + /** Returns the rectangle's top-left position as a Point. */ + inline Point getPosition() const noexcept { return pos; } + + /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */ + inline void setPosition (const Point newPos) noexcept { pos = newPos; } + + /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */ + inline void setPosition (const ValueType newX, const ValueType newY) noexcept { pos.setXY (newX, newY); } + + /** Returns the rectangle's top-left position as a Point. */ + Point getTopLeft() const noexcept { return pos; } + + /** Returns the rectangle's top-right position as a Point. */ + Point getTopRight() const noexcept { return Point (pos.x + w, pos.y); } + + /** Returns the rectangle's bottom-left position as a Point. */ + Point getBottomLeft() const noexcept { return Point (pos.x, pos.y + h); } + + /** Returns the rectangle's bottom-right position as a Point. */ + Point getBottomRight() const noexcept { return Point (pos.x + w, pos.y + h); } + + /** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */ + void setSize (const ValueType newWidth, const ValueType newHeight) noexcept { w = newWidth; h = newHeight; } + + /** Changes all the rectangle's coordinates. */ + void setBounds (const ValueType newX, const ValueType newY, + const ValueType newWidth, const ValueType newHeight) noexcept { pos.x = newX; pos.y = newY; w = newWidth; h = newHeight; } + + /** Changes the rectangle's X coordinate */ + inline void setX (const ValueType newX) noexcept { pos.x = newX; } + + /** Changes the rectangle's Y coordinate */ + inline void setY (const ValueType newY) noexcept { pos.y = newY; } + + /** Changes the rectangle's width */ + inline void setWidth (const ValueType newWidth) noexcept { w = newWidth; } + + /** Changes the rectangle's height */ + inline void setHeight (const ValueType newHeight) noexcept { h = newHeight; } + + /** Changes the position of the rectangle's centre (leaving its size unchanged). */ + inline void setCentre (const ValueType newCentreX, const ValueType newCentreY) noexcept { pos.x = newCentreX - w / (ValueType) 2; pos.y = newCentreY - h / (ValueType) 2; } + + /** Changes the position of the rectangle's centre (leaving its size unchanged). */ + inline void setCentre (const Point newCentre) noexcept { setCentre (newCentre.x, newCentre.y); } + + /** Returns a rectangle which has the same size and y-position as this one, but with a different x-position. */ + Rectangle withX (const ValueType newX) const noexcept { return Rectangle (newX, pos.y, w, h); } + + /** Returns a rectangle which has the same size and x-position as this one, but with a different y-position. */ + Rectangle withY (const ValueType newY) const noexcept { return Rectangle (pos.x, newY, w, h); } + + /** Returns a rectangle with the same size as this one, but a new position. */ + Rectangle withPosition (const ValueType newX, const ValueType newY) const noexcept { return Rectangle (newX, newY, w, h); } + + /** Returns a rectangle with the same size as this one, but a new position. */ + Rectangle withPosition (const Point newPos) const noexcept { return Rectangle (newPos.x, newPos.y, w, h); } + + /** Returns a rectangle whose size is the same as this one, but whose top-left position is (0, 0). */ + Rectangle withZeroOrigin() const noexcept { return Rectangle (w, h); } + + /** Returns a rectangle with the same size as this one, but a new centre position. */ + Rectangle withCentre (const Point newCentre) const noexcept { return Rectangle (newCentre.x - w / (ValueType) 2, + newCentre.y - h / (ValueType) 2, w, h); } + + /** Returns a rectangle which has the same position and height as this one, but with a different width. */ + Rectangle withWidth (ValueType newWidth) const noexcept { return Rectangle (pos.x, pos.y, newWidth, h); } + + /** Returns a rectangle which has the same position and width as this one, but with a different height. */ + Rectangle withHeight (ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, w, newHeight); } + + /** Returns a rectangle with the same top-left position as this one, but a new size. */ + Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, newWidth, newHeight); } + + /** Returns a rectangle with the same centre position as this one, but a new size. */ + Rectangle withSizeKeepingCentre (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x + (w - newWidth) / (ValueType) 2, + pos.y + (h - newHeight) / (ValueType) 2, newWidth, newHeight); } + + /** Moves the x position, adjusting the width so that the right-hand edge remains in the same place. + If the x is moved to be on the right of the current right-hand edge, the width will be set to zero. + @see withLeft + */ + void setLeft (ValueType newLeft) noexcept { w = jmax (ValueType(), pos.x + w - newLeft); pos.x = newLeft; } + + /** Returns a new rectangle with a different x position, but the same right-hand edge as this one. + If the new x is beyond the right of the current right-hand edge, the width will be set to zero. + @see setLeft + */ + Rectangle withLeft (ValueType newLeft) const noexcept { return Rectangle (newLeft, pos.y, jmax (ValueType(), pos.x + w - newLeft), h); } + + /** Moves the y position, adjusting the height so that the bottom edge remains in the same place. + If the y is moved to be below the current bottom edge, the height will be set to zero. + @see withTop + */ + void setTop (ValueType newTop) noexcept { h = jmax (ValueType(), pos.y + h - newTop); pos.y = newTop; } + + /** Returns a new rectangle with a different y position, but the same bottom edge as this one. + If the new y is beyond the bottom of the current rectangle, the height will be set to zero. + @see setTop + */ + Rectangle withTop (ValueType newTop) const noexcept { return Rectangle (pos.x, newTop, w, jmax (ValueType(), pos.y + h - newTop)); } + + /** Adjusts the width so that the right-hand edge of the rectangle has this new value. + If the new right is below the current X value, the X will be pushed down to match it. + @see getRight, withRight + */ + void setRight (ValueType newRight) noexcept { pos.x = jmin (pos.x, newRight); w = newRight - pos.x; } + + /** Returns a new rectangle with a different right-hand edge position, but the same left-hand edge as this one. + If the new right edge is below the current left-hand edge, the width will be set to zero. + @see setRight + */ + Rectangle withRight (ValueType newRight) const noexcept { return Rectangle (jmin (pos.x, newRight), pos.y, jmax (ValueType(), newRight - pos.x), h); } + + /** Adjusts the height so that the bottom edge of the rectangle has this new value. + If the new bottom is lower than the current Y value, the Y will be pushed down to match it. + @see getBottom, withBottom + */ + void setBottom (ValueType newBottom) noexcept { pos.y = jmin (pos.y, newBottom); h = newBottom - pos.y; } + + /** Returns a new rectangle with a different bottom edge position, but the same top edge as this one. + If the new y is beyond the bottom of the current rectangle, the height will be set to zero. + @see setBottom + */ + Rectangle withBottom (ValueType newBottom) const noexcept { return Rectangle (pos.x, jmin (pos.y, newBottom), w, jmax (ValueType(), newBottom - pos.y)); } + + /** Returns a version of this rectangle with the given amount removed from its left-hand edge. */ + Rectangle withTrimmedLeft (ValueType amountToRemove) const noexcept { return withLeft (pos.x + amountToRemove); } + + /** Returns a version of this rectangle with the given amount removed from its right-hand edge. */ + Rectangle withTrimmedRight (ValueType amountToRemove) const noexcept { return withWidth (w - amountToRemove); } + + /** Returns a version of this rectangle with the given amount removed from its top edge. */ + Rectangle withTrimmedTop (ValueType amountToRemove) const noexcept { return withTop (pos.y + amountToRemove); } + + /** Returns a version of this rectangle with the given amount removed from its bottom edge. */ + Rectangle withTrimmedBottom (ValueType amountToRemove) const noexcept { return withHeight (h - amountToRemove); } + + //============================================================================== + /** Moves the rectangle's position by adding amount to its x and y coordinates. */ + void translate (const ValueType deltaX, + const ValueType deltaY) noexcept + { + pos.x += deltaX; + pos.y += deltaY; + } + + /** Returns a rectangle which is the same as this one moved by a given amount. */ + Rectangle translated (const ValueType deltaX, + const ValueType deltaY) const noexcept + { + return Rectangle (pos.x + deltaX, pos.y + deltaY, w, h); + } + + /** Returns a rectangle which is the same as this one moved by a given amount. */ + Rectangle operator+ (const Point deltaPosition) const noexcept + { + return Rectangle (pos.x + deltaPosition.x, pos.y + deltaPosition.y, w, h); + } + + /** Moves this rectangle by a given amount. */ + Rectangle& operator+= (const Point deltaPosition) noexcept + { + pos += deltaPosition; + return *this; + } + + /** Returns a rectangle which is the same as this one moved by a given amount. */ + Rectangle operator- (const Point deltaPosition) const noexcept + { + return Rectangle (pos.x - deltaPosition.x, pos.y - deltaPosition.y, w, h); + } + + /** Moves this rectangle by a given amount. */ + Rectangle& operator-= (const Point deltaPosition) noexcept + { + pos -= deltaPosition; + return *this; + } + + /** Returns a rectangle that has been scaled by the given amount, centred around the origin. + Note that if the rectangle has int coordinates and it's scaled by a + floating-point amount, then the result will be converted back to integer + coordinates using getSmallestIntegerContainer(). + */ + template + Rectangle operator* (FloatType scaleFactor) const noexcept + { + Rectangle r (*this); + r *= scaleFactor; + return r; + } + + /** Scales this rectangle by the given amount, centred around the origin. + Note that if the rectangle has int coordinates and it's scaled by a + floating-point amount, then the result will be converted back to integer + coordinates using getSmallestIntegerContainer(). + */ + template + Rectangle operator*= (FloatType scaleFactor) noexcept + { + Rectangle (pos.x * scaleFactor, + pos.y * scaleFactor, + w * scaleFactor, + h * scaleFactor).copyWithRounding (*this); + return *this; + } + + /** Scales this rectangle by the given X and Y factors, centred around the origin. + Note that if the rectangle has int coordinates and it's scaled by a + floating-point amount, then the result will be converted back to integer + coordinates using getSmallestIntegerContainer(). + */ + template + Rectangle operator*= (Point scaleFactor) noexcept + { + Rectangle (pos.x * scaleFactor.x, + pos.y * scaleFactor.y, + w * scaleFactor.x, + h * scaleFactor.y).copyWithRounding (*this); + return *this; + } + + /** Scales this rectangle by the given amount, centred around the origin. */ + template + Rectangle operator/ (FloatType scaleFactor) const noexcept + { + Rectangle r (*this); + r /= scaleFactor; + return r; + } + + /** Scales this rectangle by the given amount, centred around the origin. */ + template + Rectangle operator/= (FloatType scaleFactor) noexcept + { + Rectangle (pos.x / scaleFactor, + pos.y / scaleFactor, + w / scaleFactor, + h / scaleFactor).copyWithRounding (*this); + return *this; + } + + /** Scales this rectangle by the given X and Y factors, centred around the origin. */ + template + Rectangle operator/= (Point scaleFactor) noexcept + { + Rectangle (pos.x / scaleFactor.x, + pos.y / scaleFactor.y, + w / scaleFactor.x, + h / scaleFactor.y).copyWithRounding (*this); + return *this; + } + + /** Expands the rectangle by a given amount. + + Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2). + @see expanded, reduce, reduced + */ + void expand (const ValueType deltaX, + const ValueType deltaY) noexcept + { + const ValueType nw = jmax (ValueType(), w + deltaX * 2); + const ValueType nh = jmax (ValueType(), h + deltaY * 2); + setBounds (pos.x - deltaX, pos.y - deltaY, nw, nh); + } + + /** Returns a rectangle that is larger than this one by a given amount. + + Effectively, the rectangle returned is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2). + @see expand, reduce, reduced + */ + Rectangle expanded (const ValueType deltaX, + const ValueType deltaY) const noexcept + { + const ValueType nw = jmax (ValueType(), w + deltaX * 2); + const ValueType nh = jmax (ValueType(), h + deltaY * 2); + return Rectangle (pos.x - deltaX, pos.y - deltaY, nw, nh); + } + + /** Returns a rectangle that is larger than this one by a given amount. + + Effectively, the rectangle returned is (x - delta, y - delta, w + delta * 2, h + delta * 2). + @see expand, reduce, reduced + */ + Rectangle expanded (const ValueType delta) const noexcept + { + return expanded (delta, delta); + } + + /** Shrinks the rectangle by a given amount. + + Effectively, its new size is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2). + @see reduced, expand, expanded + */ + void reduce (const ValueType deltaX, + const ValueType deltaY) noexcept + { + expand (-deltaX, -deltaY); + } + + /** Returns a rectangle that is smaller than this one by a given amount. + + Effectively, the rectangle returned is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2). + @see reduce, expand, expanded + */ + Rectangle reduced (const ValueType deltaX, + const ValueType deltaY) const noexcept + { + return expanded (-deltaX, -deltaY); + } + + /** Returns a rectangle that is smaller than this one by a given amount. + + Effectively, the rectangle returned is (x + delta, y + delta, w - delta * 2, h - delta * 2). + @see reduce, expand, expanded + */ + Rectangle reduced (const ValueType delta) const noexcept + { + return reduced (delta, delta); + } + + /** Removes a strip from the top of this rectangle, reducing this rectangle + by the specified amount and returning the section that was removed. + + E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will + return (100, 100, 300, 50) and leave this rectangle as (100, 150, 300, 250). + + If amountToRemove is greater than the height of this rectangle, it'll be clipped to + that value. + */ + Rectangle removeFromTop (const ValueType amountToRemove) noexcept + { + const Rectangle r (pos.x, pos.y, w, jmin (amountToRemove, h)); + pos.y += r.h; h -= r.h; + return r; + } + + /** Removes a strip from the left-hand edge of this rectangle, reducing this rectangle + by the specified amount and returning the section that was removed. + + E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will + return (100, 100, 50, 300) and leave this rectangle as (150, 100, 250, 300). + + If amountToRemove is greater than the width of this rectangle, it'll be clipped to + that value. + */ + Rectangle removeFromLeft (const ValueType amountToRemove) noexcept + { + const Rectangle r (pos.x, pos.y, jmin (amountToRemove, w), h); + pos.x += r.w; w -= r.w; + return r; + } + + /** Removes a strip from the right-hand edge of this rectangle, reducing this rectangle + by the specified amount and returning the section that was removed. + + E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will + return (250, 100, 50, 300) and leave this rectangle as (100, 100, 250, 300). + + If amountToRemove is greater than the width of this rectangle, it'll be clipped to + that value. + */ + Rectangle removeFromRight (ValueType amountToRemove) noexcept + { + amountToRemove = jmin (amountToRemove, w); + const Rectangle r (pos.x + w - amountToRemove, pos.y, amountToRemove, h); + w -= amountToRemove; + return r; + } + + /** Removes a strip from the bottom of this rectangle, reducing this rectangle + by the specified amount and returning the section that was removed. + + E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will + return (100, 250, 300, 50) and leave this rectangle as (100, 100, 300, 250). + + If amountToRemove is greater than the height of this rectangle, it'll be clipped to + that value. + */ + Rectangle removeFromBottom (ValueType amountToRemove) noexcept + { + amountToRemove = jmin (amountToRemove, h); + const Rectangle r (pos.x, pos.y + h - amountToRemove, w, amountToRemove); + h -= amountToRemove; + return r; + } + + //============================================================================== + /** Returns true if the two rectangles are identical. */ + bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; } + + /** Returns true if the two rectangles are not identical. */ + bool operator!= (const Rectangle& other) const noexcept { return pos != other.pos || w != other.w || h != other.h; } + + /** Returns true if this coordinate is inside the rectangle. */ + bool contains (const ValueType xCoord, const ValueType yCoord) const noexcept + { + return xCoord >= pos.x && yCoord >= pos.y && xCoord < pos.x + w && yCoord < pos.y + h; + } + + /** Returns true if this coordinate is inside the rectangle. */ + bool contains (const Point point) const noexcept + { + return point.x >= pos.x && point.y >= pos.y && point.x < pos.x + w && point.y < pos.y + h; + } + + /** Returns true if this other rectangle is completely inside this one. */ + bool contains (const Rectangle& other) const noexcept + { + return pos.x <= other.pos.x && pos.y <= other.pos.y + && pos.x + w >= other.pos.x + other.w && pos.y + h >= other.pos.y + other.h; + } + + /** Returns the nearest point to the specified point that lies within this rectangle. */ + Point getConstrainedPoint (const Point point) const noexcept + { + return Point (jlimit (pos.x, pos.x + w, point.x), + jlimit (pos.y, pos.y + h, point.y)); + } + + /** Returns a point within this rectangle, specified as proportional coordinates. + The relative X and Y values should be between 0 and 1, where 0 is the left or + top of this rectangle, and 1 is the right or bottom. (Out-of-bounds values + will return a point outside the rectangle). + */ + Point getRelativePoint (double relativeX, double relativeY) const noexcept + { + return Point (pos.x + static_cast (w * relativeX), + pos.y + static_cast (h * relativeY)); + } + + /** Returns true if any part of another rectangle overlaps this one. */ + bool intersects (const Rectangle& other) const noexcept + { + return pos.x + w > other.pos.x + && pos.y + h > other.pos.y + && pos.x < other.pos.x + other.w + && pos.y < other.pos.y + other.h + && w > ValueType() && h > ValueType() + && other.w > ValueType() && other.h > ValueType(); + } + + /** Returns true if any part of the given line lies inside this rectangle. */ + bool intersects (const Line& line) const noexcept + { + return contains (line.getStart()) || contains (line.getEnd()) + || line.intersects (Line (getTopLeft(), getTopRight())) + || line.intersects (Line (getTopRight(), getBottomRight())) + || line.intersects (Line (getBottomRight(), getBottomLeft())) + || line.intersects (Line (getBottomLeft(), getTopLeft())); + } + + /** Returns the region that is the overlap between this and another rectangle. + If the two rectangles don't overlap, the rectangle returned will be empty. + */ + Rectangle getIntersection (const Rectangle& other) const noexcept + { + const ValueType nx = jmax (pos.x, other.pos.x); + const ValueType ny = jmax (pos.y, other.pos.y); + const ValueType nw = jmin (pos.x + w, other.pos.x + other.w) - nx; + const ValueType nh = jmin (pos.y + h, other.pos.y + other.h) - ny; + + if (nw >= ValueType() && nh >= ValueType()) + return Rectangle (nx, ny, nw, nh); + + return Rectangle(); + } + + /** Clips a set of rectangle coordinates so that they lie only within this one. + This is a non-static version of intersectRectangles(). + Returns false if the two rectangles didn't overlap. + */ + bool intersectRectangle (ValueType& otherX, ValueType& otherY, ValueType& otherW, ValueType& otherH) const noexcept + { + const ValueType maxX (jmax (otherX, pos.x)); + otherW = jmin (otherX + otherW, pos.x + w) - maxX; + + if (otherW > ValueType()) + { + const ValueType maxY (jmax (otherY, pos.y)); + otherH = jmin (otherY + otherH, pos.y + h) - maxY; + + if (otherH > ValueType()) + { + otherX = maxX; otherY = maxY; + return true; + } + } + + return false; + } + + /** Clips a rectangle so that it lies only within this one. + Returns false if the two rectangles didn't overlap. + */ + bool intersectRectangle (Rectangle& rectangleToClip) const noexcept + { + return intersectRectangle (rectangleToClip.pos.x, rectangleToClip.pos.y, + rectangleToClip.w, rectangleToClip.h); + } + + /** Returns the smallest rectangle that contains both this one and the one passed-in. + + If either this or the other rectangle are empty, they will not be counted as + part of the resulting region. + */ + Rectangle getUnion (const Rectangle& other) const noexcept + { + if (other.isEmpty()) return *this; + if (isEmpty()) return other; + + const ValueType newX = jmin (pos.x, other.pos.x); + const ValueType newY = jmin (pos.y, other.pos.y); + + return Rectangle (newX, newY, + jmax (pos.x + w, other.pos.x + other.w) - newX, + jmax (pos.y + h, other.pos.y + other.h) - newY); + } + + /** If this rectangle merged with another one results in a simple rectangle, this + will set this rectangle to the result, and return true. + + Returns false and does nothing to this rectangle if the two rectangles don't overlap, + or if they form a complex region. + */ + bool enlargeIfAdjacent (const Rectangle& other) noexcept + { + if (pos.x == other.pos.x && getRight() == other.getRight() + && (other.getBottom() >= pos.y && other.pos.y <= getBottom())) + { + const ValueType newY = jmin (pos.y, other.pos.y); + h = jmax (getBottom(), other.getBottom()) - newY; + pos.y = newY; + return true; + } + + if (pos.y == other.pos.y && getBottom() == other.getBottom() + && (other.getRight() >= pos.x && other.pos.x <= getRight())) + { + const ValueType newX = jmin (pos.x, other.pos.x); + w = jmax (getRight(), other.getRight()) - newX; + pos.x = newX; + return true; + } + + return false; + } + + /** If after removing another rectangle from this one the result is a simple rectangle, + this will set this object's bounds to be the result, and return true. + + Returns false and does nothing to this rectangle if the two rectangles don't overlap, + or if removing the other one would form a complex region. + */ + bool reduceIfPartlyContainedIn (const Rectangle& other) noexcept + { + int inside = 0; + const ValueType otherR (other.getRight()); + if (pos.x >= other.pos.x && pos.x < otherR) inside = 1; + const ValueType otherB (other.getBottom()); + if (pos.y >= other.pos.y && pos.y < otherB) inside |= 2; + const ValueType r (pos.x + w); + if (r >= other.pos.x && r < otherR) inside |= 4; + const ValueType b (pos.y + h); + if (b >= other.pos.y && b < otherB) inside |= 8; + + switch (inside) + { + case 1 + 2 + 8: w = r - otherR; pos.x = otherR; return true; + case 1 + 2 + 4: h = b - otherB; pos.y = otherB; return true; + case 2 + 4 + 8: w = other.pos.x - pos.x; return true; + case 1 + 4 + 8: h = other.pos.y - pos.y; return true; + } + + return false; + } + + /** Tries to fit this rectangle within a target area, returning the result. + + If this rectangle is not completely inside the target area, then it'll be + shifted (without changing its size) so that it lies within the target. If it + is larger than the target rectangle in either dimension, then that dimension + will be reduced to fit within the target. + */ + Rectangle constrainedWithin (const Rectangle& areaToFitWithin) const noexcept + { + const ValueType newW (jmin (w, areaToFitWithin.getWidth())); + const ValueType newH (jmin (h, areaToFitWithin.getHeight())); + + return Rectangle (jlimit (areaToFitWithin.getX(), areaToFitWithin.getRight() - newW, pos.x), + jlimit (areaToFitWithin.getY(), areaToFitWithin.getBottom() - newH, pos.y), + newW, newH); + } + + /** Returns the smallest rectangle that can contain the shape created by applying + a transform to this rectangle. + + This should only be used on floating point rectangles. + */ + Rectangle transformedBy (const AffineTransform& transform) const noexcept + { + typedef typename TypeHelpers::SmallestFloatType::type FloatType; + + FloatType x1 = static_cast (pos.x), y1 = static_cast (pos.y); + FloatType x2 = static_cast (pos.x + w), y2 = static_cast (pos.y); + FloatType x3 = static_cast (pos.x), y3 = static_cast (pos.y + h); + FloatType x4 = static_cast (x2), y4 = static_cast (y3); + + transform.transformPoints (x1, y1, x2, y2); + transform.transformPoints (x3, y3, x4, y4); + + const FloatType rx1 = jmin (x1, x2, x3, x4); + const FloatType rx2 = jmax (x1, x2, x3, x4); + const FloatType ry1 = jmin (y1, y2, y3, y4); + const FloatType ry2 = jmax (y1, y2, y3, y4); + + Rectangle r; + Rectangle (rx1, ry1, rx2 - rx1, ry2 - ry1).copyWithRounding (r); + return r; + } + + /** Returns the smallest integer-aligned rectangle that completely contains this one. + This is only relevent for floating-point rectangles, of course. + @see toFloat() + */ + Rectangle getSmallestIntegerContainer() const noexcept + { + const int x1 = floorAsInt (pos.x); + const int y1 = floorAsInt (pos.y); + const int x2 = ceilAsInt (pos.x + w); + const int y2 = ceilAsInt (pos.y + h); + + return Rectangle (x1, y1, x2 - x1, y2 - y1); + } + + /** Casts this rectangle to a Rectangle. + @see getSmallestIntegerContainer + */ + Rectangle toFloat() const noexcept + { + return Rectangle (static_cast (pos.x), static_cast (pos.y), + static_cast (w), static_cast (h)); + } + + /** Casts this rectangle to a Rectangle. + @see getSmallestIntegerContainer + */ + Rectangle toDouble() const noexcept + { + return Rectangle (static_cast (pos.x), static_cast (pos.y), + static_cast (w), static_cast (h)); + } + + /** Casts this rectangle to a Rectangle with the given type. + If the target type is a conversion from float to int, then the conversion + will be done using getSmallestIntegerContainer(). + */ + template + Rectangle toType() const noexcept + { + Rectangle r; + copyWithRounding (r); + return r; + } + + /** Returns the smallest Rectangle that can contain a set of points. */ + static Rectangle findAreaContainingPoints (const Point* const points, const int numPoints) noexcept + { + if (numPoints == 0) + return Rectangle(); + + ValueType minX (points[0].x); + ValueType maxX (minX); + ValueType minY (points[0].y); + ValueType maxY (minY); + + for (int i = 1; i < numPoints; ++i) + { + minX = jmin (minX, points[i].x); + maxX = jmax (maxX, points[i].x); + minY = jmin (minY, points[i].y); + maxY = jmax (maxY, points[i].y); + } + + return Rectangle (minX, minY, maxX - minX, maxY - minY); + } + + //============================================================================== + /** Static utility to intersect two sets of rectangular coordinates. + Returns false if the two regions didn't overlap. + @see intersectRectangle + */ + static bool intersectRectangles (ValueType& x1, ValueType& y1, ValueType& w1, ValueType& h1, + const ValueType x2, const ValueType y2, const ValueType w2, const ValueType h2) noexcept + { + const ValueType x (jmax (x1, x2)); + w1 = jmin (x1 + w1, x2 + w2) - x; + + if (w1 > ValueType()) + { + const ValueType y (jmax (y1, y2)); + h1 = jmin (y1 + h1, y2 + h2) - y; + + if (h1 > ValueType()) + { + x1 = x; y1 = y; + return true; + } + } + + return false; + } + + //============================================================================== + /** Creates a string describing this rectangle. + + The string will be of the form "x y width height", e.g. "100 100 400 200". + + Coupled with the fromString() method, this is very handy for things like + storing rectangles (particularly component positions) in XML attributes. + + @see fromString + */ + String toString() const + { + String s; + s.preallocateBytes (32); + s << pos.x << ' ' << pos.y << ' ' << w << ' ' << h; + return s; + } + + /** Parses a string containing a rectangle's details. + + The string should contain 4 integer tokens, in the form "x y width height". They + can be comma or whitespace separated. + + This method is intended to go with the toString() method, to form an easy way + of saving/loading rectangles as strings. + + @see toString + */ + static Rectangle fromString (StringRef stringVersion) + { + StringArray toks; + toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", ""); + + return Rectangle (parseIntAfterSpace (toks[0]), + parseIntAfterSpace (toks[1]), + parseIntAfterSpace (toks[2]), + parseIntAfterSpace (toks[3])); + } + + #ifndef DOXYGEN + // This has been renamed by transformedBy, in order to match the method names used in the Point class. + JUCE_DEPRECATED_WITH_BODY (Rectangle transformed (const AffineTransform& t) const noexcept, { return transformedBy (t); }) + #endif + +private: + template friend class Rectangle; + + Point pos; + ValueType w, h; + + static int parseIntAfterSpace (StringRef s) noexcept + { return s.text.findEndOfWhitespace().getIntValue32(); } + + void copyWithRounding (Rectangle& result) const noexcept { result = getSmallestIntegerContainer(); } + void copyWithRounding (Rectangle& result) const noexcept { result = toFloat(); } + void copyWithRounding (Rectangle& result) const noexcept { result = toDouble(); } + + static int floorAsInt (int n) noexcept { return n; } + static int floorAsInt (float n) noexcept { return (int) std::floor (n); } + static int floorAsInt (double n) noexcept { return (int) std::floor (n); } + static int ceilAsInt (int n) noexcept { return n; } + static int ceilAsInt (float n) noexcept { return (int) std::ceil (n); } + static int ceilAsInt (double n) noexcept { return (int) std::ceil (n); } +}; + + +#endif // JUCE_RECTANGLE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_RectangleList.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_RectangleList.h new file mode 100644 index 0000000..f1b81a7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_RectangleList.h @@ -0,0 +1,648 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_RECTANGLELIST_H_INCLUDED +#define JUCE_RECTANGLELIST_H_INCLUDED + + +//============================================================================== +/** + Maintains a set of rectangles as a complex region. + + This class allows a set of rectangles to be treated as a solid shape, and can + add and remove rectangular sections of it, and simplify overlapping or + adjacent rectangles. + + @see Rectangle +*/ +template +class RectangleList +{ +public: + typedef Rectangle RectangleType; + + //============================================================================== + /** Creates an empty RectangleList */ + RectangleList() noexcept {} + + /** Creates a copy of another list */ + RectangleList (const RectangleList& other) : rects (other.rects) + { + } + + /** Creates a list containing just one rectangle. */ + RectangleList (const RectangleType& rect) + { + addWithoutMerging (rect); + } + + /** Copies this list from another one. */ + RectangleList& operator= (const RectangleList& other) + { + rects = other.rects; + return *this; + } + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + RectangleList (RectangleList&& other) noexcept + : rects (static_cast&&> (other.rects)) + { + } + + RectangleList& operator= (RectangleList&& other) noexcept + { + rects = static_cast&&> (other.rects); + return *this; + } + #endif + + //============================================================================== + /** Returns true if the region is empty. */ + bool isEmpty() const noexcept { return rects.size() == 0; } + + /** Returns the number of rectangles in the list. */ + int getNumRectangles() const noexcept { return rects.size(); } + + /** Returns one of the rectangles at a particular index. + @returns the rectangle at the index, or an empty rectangle if the index is out-of-range. + */ + RectangleType getRectangle (int index) const noexcept { return rects[index]; } + + //============================================================================== + /** Removes all rectangles to leave an empty region. */ + void clear() + { + rects.clearQuick(); + } + + /** Merges a new rectangle into the list. + + The rectangle being added will first be clipped to remove any parts of it + that overlap existing rectangles in the list, and adjacent rectangles will be + merged into it. + */ + void add (const RectangleType& rect) + { + if (! rect.isEmpty()) + { + if (rects.size() == 0) + { + rects.add (rect); + } + else + { + bool anyOverlaps = false; + + for (int j = rects.size(); --j >= 0;) + { + RectangleType& ourRect = rects.getReference (j); + + if (rect.intersects (ourRect)) + { + if (rect.contains (ourRect)) + rects.remove (j); + else if (! ourRect.reduceIfPartlyContainedIn (rect)) + anyOverlaps = true; + } + } + + if (anyOverlaps && rects.size() > 0) + { + RectangleList r (rect); + + for (int i = rects.size(); --i >= 0;) + { + const RectangleType& ourRect = rects.getReference (i); + + if (rect.intersects (ourRect)) + { + r.subtract (ourRect); + + if (r.rects.size() == 0) + return; + } + } + + rects.addArray (r.rects); + } + else + { + rects.add (rect); + } + } + } + } + + /** Merges a new rectangle into the list. + + The rectangle being added will first be clipped to remove any parts of it + that overlap existing rectangles in the list. + */ + void add (ValueType x, ValueType y, ValueType width, ValueType height) + { + add (RectangleType (x, y, width, height)); + } + + /** Dumbly adds a rectangle to the list without checking for overlaps. + + This simply adds the rectangle to the end, it doesn't merge it or remove + any overlapping bits. + */ + void addWithoutMerging (const RectangleType& rect) + { + if (! rect.isEmpty()) + rects.add (rect); + } + + /** Merges another rectangle list into this one. + + Any overlaps between the two lists will be clipped, so that the result is + the union of both lists. + */ + void add (const RectangleList& other) + { + for (const RectangleType* r = other.begin(), * const e = other.end(); r != e; ++r) + add (*r); + } + + /** Removes a rectangular region from the list. + + Any rectangles in the list which overlap this will be clipped and subdivided + if necessary. + */ + void subtract (const RectangleType& rect) + { + const int originalNumRects = rects.size(); + + if (originalNumRects > 0) + { + const ValueType x1 = rect.getX(); + const ValueType y1 = rect.getY(); + const ValueType x2 = x1 + rect.getWidth(); + const ValueType y2 = y1 + rect.getHeight(); + + for (int i = getNumRectangles(); --i >= 0;) + { + RectangleType& r = rects.getReference (i); + + const ValueType rx1 = r.getX(); + const ValueType ry1 = r.getY(); + const ValueType rx2 = rx1 + r.getWidth(); + const ValueType ry2 = ry1 + r.getHeight(); + + if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2)) + { + if (x1 > rx1 && x1 < rx2) + { + if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2) + { + r.setWidth (x1 - rx1); + } + else + { + r.setX (x1); + r.setWidth (rx2 - x1); + + rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1, ry2 - ry1)); + ++i; + } + } + else if (x2 > rx1 && x2 < rx2) + { + r.setX (x2); + r.setWidth (rx2 - x2); + + if (y1 > ry1 || y2 < ry2 || x1 > rx1) + { + rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1, ry2 - ry1)); + ++i; + } + } + else if (y1 > ry1 && y1 < ry2) + { + if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2) + { + r.setHeight (y1 - ry1); + } + else + { + r.setY (y1); + r.setHeight (ry2 - y1); + + rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1)); + ++i; + } + } + else if (y2 > ry1 && y2 < ry2) + { + r.setY (y2); + r.setHeight (ry2 - y2); + + if (x1 > rx1 || x2 < rx2 || y1 > ry1) + { + rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1)); + ++i; + } + } + else + { + rects.remove (i); + } + } + } + } + } + + /** Removes all areas in another RectangleList from this one. + + Any rectangles in the list which overlap this will be clipped and subdivided + if necessary. + + @returns true if the resulting list is non-empty. + */ + bool subtract (const RectangleList& otherList) + { + for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;) + subtract (otherList.rects.getReference (i)); + + return rects.size() > 0; + } + + /** Removes any areas of the region that lie outside a given rectangle. + + Any rectangles in the list which overlap this will be clipped and subdivided + if necessary. + + Returns true if the resulting region is not empty, false if it is empty. + + @see getIntersectionWith + */ + bool clipTo (const RectangleType& rect) + { + bool notEmpty = false; + + if (rect.isEmpty()) + { + clear(); + } + else + { + for (int i = rects.size(); --i >= 0;) + { + RectangleType& r = rects.getReference (i); + + if (! rect.intersectRectangle (r)) + rects.remove (i); + else + notEmpty = true; + } + } + + return notEmpty; + } + + /** Removes any areas of the region that lie outside a given rectangle list. + + Any rectangles in this object which overlap the specified list will be clipped + and subdivided if necessary. + + Returns true if the resulting region is not empty, false if it is empty. + + @see getIntersectionWith + */ + template + bool clipTo (const RectangleList& other) + { + if (rects.size() == 0) + return false; + + RectangleList result; + + for (int j = 0; j < rects.size(); ++j) + { + const RectangleType& rect = rects.getReference (j); + + for (const Rectangle* r = other.begin(), * const e = other.end(); r != e; ++r) + { + RectangleType clipped (r->template toType()); + + if (rect.intersectRectangle (clipped)) + result.rects.add (clipped); + } + } + + swapWith (result); + return ! isEmpty(); + } + + /** Creates a region which is the result of clipping this one to a given rectangle. + + Unlike the other clipTo method, this one doesn't affect this object - it puts the + resulting region into the list whose reference is passed-in. + + Returns true if the resulting region is not empty, false if it is empty. + + @see clipTo + */ + bool getIntersectionWith (const RectangleType& rect, RectangleList& destRegion) const + { + destRegion.clear(); + + if (! rect.isEmpty()) + { + for (int i = rects.size(); --i >= 0;) + { + RectangleType r (rects.getReference (i)); + + if (rect.intersectRectangle (r)) + destRegion.rects.add (r); + } + } + + return destRegion.rects.size() > 0; + } + + /** Swaps the contents of this and another list. + + This swaps their internal pointers, so is hugely faster than using copy-by-value + to swap them. + */ + void swapWith (RectangleList& otherList) noexcept + { + rects.swapWith (otherList.rects); + } + + //============================================================================== + /** Checks whether the region contains a given point. + @returns true if the point lies within one of the rectangles in the list + */ + bool containsPoint (Point point) const noexcept + { + for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + if (r->contains (point)) + return true; + + return false; + } + + /** Checks whether the region contains a given point. + @returns true if the point lies within one of the rectangles in the list + */ + bool containsPoint (ValueType x, ValueType y) const noexcept + { + return containsPoint (Point (x, y)); + } + + /** Checks whether the region contains the whole of a given rectangle. + + @returns true all parts of the rectangle passed in lie within the region + defined by this object + @see intersectsRectangle, containsPoint + */ + bool containsRectangle (const RectangleType& rectangleToCheck) const + { + if (rects.size() > 1) + { + RectangleList r (rectangleToCheck); + + for (int i = rects.size(); --i >= 0;) + { + r.subtract (rects.getReference (i)); + + if (r.rects.size() == 0) + return true; + } + } + else if (rects.size() > 0) + { + return rects.getReference (0).contains (rectangleToCheck); + } + + return false; + } + + /** Checks whether the region contains any part of a given rectangle. + + @returns true if any part of the rectangle passed in lies within the region + defined by this object + @see containsRectangle + */ + bool intersectsRectangle (const RectangleType& rectangleToCheck) const noexcept + { + for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + if (r->intersects (rectangleToCheck)) + return true; + + return false; + } + + /** Checks whether this region intersects any part of another one. + + @see intersectsRectangle + */ + bool intersects (const RectangleList& other) const noexcept + { + for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + if (other.intersectsRectangle (*r)) + return true; + + return false; + } + + //============================================================================== + /** Returns the smallest rectangle that can enclose the whole of this region. */ + RectangleType getBounds() const noexcept + { + if (rects.size() <= 1) + { + if (rects.size() == 0) + return RectangleType(); + + return rects.getReference (0); + } + + const RectangleType& r = rects.getReference (0); + + ValueType minX = r.getX(); + ValueType minY = r.getY(); + ValueType maxX = minX + r.getWidth(); + ValueType maxY = minY + r.getHeight(); + + for (int i = rects.size(); --i > 0;) + { + const RectangleType& r2 = rects.getReference (i); + + minX = jmin (minX, r2.getX()); + minY = jmin (minY, r2.getY()); + maxX = jmax (maxX, r2.getRight()); + maxY = jmax (maxY, r2.getBottom()); + } + + return RectangleType (minX, minY, maxX - minX, maxY - minY); + } + + /** Optimises the list into a minimum number of constituent rectangles. + + This will try to combine any adjacent rectangles into larger ones where + possible, to simplify lists that might have been fragmented by repeated + add/subtract calls. + */ + void consolidate() + { + for (int i = 0; i < getNumRectangles() - 1; ++i) + { + RectangleType& r = rects.getReference (i); + const ValueType rx1 = r.getX(); + const ValueType ry1 = r.getY(); + const ValueType rx2 = rx1 + r.getWidth(); + const ValueType ry2 = ry1 + r.getHeight(); + + for (int j = rects.size(); --j > i;) + { + RectangleType& r2 = rects.getReference (j); + const ValueType jrx1 = r2.getX(); + const ValueType jry1 = r2.getY(); + const ValueType jrx2 = jrx1 + r2.getWidth(); + const ValueType jry2 = jry1 + r2.getHeight(); + + // if the vertical edges of any blocks are touching and their horizontals don't + // line up, split them horizontally.. + if (jrx1 == rx2 || jrx2 == rx1) + { + if (jry1 > ry1 && jry1 < ry2) + { + r.setHeight (jry1 - ry1); + rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1)); + i = -1; + break; + } + + if (jry2 > ry1 && jry2 < ry2) + { + r.setHeight (jry2 - ry1); + rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2)); + i = -1; + break; + } + else if (ry1 > jry1 && ry1 < jry2) + { + r2.setHeight (ry1 - jry1); + rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1)); + i = -1; + break; + } + else if (ry2 > jry1 && ry2 < jry2) + { + r2.setHeight (ry2 - jry1); + rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2)); + i = -1; + break; + } + } + } + } + + for (int i = 0; i < rects.size() - 1; ++i) + { + RectangleType& r = rects.getReference (i); + + for (int j = rects.size(); --j > i;) + { + if (r.enlargeIfAdjacent (rects.getReference (j))) + { + rects.remove (j); + i = -1; + break; + } + } + } + } + + /** Adds an x and y value to all the coordinates. */ + void offsetAll (Point offset) noexcept + { + for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + *r += offset; + } + + /** Adds an x and y value to all the coordinates. */ + void offsetAll (ValueType dx, ValueType dy) noexcept + { + offsetAll (Point (dx, dy)); + } + + /** Scales all the coordinates. */ + template + void scaleAll (ScaleType scaleFactor) noexcept + { + for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + *r *= scaleFactor; + } + + /** Applies a transform to all the rectangles. + Obviously this will create a mess if the transform involves any + rotation or skewing. + */ + void transformAll (const AffineTransform& transform) noexcept + { + for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r) + *r = r->transformedBy (transform); + } + + //============================================================================== + /** Creates a Path object to represent this region. */ + Path toPath() const + { + Path p; + + for (int i = 0; i < rects.size(); ++i) + p.addRectangle (rects.getReference (i)); + + return p; + } + + //============================================================================== + /** Standard method for iterating the rectangles in the list. */ + const RectangleType* begin() const noexcept { return rects.begin(); } + /** Standard method for iterating the rectangles in the list. */ + const RectangleType* end() const noexcept { return rects.end(); } + + /** Increases the internal storage to hold a minimum number of rectangles. + Calling this before adding a large number of rectangles means that + the array won't have to keep dynamically resizing itself as the elements + are added, and it'll therefore be more efficient. + @see Array::ensureStorageAllocated + */ + void ensureStorageAllocated (int minNumRectangles) + { + rects.ensureStorageAllocated (minNumRectangles); + } + +private: + //============================================================================== + Array rects; +}; + + +#endif // JUCE_RECTANGLELIST_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/README b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/README new file mode 100644 index 0000000..86cc206 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/README @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/cderror.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/cderror.h new file mode 100644 index 0000000..c19d38f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/cderror.h @@ -0,0 +1,132 @@ +/* + * cderror.h + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the cjpeg/djpeg + * applications. These strings are not needed as part of the JPEG library + * proper. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef CDERROR_H +#define CDERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* CDERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_FIRSTADDONCODE=1000, NULL) /* Must be first entry! */ + +#ifdef BMP_SUPPORTED +JMESSAGE(JERR_BMP_BADCMAP, "Unsupported BMP colormap format") +JMESSAGE(JERR_BMP_BADDEPTH, "Only 8- and 24-bit BMP files are supported") +JMESSAGE(JERR_BMP_BADHEADER, "Invalid BMP file: bad header length") +JMESSAGE(JERR_BMP_BADPLANES, "Invalid BMP file: biPlanes not equal to 1") +JMESSAGE(JERR_BMP_COLORSPACE, "BMP output must be grayscale or RGB") +JMESSAGE(JERR_BMP_COMPRESSED, "Sorry, compressed BMPs not yet supported") +JMESSAGE(JERR_BMP_NOT, "Not a BMP file - does not start with BM") +JMESSAGE(JTRC_BMP, "%ux%u 24-bit BMP image") +JMESSAGE(JTRC_BMP_MAPPED, "%ux%u 8-bit colormapped BMP image") +JMESSAGE(JTRC_BMP_OS2, "%ux%u 24-bit OS2 BMP image") +JMESSAGE(JTRC_BMP_OS2_MAPPED, "%ux%u 8-bit colormapped OS2 BMP image") +#endif /* BMP_SUPPORTED */ + +#ifdef GIF_SUPPORTED +JMESSAGE(JERR_GIF_BUG, "GIF output got confused") +JMESSAGE(JERR_GIF_CODESIZE, "Bogus GIF codesize %d") +JMESSAGE(JERR_GIF_COLORSPACE, "GIF output must be grayscale or RGB") +JMESSAGE(JERR_GIF_IMAGENOTFOUND, "Too few images in GIF file") +JMESSAGE(JERR_GIF_NOT, "Not a GIF file") +JMESSAGE(JTRC_GIF, "%ux%ux%d GIF image") +JMESSAGE(JTRC_GIF_BADVERSION, + "Warning: unexpected GIF version number '%c%c%c'") +JMESSAGE(JTRC_GIF_EXTENSION, "Ignoring GIF extension block of type 0x%02x") +JMESSAGE(JTRC_GIF_NONSQUARE, "Caution: nonsquare pixels in input") +JMESSAGE(JWRN_GIF_BADDATA, "Corrupt data in GIF file") +JMESSAGE(JWRN_GIF_CHAR, "Bogus char 0x%02x in GIF file, ignoring") +JMESSAGE(JWRN_GIF_ENDCODE, "Premature end of GIF image") +JMESSAGE(JWRN_GIF_NOMOREDATA, "Ran out of GIF bits") +#endif /* GIF_SUPPORTED */ + +#ifdef PPM_SUPPORTED +JMESSAGE(JERR_PPM_COLORSPACE, "PPM output must be grayscale or RGB") +JMESSAGE(JERR_PPM_NONNUMERIC, "Nonnumeric data in PPM file") +JMESSAGE(JERR_PPM_NOT, "Not a PPM/PGM file") +JMESSAGE(JTRC_PGM, "%ux%u PGM image") +JMESSAGE(JTRC_PGM_TEXT, "%ux%u text PGM image") +JMESSAGE(JTRC_PPM, "%ux%u PPM image") +JMESSAGE(JTRC_PPM_TEXT, "%ux%u text PPM image") +#endif /* PPM_SUPPORTED */ + +#ifdef RLE_SUPPORTED +JMESSAGE(JERR_RLE_BADERROR, "Bogus error code from RLE library") +JMESSAGE(JERR_RLE_COLORSPACE, "RLE output must be grayscale or RGB") +JMESSAGE(JERR_RLE_DIMENSIONS, "Image dimensions (%ux%u) too large for RLE") +JMESSAGE(JERR_RLE_EMPTY, "Empty RLE file") +JMESSAGE(JERR_RLE_EOF, "Premature EOF in RLE header") +JMESSAGE(JERR_RLE_MEM, "Insufficient memory for RLE header") +JMESSAGE(JERR_RLE_NOT, "Not an RLE file") +JMESSAGE(JERR_RLE_TOOMANYCHANNELS, "Cannot handle %d output channels for RLE") +JMESSAGE(JERR_RLE_UNSUPPORTED, "Cannot handle this RLE setup") +JMESSAGE(JTRC_RLE, "%ux%u full-color RLE file") +JMESSAGE(JTRC_RLE_FULLMAP, "%ux%u full-color RLE file with map of length %d") +JMESSAGE(JTRC_RLE_GRAY, "%ux%u grayscale RLE file") +JMESSAGE(JTRC_RLE_MAPGRAY, "%ux%u grayscale RLE file with map of length %d") +JMESSAGE(JTRC_RLE_MAPPED, "%ux%u colormapped RLE file with map of length %d") +#endif /* RLE_SUPPORTED */ + +#ifdef TARGA_SUPPORTED +JMESSAGE(JERR_TGA_BADCMAP, "Unsupported Targa colormap format") +JMESSAGE(JERR_TGA_BADPARMS, "Invalid or unsupported Targa file") +JMESSAGE(JERR_TGA_COLORSPACE, "Targa output must be grayscale or RGB") +JMESSAGE(JTRC_TGA, "%ux%u RGB Targa image") +JMESSAGE(JTRC_TGA_GRAY, "%ux%u grayscale Targa image") +JMESSAGE(JTRC_TGA_MAPPED, "%ux%u colormapped Targa image") +#else +JMESSAGE(JERR_TGA_NOTCOMP, "Targa support was not compiled") +#endif /* TARGA_SUPPORTED */ + +JMESSAGE(JERR_BAD_CMAP_FILE, + "Color map file is invalid or of unsupported format") +JMESSAGE(JERR_TOO_MANY_COLORS, + "Output file format cannot handle %d colormap entries") +JMESSAGE(JERR_UNGETC_FAILED, "ungetc failed") +#ifdef TARGA_SUPPORTED +JMESSAGE(JERR_UNKNOWN_FORMAT, + "Unrecognized input file format --- perhaps you need -targa") +#else +JMESSAGE(JERR_UNKNOWN_FORMAT, "Unrecognized input file format") +#endif +JMESSAGE(JERR_UNSUPPORTED_FORMAT, "Unsupported output file format") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTADDONCODE +} ADDON_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/changes to libjpeg for JUCE.txt b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/changes to libjpeg for JUCE.txt new file mode 100644 index 0000000..d4924fe --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/changes to libjpeg for JUCE.txt @@ -0,0 +1,16 @@ + +I've included libjpeg in the JUCE tree because loading jpegs is a pretty useful thing to +be able to do, but I've left out as many files as possible to keep it lean-and-mean. + +If you want to get hold of the full version of libjpeg, it's freely available at: + +http://www.ijg.org/ + + +Please note that part of the IJG's license for libjpeg states that: + + "If you use it in a program, you must acknowledge somewhere in + your documentation that you've used the IJG code". + +..so if you release a JUCE program that reads JPEGs, you should probably give them a mention. + diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapimin.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapimin.c new file mode 100644 index 0000000..55e2c9b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapimin.c @@ -0,0 +1,280 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateCompress (j_compress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_compress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_compress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->script_space = NULL; + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL(void) +jpeg_destroy_compress (j_compress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_compress (j_compress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL(void) +jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress) +{ + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL) + qtbl->sent_table = suppress; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL(void) +jpeg_finish_compress (j_compress_ptr cinfo) +{ + JDIMENSION iMCU_row; + + if (cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK) { + /* Terminate first pass */ + if (cinfo->next_scanline < cinfo->image_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_pass) (cinfo); + } else if (cinfo->global_state != CSTATE_WRCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any remaining passes */ + while (! cinfo->master->is_last_pass) { + (*cinfo->master->prepare_for_pass) (cinfo); + for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) { + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + (*cinfo->master->finish_pass) (cinfo); + } + /* Write EOI, do final cleanup */ + (*cinfo->marker->write_file_trailer) (cinfo); + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL(void) +jpeg_write_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +{ + JMETHOD(void, write_marker_byte, (j_compress_ptr info, int val)); + + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); + write_marker_byte = cinfo->marker->write_marker_byte; /* copy for speed */ + while (datalen--) { + (*write_marker_byte) (cinfo, *dataptr); + dataptr++; + } +} + +/* Same, but piecemeal. */ + +GLOBAL(void) +jpeg_write_m_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +{ + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_marker_header) (cinfo, marker, datalen); +} + +GLOBAL(void) +jpeg_write_m_byte (j_compress_ptr cinfo, int val) +{ + (*cinfo->marker->write_marker_byte) (cinfo, val); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL(void) +jpeg_write_tables (j_compress_ptr cinfo) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer(cinfo); + /* Write them tables! */ + (*cinfo->marker->write_tables_only) (cinfo); + /* And clean up. */ + (*cinfo->dest->term_destination) (cinfo); + /* + * In library releases up through v6a, we called jpeg_abort() here to free + * any working memory allocated by the destination manager and marker + * writer. Some applications had a problem with that: they allocated space + * of their own from the library memory manager, and didn't want it to go + * away during write_tables. So now we do nothing. This will cause a + * memory leak if an app calls write_tables repeatedly without doing a full + * compression cycle or otherwise resetting the JPEG object. However, that + * seems less bad than unexpectedly freeing memory in the normal case. + * An app that prefers the old behavior can call jpeg_abort for itself after + * each call to jpeg_write_tables(). + */ +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapistd.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapistd.c new file mode 100644 index 0000000..fed66ca --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcapistd.c @@ -0,0 +1,161 @@ +/* + * jcapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-compression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_compress, it will end up linking in the entire compressor. + * We thus must separate this file from jcapimin.c to avoid linking the + * whole compression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL(void) +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL(JDIMENSION) +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to write raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_write_raw_data (j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != CSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_raw_data. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_raw_data. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Verify that at least one iMCU row has been passed. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE; + if (num_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Directly compress the row. */ + if (! (*cinfo->coef->compress_data) (cinfo, data)) { + /* If compressor did not consume the whole row, suspend processing. */ + return 0; + } + + /* OK, we processed one iMCU row. */ + cinfo->next_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccoefct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccoefct.c new file mode 100644 index 0000000..554a21e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccoefct.c @@ -0,0 +1,449 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF(boolean) compress_data + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF(boolean) compress_first_pass + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +METHODDEF(boolean) compress_output + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#endif + + +LOCAL(void) +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (coef->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(boolean) +compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + + /* Loop to write as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */ + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[compptr->component_index], + coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt); + if (blockcnt < compptr->MCU_width) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt], + (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); + for (bi = blockcnt; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF(JBLOCK)); + for (bi = 0; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF(boolean) +compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (coef->iMCU_row_num < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) (blocks_across % h_samp_factor); + if (ndummy > 0) + ndummy = h_samp_factor - ndummy; + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for (block_row = 0; block_row < block_rows; block_row++) { + thisblockrow = buffer[block_row]; + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) (block_row * DCTSIZE), + (JDIMENSION) 0, blocks_across); + if (ndummy > 0) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK)); + lastDC = thisblockrow[-1][0]; + for (bi = 0; bi < ndummy; bi++) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if (coef->iMCU_row_num == last_iMCU_row) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for (block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row-1]; + jzero_far((void FAR *) thisblockrow, + (size_t) (blocks_across * SIZEOF(JBLOCK))); + for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { + lastDC = lastblockrow[h_samp_factor-1][0]; + for (bi = 0; bi < h_samp_factor; bi++) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output(cinfo, input_buf); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output (j_compress_ptr cinfo, JSAMPIMAGE) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccolor.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccolor.c new file mode 100644 index 0000000..fa239c1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jccolor.c @@ -0,0 +1,459 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ((INT32) CENTERJSAMPLE << SCALEBITS) +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L< Y section */ +#define G_Y_OFF (1*(MAXJSAMPLE+1)) /* offset to G => Y section */ +#define B_Y_OFF (2*(MAXJSAMPLE+1)) /* etc. */ +#define R_CB_OFF (3*(MAXJSAMPLE+1)) +#define G_CB_OFF (4*(MAXJSAMPLE+1)) +#define B_CB_OFF (5*(MAXJSAMPLE+1)) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF (6*(MAXJSAMPLE+1)) +#define B_CR_OFF (7*(MAXJSAMPLE+1)) +#define TABLE_SIZE (8*(MAXJSAMPLE+1)) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF(void) +rgb_ycc_start (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (TABLE_SIZE * SIZEOF(INT32))); + + for (i = 0; i <= MAXJSAMPLE; i++) { + rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i; + rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i; + rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; + rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i; + rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i; + rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +rgb_ycc_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF(void) +rgb_gray_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF(void) +cmyk_ycck_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = MAXJSAMPLE - GETJSAMPLE(inptr[0]); + g = MAXJSAMPLE - GETJSAMPLE(inptr[1]); + b = MAXJSAMPLE - GETJSAMPLE(inptr[2]); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF(void) +grayscale_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF(void) +null_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + /* It seems fastest to make a separate pass for each component. */ + for (ci = 0; ci < nc; ci++) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +null_method (j_compress_ptr) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL(void) +jinit_color_converter (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_converter)); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + if (cinfo->input_components != 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if (cinfo->input_components != RGB_PIXELSIZE) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if (cinfo->input_components != 3) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->input_components != 4) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->input_components < 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_GRAYSCALE) + cconvert->pub.color_convert = grayscale_convert; + else if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = grayscale_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if (cinfo->in_color_space == JCS_YCCK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if (cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components) + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcdctmgr.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcdctmgr.c new file mode 100644 index 0000000..e3f90dc --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcdctmgr.c @@ -0,0 +1,387 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF(void) +start_pass_fdctmgr (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + DCTELEM * dtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = ((DCTELEM) qtbl->quantval[i]) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = (DCTELEM) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-3); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if (fdct->float_divisors[qtblno] == NULL) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(FAST_FLOAT)); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fdtbl[i] = (FAST_FLOAT) + (1.0 / (((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col] * 8.0))); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF(void) +forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for integer DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a,b) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} + + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF(void) +forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for floating-point DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = (FAST_FLOAT) + (GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL(void) +jinit_forward_dct (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_fdct_controller)); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + + /* Mark divisor tables unallocated */ + for (i = 0; i < NUM_QUANT_TBLS; i++) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.c new file mode 100644 index 0000000..581ed59 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.c @@ -0,0 +1,909 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).put_buffer = (src).put_buffer, \ + (dest).put_bits = (src).put_bits, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF(boolean) encode_mcu_huff JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_huff JPP((j_compress_ptr cinfo)); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF(boolean) encode_mcu_gather JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_gather JPP((j_compress_ptr cinfo)); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF(void) +start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Check for invalid table indexes */ + /* (make_c_derived_tbl does this in the other path) */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->dc_count_ptrs[dctbl] == NULL) + entropy->dc_count_ptrs[dctbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * SIZEOF(long)); + if (entropy->ac_count_ptrs[actbl] == NULL) + entropy->ac_count_ptrs[actbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->ac_count_ptrs[actbl], 257 * SIZEOF(long)); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, TRUE, dctbl, + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_c_derived_tbl(cinfo, FALSE, actbl, + & entropy->ac_derived_tbls[actbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + * + * Note this is also used by jcphuff.c. + */ + +GLOBAL(void) +jpeg_make_c_derived_tbl (j_compress_ptr cinfo, boolean isDC, int tblno, + c_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + c_derived_tbl *dtbl; + int p, i, l, lastp, si, maxsymbol; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (c_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(c_derived_tbl)); + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set all codeless symbols to have code length 0; + * this lets us detect duplicate VAL entries here, and later + * allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi)); + + /* This is also a convenient place to check for out-of-range + * and duplicated VAL entries. We allow 0..255 for AC symbols + * but only 0..15 for DC. (We could constrain them further + * based on data depth and mode, but this seems enough.) + */ + maxsymbol = isDC ? 15 : 255; + + for (p = 0; p < lastp; p++) { + i = htbl->huffval[p]; + if (i < 0 || i > maxsymbol || dtbl->ehufsi[i]) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + dtbl->ehufco[i] = huffcode[p]; + dtbl->ehufsi[i] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte(state,val,action) \ + { *(state)->next_output_byte++ = (JOCTET) (val); \ + if (--(state)->free_in_buffer == 0) \ + if (! dump_buffer(state)) \ + { action; } } + + +LOCAL(boolean) +dump_buffer (working_state * state) +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ +{ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if (! (*dest->empty_output_buffer) (state->cinfo)) + return FALSE; + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL(boolean) +emit_bits (working_state * state, unsigned int code, int size) +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE); + + put_buffer &= (((INT32) 1)<cur.put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(state, c, return FALSE); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(state, 0, return FALSE); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL(boolean) +flush_bits (working_state * state) +{ + if (! emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */ + return FALSE; + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL(boolean) +encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl) +{ + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit the Huffman-coded symbol for the number of bits */ + if (! emit_bits(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + if (! emit_bits(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0])) + return FALSE; + r -= 16; + } + + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(state->cinfo, JERR_BAD_DCT_COEF); + + /* Emit Huffman symbol for run length / number of bits */ + i = (r << 4) + nbits; + if (! emit_bits(state, actbl->ehufco[i], actbl->ehufsi[i])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + if (! emit_bits(state, actbl->ehufco[0], actbl->ehufsi[0])) + return FALSE; + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(boolean) +emit_restart (working_state * state, int restart_num) +{ + int ci; + + if (! flush_bits(state)) + return FALSE; + + emit_byte(state, 0xFF, return FALSE); + emit_byte(state, JPEG_RST0 + restart_num, return FALSE); + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < state->cinfo->comps_in_scan; ci++) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF(boolean) +encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! emit_restart(&state, entropy->next_restart_num)) + return FALSE; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if (! encode_one_block(&state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no])) + return FALSE; + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF(void) +finish_pass_huff (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Flush out the last data */ + if (! flush_bits(&state)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); +} + + +/* + * Huffman coding optimization. + * + * We first scan the supplied data and count the number of uses of each symbol + * that is to be Huffman-coded. (This process MUST agree with the code above.) + * Then we build a Huffman coding tree for the observed counts. + * Symbols which are not needed at all for the particular image are not + * assigned any code, which saves space in the DHT marker as well as in + * the compressed data. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL(void) +htest_one_block (j_compress_ptr cinfo, JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[]) +{ + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[(r << 4) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + ac_counts[0]++; +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF(boolean) +encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block(cinfo, MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no]); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the best Huffman code table for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + * + * The JPEG standard requires that no symbol be assigned a codeword of all + * one bits (so that padding bits added at the end of a compressed segment + * can't look like a valid code). Because of the canonical ordering of + * codewords, this just means that there must be an unused slot in the + * longest codeword length category. Section K.2 of the JPEG spec suggests + * reserving such a slot by pretending that symbol 256 is a valid symbol + * with count 1. In theory that's not optimal; giving it count zero but + * including it in the symbol set anyway should give a better Huffman code. + * But the theoretically better code actually seems to come out worse in + * practice, because it produces more all-ones bytes (which incur stuffed + * zero bytes in the final file). In any case the difference is tiny. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in JPEG section K.2. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * typically only very-low-frequency symbols will be given less-than-optimal + * lengths, so the code is almost optimal. Experimental comparisons against + * an optimal limited-length-code algorithm indicate that the difference is + * microscopic --- usually less than a hundredth of a percent of total size. + * So the extra complexity of an optimal algorithm doesn't seem worthwhile. + */ + +GLOBAL(void) +jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]) +{ +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN+1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO(bits, SIZEOF(bits)); + MEMZERO(codesize, SIZEOF(codesize)); + for (i = 0; i < 257; i++) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure 256 has a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed last in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;;) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v && i != c1) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if (c2 < 0) + break; + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while (others[c1] >= 0) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while (others[c2] >= 0) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for (i = 0; i <= 256; i++) { + if (codesize[i]) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if (codesize[i] > MAX_CLEN) + ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW); + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for (i = MAX_CLEN; i > 16; i--) { + while (bits[i] > 0) { + j = i - 2; /* find length of new prefix to be used */ + while (bits[j] == 0) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i-1]++; /* one goes in this length */ + bits[j+1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while (bits[i] == 0) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits)); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for (i = 1; i <= MAX_CLEN; i++) { + for (j = 0; j <= 255; j++) { + if (codesize[j] == i) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF(void) +finish_pass_gather (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did_dc, SIZEOF(did_dc)); + MEMZERO(did_ac, SIZEOF(did_ac)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (! did_dc[dctbl]) { + htblptr = & cinfo->dc_huff_tbl_ptrs[dctbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[dctbl]); + did_dc[dctbl] = TRUE; + } + if (! did_ac[actbl]) { + htblptr = & cinfo->ac_huff_tbl_ptrs[actbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[actbl]); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL(void) +jinit_huff_encoder (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.h new file mode 100644 index 0000000..783b32b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jchuff.h @@ -0,0 +1,52 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* The legal range of a DCT coefficient is + * -1024 .. +1023 for 8-bit data; + * -16384 .. +16383 for 12-bit data. + * Hence the magnitude should always fit in 10 or 14 bits respectively. + */ + +#ifndef _jchuff_h_ +#define _jchuff_h_ + +#if BITS_IN_JSAMPLE == 8 +#define MAX_COEF_BITS 10 +#else +#define MAX_COEF_BITS 14 +#endif + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN(void) jpeg_make_c_derived_tbl + JPP((j_compress_ptr cinfo, boolean isDC, int tblno, + c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN(void) jpeg_gen_optimal_table + JPP((j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[])); + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcinit.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcinit.c new file mode 100644 index 0000000..19de8d0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcinit.c @@ -0,0 +1,72 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL(void) +jinit_compress_master (j_compress_ptr cinfo) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, FALSE /* full compression */); + + /* Preprocessing */ + if (! cinfo->raw_data_in) { + jinit_color_converter(cinfo); + jinit_downsampler(cinfo); + jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); + } + /* Forward DCT */ + jinit_forward_dct(cinfo); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller(cinfo, + (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding)); + jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmainct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmainct.c new file mode 100644 index 0000000..261b284 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmainct.c @@ -0,0 +1,293 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF(void) process_data_buffer_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main_ = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + main_->cur_iMCU_row = 0; /* initialize counters */ + main_->rowgroup_ctr = 0; + main_->suspended = FALSE; + main_->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch (pass_mode) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if (main_->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + main_->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if (main_->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + main_->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF(void) +process_data_simple_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main_ = (my_main_ptr) cinfo->main; + + while (main_->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Read input data if we haven't filled the main buffer yet */ + if (main_->rowgroup_ctr < DCTSIZE) + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main_->buffer, &main_->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if (main_->rowgroup_ctr != DCTSIZE) + return; + + /* Send the completed row to the compressor */ + if (! (*cinfo->coef->compress_data) (cinfo, main_->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main_->suspended) { + (*in_row_ctr)--; + main_->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main_->suspended) { + (*in_row_ctr)++; + main_->suspended = FALSE; + } + main_->rowgroup_ctr = 0; + main_->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF(void) +process_data_buffer_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = (main->pass_mode != JBUF_CRANK_DEST); + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if (main->rowgroup_ctr == 0) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE), + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing); + } + /* In a read pass, pretend we just read some source data. */ + if (! writing) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if (writing) { + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + /* Return to application if we need more data to fill the iMCU row. */ + if (main->rowgroup_ctr < DCTSIZE) + return; + } + + /* Emit data, unless this is a sink-only pass. */ + if (main->pass_mode != JBUF_SAVE_SOURCE) { + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main_; + int ci; + jpeg_component_info *compptr; + + main_ = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_c_main_controller *) main_; + main_->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if (need_full_buffer) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->whole_image[ci] = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor) * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + main_->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main_->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmarker.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmarker.c new file mode 100644 index 0000000..be90314 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmarker.c @@ -0,0 +1,597 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + + +/* Private state */ + +typedef struct { + struct jpeg_marker_writer pub; /* public fields */ + + unsigned int last_restart_interval; /* last DRI value emitted; 0 after SOI */ +} my_marker_writer; + +typedef my_marker_writer * my_marker_ptr; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL(void) +emit_byte (j_compress_ptr cinfo, int val) +/* Emit a byte */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *(dest->next_output_byte)++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) { + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } +} + + +LOCAL(void) +emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark) +/* Emit a marker code */ +{ + emit_byte(cinfo, 0xFF); + emit_byte(cinfo, (int) mark); +} + + +LOCAL(void) +emit_2bytes (j_compress_ptr cinfo, int value) +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ +{ + emit_byte(cinfo, (value >> 8) & 0xFF); + emit_byte(cinfo, value & 0xFF); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL(int) +emit_dqt (j_compress_ptr cinfo, int index) +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ +{ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if (qtbl == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index); + + prec = 0; + for (i = 0; i < DCTSIZE2; i++) { + if (qtbl->quantval[i] > 255) + prec = 1; + } + + if (! qtbl->sent_table) { + emit_marker(cinfo, M_DQT); + + emit_2bytes(cinfo, prec ? DCTSIZE2*2 + 1 + 2 : DCTSIZE2 + 1 + 2); + + emit_byte(cinfo, index + (prec<<4)); + + for (i = 0; i < DCTSIZE2; i++) { + /* The table entries must be emitted in zigzag order. */ + unsigned int qval = qtbl->quantval[jpeg_natural_order[i]]; + if (prec) + emit_byte(cinfo, (int) (qval >> 8)); + emit_byte(cinfo, (int) (qval & 0xFF)); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL(void) +emit_dht (j_compress_ptr cinfo, int index, boolean is_ac) +/* Emit a DHT marker */ +{ + JHUFF_TBL * htbl; + int length, i; + + if (is_ac) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index); + + if (! htbl->sent_table) { + emit_marker(cinfo, M_DHT); + + length = 0; + for (i = 1; i <= 16; i++) + length += htbl->bits[i]; + + emit_2bytes(cinfo, length + 2 + 1 + 16); + emit_byte(cinfo, index); + + for (i = 1; i <= 16; i++) + emit_byte(cinfo, htbl->bits[i]); + + for (i = 0; i < length; i++) + emit_byte(cinfo, htbl->huffval[i]); + + htbl->sent_table = TRUE; + } +} + + +LOCAL(void) +emit_dac (j_compress_ptr) +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +{ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for (i = 0; i < NUM_ARITH_TBLS; i++) + dc_in_use[i] = ac_in_use[i] = 0; + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for (i = 0; i < NUM_ARITH_TBLS; i++) + length += dc_in_use[i] + ac_in_use[i]; + + emit_marker(cinfo, M_DAC); + + emit_2bytes(cinfo, length*2 + 2); + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + if (dc_in_use[i]) { + emit_byte(cinfo, i); + emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4)); + } + if (ac_in_use[i]) { + emit_byte(cinfo, i + 0x10); + emit_byte(cinfo, cinfo->arith_ac_K[i]); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL(void) +emit_dri (j_compress_ptr cinfo) +/* Emit a DRI marker */ +{ + emit_marker(cinfo, M_DRI); + + emit_2bytes(cinfo, 4); /* fixed length */ + + emit_2bytes(cinfo, (int) cinfo->restart_interval); +} + + +LOCAL(void) +emit_sof (j_compress_ptr cinfo, JPEG_MARKER code) +/* Emit a SOF marker */ +{ + int ci; + jpeg_component_info *compptr; + + emit_marker(cinfo, code); + + emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ((long) cinfo->image_height > 65535L || + (long) cinfo->image_width > 65535L) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535); + + emit_byte(cinfo, cinfo->data_precision); + emit_2bytes(cinfo, (int) cinfo->image_height); + emit_2bytes(cinfo, (int) cinfo->image_width); + + emit_byte(cinfo, cinfo->num_components); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + emit_byte(cinfo, compptr->component_id); + emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor); + emit_byte(cinfo, compptr->quant_tbl_no); + } +} + + +LOCAL(void) +emit_sos (j_compress_ptr cinfo) +/* Emit a SOS marker */ +{ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */ + + emit_byte(cinfo, cinfo->comps_in_scan); + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + emit_byte(cinfo, compptr->component_id); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if (cinfo->Ss == 0) { + ta = 0; /* DC scan */ + if (cinfo->Ah != 0 && !cinfo->arith_code) + td = 0; /* no DC table either */ + } else { + td = 0; /* AC scan */ + } + } + emit_byte(cinfo, (td << 4) + ta); + } + + emit_byte(cinfo, cinfo->Ss); + emit_byte(cinfo, cinfo->Se); + emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al); +} + + +LOCAL(void) +emit_jfif_app0 (j_compress_ptr cinfo) +/* Emit a JFIF-compliant APP0 marker */ +{ + /* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - major first) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker(cinfo, M_APP0); + + emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ + + emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */ + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0x49); + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0); + emit_byte(cinfo, cinfo->JFIF_major_version); /* Version fields */ + emit_byte(cinfo, cinfo->JFIF_minor_version); + emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */ + emit_2bytes(cinfo, (int) cinfo->X_density); + emit_2bytes(cinfo, (int) cinfo->Y_density); + emit_byte(cinfo, 0); /* No thumbnail image */ + emit_byte(cinfo, 0); +} + + +LOCAL(void) +emit_adobe_app14 (j_compress_ptr cinfo) +/* Emit an Adobe APP14 marker */ +{ + /* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker(cinfo, M_APP14); + + emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */ + + emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */ + emit_byte(cinfo, 0x64); + emit_byte(cinfo, 0x6F); + emit_byte(cinfo, 0x62); + emit_byte(cinfo, 0x65); + emit_2bytes(cinfo, 100); /* Version */ + emit_2bytes(cinfo, 0); /* Flags0 */ + emit_2bytes(cinfo, 0); /* Flags1 */ + switch (cinfo->jpeg_color_space) { + case JCS_YCbCr: + emit_byte(cinfo, 1); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte(cinfo, 2); /* Color transform = 2 */ + break; + default: + emit_byte(cinfo, 0); /* Color transform = 0 */ + break; + } +} + + +/* + * These routines allow writing an arbitrary marker with parameters. + * The only intended use is to emit COM or APPn markers after calling + * write_file_header and before calling write_frame_header. + * Other uses are not guaranteed to produce desirable results. + * Counting the parameter bytes properly is the caller's responsibility. + */ + +METHODDEF(void) +write_marker_header (j_compress_ptr cinfo, int marker, unsigned int datalen) +/* Emit an arbitrary marker header */ +{ + if (datalen > (unsigned int) 65533) /* safety check */ + ERREXIT(cinfo, JERR_BAD_LENGTH); + + emit_marker(cinfo, (JPEG_MARKER) marker); + + emit_2bytes(cinfo, (int) (datalen + 2)); /* total length */ +} + +METHODDEF(void) +write_marker_byte (j_compress_ptr cinfo, int val) +/* Emit one byte of marker parameters following write_marker_header */ +{ + emit_byte(cinfo, val); +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF(void) +write_file_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + + emit_marker(cinfo, M_SOI); /* first the SOI */ + + /* SOI is defined to reset restart interval to 0 */ + marker->last_restart_interval = 0; + + if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */ + emit_jfif_app0(cinfo); + if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */ + emit_adobe_app14(cinfo); +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF(void) +write_frame_header (j_compress_ptr cinfo) +{ + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prec += emit_dqt(cinfo, compptr->quant_tbl_no); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if (cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1) + is_baseline = FALSE; + } + if (prec && is_baseline) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS(cinfo, 0, JTRC_16BIT_TABLES); + } + } + + /* Emit the proper SOF marker */ + if (cinfo->arith_code) { + emit_sof(cinfo, M_SOF9); /* SOF code for arithmetic coding */ + } else { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */ + else if (is_baseline) + emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */ + else + emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */ + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF(void) +write_scan_header (j_compress_ptr cinfo) +{ + my_marker_ptr marker = (my_marker_ptr) cinfo->marker; + int i; + jpeg_component_info *compptr; + + if (cinfo->arith_code) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac(cinfo); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if (cinfo->Ss == 0) { + if (cinfo->Ah == 0) /* DC needs no table for refinement scan */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + } else { + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * We avoid wasting space with unnecessary DRIs, however. + */ + if (cinfo->restart_interval != marker->last_restart_interval) { + emit_dri(cinfo); + marker->last_restart_interval = cinfo->restart_interval; + } + + emit_sos(cinfo); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF(void) +write_file_trailer (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_EOI); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF(void) +write_tables_only (j_compress_ptr cinfo) +{ + int i; + + emit_marker(cinfo, M_SOI); + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if (cinfo->quant_tbl_ptrs[i] != NULL) + (void) emit_dqt(cinfo, i); + } + + if (! cinfo->arith_code) { + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if (cinfo->dc_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, FALSE); + if (cinfo->ac_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, TRUE); + } + } + + emit_marker(cinfo, M_EOI); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL(void) +jinit_marker_writer (j_compress_ptr cinfo) +{ + my_marker_ptr marker; + + /* Create the subobject */ + marker = (my_marker_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_marker_writer)); + cinfo->marker = (struct jpeg_marker_writer *) marker; + /* Initialize method pointers */ + marker->pub.write_file_header = write_file_header; + marker->pub.write_frame_header = write_frame_header; + marker->pub.write_scan_header = write_scan_header; + marker->pub.write_file_trailer = write_file_trailer; + marker->pub.write_tables_only = write_tables_only; + marker->pub.write_marker_header = write_marker_header; + marker->pub.write_marker_byte = write_marker_byte; + /* Initialize private state */ + marker->last_restart_interval = 0; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmaster.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmaster.c new file mode 100644 index 0000000..a45b03b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcmaster.c @@ -0,0 +1,590 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL(void) +initial_setup (j_compress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ + int ci; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 || cinfo->input_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL(void) +validate_script (j_compress_ptr cinfo) +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ +{ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if (cinfo->num_scans <= 0) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = & last_bitpos[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (coefi = 0; coefi < DCTSIZE2; coefi++) + *last_bitpos_ptr++ = -1; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + for (ci = 0; ci < cinfo->num_components; ci++) + component_sent[ci] = FALSE; + } + + for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN); + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (thisi < 0 || thisi >= cinfo->num_components) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + /* Components must appear in SOF order within each scan */ + if (ci > 0 && thisi <= scanptr->component_index[ci-1]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* The JPEG spec simply gives the ranges 0..13 for Ah and Al, but that + * seems wrong: the upper bound ought to depend on data precision. + * Perhaps they really meant 0..N+1 for N-bit precision. + * Here we allow 0..10 for 8-bit data; Al larger than 10 results in + * out-of-range reconstructed DC values during the first DC scan, + * which might cause problems for some decoders. + */ +#if BITS_IN_JSAMPLE == 8 +#define MAX_AH_AL 10 +#else +#define MAX_AH_AL 13 +#endif + if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > MAX_AH_AL || Al < 0 || Al > MAX_AH_AL) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + if (Ss == 0) { + if (Se != 0) /* DC and AC together not OK */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + if (ncomps != 1) /* AC scans must be for only one component */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + for (ci = 0; ci < ncomps; ci++) { + last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0]; + if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + for (coefi = Ss; coefi <= Se; coefi++) { + if (last_bitpos_ptr[coefi] < 0) { + /* first scan of this coefficient */ + if (Ah != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + /* not first scan */ + if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + /* Make sure components are not sent twice */ + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (component_sent[thisi]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for (ci = 0; ci < cinfo->num_components; ci++) { + if (last_bitpos[ci][0] < 0) + ERREXIT(cinfo, JERR_MISSING_DATA); + } +#endif + } else { + for (ci = 0; ci < cinfo->num_components; ci++) { + if (! component_sent[ci]) + ERREXIT(cinfo, JERR_MISSING_DATA); + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL(void) +select_scan_parameters (j_compress_ptr cinfo) +/* Set up the scan parameters for the current scan */ +{ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if (cinfo->scan_info != NULL) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for (ci = 0; ci < scanptr->comps_in_scan; ci++) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } + else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if (cinfo->num_components > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN); + cinfo->comps_in_scan = cinfo->num_components; + for (ci = 0; ci < cinfo->num_components; ci++) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2-1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL(void) +per_scan_setup (j_compress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if (cinfo->restart_in_rows > 0) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF(void) +prepare_for_pass (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch (master->pass_type) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (! cinfo->raw_data_in) { + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->downsample->start_pass) (cinfo); + (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU); + } + (*cinfo->fdct->start_pass) (cinfo); + (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding); + (*cinfo->coef->start_pass) (cinfo, + (master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + if (cinfo->optimize_coding) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code) { + (*cinfo->entropy->start_pass) (cinfo, TRUE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if (! cinfo->optimize_coding) { + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + } + (*cinfo->entropy->start_pass) (cinfo, FALSE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + /* We emit frame/scan headers now */ + if (master->scan_number == 0) + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + } + + master->pub.is_last_pass = (master->pass_number == master->total_passes-1); + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF(void) +pass_startup (j_compress_ptr cinfo) +{ + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF(void) +finish_pass_master (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + (*cinfo->entropy->finish_pass) (cinfo); + + /* Update state for next pass */ + switch (master->pass_type) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if (! cinfo->optimize_coding) + master->scan_number++; + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL(void) +jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_comp_master)); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup(cinfo); + + if (cinfo->scan_info != NULL) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if (cinfo->progressive_mode) /* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */ + + /* Initialize my private state */ + if (transcode_only) { + /* no main pass in transcoding */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + else + master->pass_type = output_pass; + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if (cinfo->optimize_coding) + master->total_passes = cinfo->num_scans * 2; + else + master->total_passes = cinfo->num_scans; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcomapi.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcomapi.c new file mode 100644 index 0000000..1b1a340 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcomapi.c @@ -0,0 +1,106 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Do nothing if called on a not-initialized or destroyed JPEG object. */ + if (cinfo->mem == NULL) + return; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + if (cinfo->is_decompressor) { + cinfo->global_state = DSTATE_START; + /* Try to keep application from accessing now-deleted marker list. + * A bit kludgy to do it here, but this is the most central place. + */ + ((j_decompress_ptr) cinfo)->marker_list = NULL; + } else { + cinfo->global_state = CSTATE_START; + } +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL(void) +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL(JQUANT_TBL *) +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL(JHUFF_TBL *) +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jconfig.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jconfig.h new file mode 100644 index 0000000..22f6140 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jconfig.h @@ -0,0 +1,57 @@ +/* jconfig.vc --- jconfig.h for Microsoft Visual C++ on Windows 95 or NT. */ +/* see jconfig.doc for explanations */ + +// disable all the warnings under MSVC +#ifdef _MSC_VER +#pragma warning (disable: 4996 4267 4100 4127 4702 4244) +#endif + +#ifdef __BORLANDC__ +#pragma warn -8057 +#pragma warn -8019 +#pragma warn -8004 +#pragma warn -8008 +#endif + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#undef CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* we presume a 32-bit flat memory model */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +/* Define "boolean" as unsigned char, not int, per Windows custom */ +#ifndef __RPCNDR_H__ /* don't conflict if rpcndr.h already read */ +typedef unsigned char boolean; +#endif +#define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */ + + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#define TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Microsoft has setmode() */ +#undef NEED_SIGNAL_CATCHER +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcparam.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcparam.c new file mode 100644 index 0000000..739dcdf --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcparam.c @@ -0,0 +1,610 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL(void) +jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + JQUANT_TBL ** qtblptr; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (which_tbl < 0 || which_tbl >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, which_tbl); + + qtblptr = & cinfo->quant_tbl_ptrs[which_tbl]; + + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo); + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long) basic_table[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (force_baseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + (*qtblptr)->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*qtblptr)->sent_table = FALSE; +} + + +GLOBAL(void) +jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +{ + /* These are the sample quantization tables given in JPEG spec section K.1. + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline); +} + + +GLOBAL(int) +jpeg_quality_scaling (int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table + * to make all the table entries 1 (hence, minimum quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality*2; + + return quality; +} + + +GLOBAL(void) +jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_set_linear_quality(cinfo, quality, force_baseline); +} + + +/* + * Huffman table setup routines + */ + +LOCAL(void) +add_huff_table (j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + int nsymbols, len; + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + /* Copy the number-of-symbols-of-each-code-length counts */ + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + + /* Validate the counts. We do this here mainly so we can copy the right + * number of symbols from the val[] array, without risking marching off + * the end of memory. jchuff.c will do a more thorough test later. + */ + nsymbols = 0; + for (len = 1; len <= 16; len++) + nsymbols += bits[len]; + if (nsymbols < 1 || nsymbols > 256) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + MEMCOPY((*htblptr)->huffval, val, nsymbols * SIZEOF(UINT8)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL(void) +std_huff_tables (j_compress_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance); + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL(void) +jpeg_set_defaults (j_compress_ptr cinfo) +{ + int i; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if (cinfo->comp_info == NULL) + cinfo->comp_info = (jpeg_component_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF(jpeg_component_info)); + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality(cinfo, 75, TRUE); + /* Set up two Huffman tables */ + std_huff_tables(cinfo); + + /* Initialize default arithmetic coding conditioning */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if (cinfo->data_precision > 8) + cinfo->optimize_coding = TRUE; + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + * + * By default, the library emits JFIF version code 1.01. + * An application that wants to emit JFIF 1.02 extension markers should set + * JFIF_minor_version to 2. We could probably get away with just defaulting + * to 1.02, but there may still be some decoders in use that will complain + * about that; saying 1.01 should minimize compatibility problems. + */ + cinfo->JFIF_major_version = 1; /* Default JFIF version = 1.01 */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace(cinfo); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL(void) +jpeg_default_colorspace (j_compress_ptr cinfo) +{ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL(void) +jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace) +{ + jpeg_component_info * compptr; + int ci; + +#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl) \ + (compptr = &cinfo->comp_info[index], \ + compptr->component_id = (id), \ + compptr->h_samp_factor = (hsamp), \ + compptr->v_samp_factor = (vsamp), \ + compptr->quant_tbl_no = (quant), \ + compptr->dc_tbl_no = (dctbl), \ + compptr->ac_tbl_no = (actbl) ) + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP(0, 1, 1,1, 0, 0,0); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0); + SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0); + SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0); + SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0); + SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0); + SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + SET_COMP(3, 4, 2,2, 0, 0,0); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + for (ci = 0; ci < cinfo->num_components; ci++) { + SET_COMP(ci, ci, 1,1, 0, 0,0); + } + break; + default: + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL(jpeg_scan_info *) +fill_a_scan (jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for specified component */ +{ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_scans (jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for each component */ +{ + int ci; + + for (ci = 0; ci < ncomps; ci++) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL(jpeg_scan_info *) +fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) +/* Support routine: generate interleaved DC scan if possible, else N scans */ +{ + int ci; + + if (ncomps <= MAX_COMPS_IN_SCAN) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for (ci = 0; ci < ncomps; ci++) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL(void) +jpeg_simple_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. + * We need to put it in the permanent pool in case the application performs + * multiple compressions without changing the settings. To avoid a memory + * leak if jpeg_simple_progression is called repeatedly for the same JPEG + * object, we try to re-use previously allocated space, and we allocate + * enough space to handle YCbCr even if initially asked for grayscale. + */ + if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) { + cinfo->script_space_size = MAX(nscans, 10); + cinfo->script_space = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + cinfo->script_space_size * SIZEOF(jpeg_scan_info)); + } + scanptr = cinfo->script_space; + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + /* Finish AC successive approximation */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + /* Luma bottom bit comes last since it's usually largest scan */ + fill_a_scan(scanptr, 0, 1, 63, 1, 0); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2); + scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2); + /* Successive approximation second pass */ + scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1); + /* Successive approximation final pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + fill_scans(scanptr, ncomps, 1, 63, 1, 0); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcphuff.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcphuff.c new file mode 100644 index 0000000..692b52b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcphuff.c @@ -0,0 +1,833 @@ +/* + * jcphuff.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Forward declarations */ +METHODDEF(boolean) encode_mcu_DC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_AC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_DC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) encode_mcu_AC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(void) finish_pass_phuff JPP((j_compress_ptr cinfo)); +METHODDEF(void) finish_pass_gather_phuff JPP((j_compress_ptr cinfo)); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF(void) +start_pass_phuff (j_compress_ptr cinfo, boolean gather_statistics) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = (cinfo->Ss == 0); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if (entropy->bit_buffer == NULL) + entropy->bit_buffer = (char *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF(char)); + } + } + if (gather_statistics) + entropy->pub.finish_pass = finish_pass_gather_phuff; + else + entropy->pub.finish_pass = finish_pass_phuff; + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Get table index */ + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + } + if (gather_statistics) { + /* Check for invalid table index */ + /* (make_c_derived_tbl does this in the other path) */ + if (tbl < 0 || tbl >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->count_ptrs[tbl] == NULL) + entropy->count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + /* Compute derived values for Huffman table */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, is_DC_band, tbl, + & entropy->derived_tbls[tbl]); + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte(entropy,val) \ + { *(entropy)->next_output_byte++ = (JOCTET) (val); \ + if (--(entropy)->free_in_buffer == 0) \ + dump_buffer_p(entropy); } + + +LOCAL(void) +dump_buffer_p (phuff_entropy_ptr entropy) +/* Empty the output buffer; we do not support suspension in this module. */ +{ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if (! (*dest->empty_output_buffer) (entropy->cinfo)) + ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND); + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL(void) +emit_bits_p (phuff_entropy_ptr entropy, unsigned int code, int size) +/* Emit some bits, unless we are in gather mode */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + if (entropy->gather_statistics) + return; /* do nothing if we're only getting stats */ + + put_buffer &= (((INT32) 1)<put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(entropy, c); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(entropy, 0); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer; /* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL(void) +flush_bits_p (phuff_entropy_ptr entropy) +{ + emit_bits_p(entropy, 0x7F, 7); /* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL(void) +emit_symbol (phuff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits_p(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL(void) +emit_buffered_bits (phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits) +{ + if (entropy->gather_statistics) + return; /* no real work */ + + while (nbits > 0) { + emit_bits_p(entropy, (unsigned int) (*bufstart), 1); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL(void) +emit_eobrun (phuff_entropy_ptr entropy) +{ + register int temp, nbits; + + if (entropy->EOBRUN > 0) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ((temp >>= 1)) + nbits++; + /* safety check: shouldn't happen given limited correction-bit buffer */ + if (nbits > 14) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + emit_symbol(entropy, entropy->ac_tbl_no, nbits << 4); + if (nbits) + emit_bits_p(entropy, entropy->EOBRUN, nbits); + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL(void) +emit_restart_p (phuff_entropy_ptr entropy, int restart_num) +{ + int ci; + + emit_eobrun(entropy); + + if (! entropy->gather_statistics) { + flush_bits_p(entropy); + emit_byte(entropy, 0xFF); + emit_byte(entropy, JPEG_RST0 + restart_num); + } + + if (entropy->cinfo->Ss == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++) + entropy->last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_p(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + /* Check for out-of-range coefficient values. + * Since we're encoding a difference, the range limit is twice as much. + */ + if (nbits > MAX_COEF_BITS+1) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol(entropy, compptr->dc_tbl_no, nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + emit_bits_p(entropy, (unsigned int) temp2, nbits); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_p(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = (*block)[jpeg_natural_order[k]]) == 0) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if (temp == 0) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if (entropy->EOBRUN > 0) + emit_eobrun(entropy); + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + /* Check for out-of-range coefficient values */ + if (nbits > MAX_COEF_BITS) + ERREXIT(cinfo, JERR_BAD_DCT_COEF); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits_p(entropy, (unsigned int) temp2, nbits); + + r = 0; /* reset zero run length */ + } + + if (r > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if (entropy->EOBRUN == 0x7FFF) + emit_eobrun(entropy); /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_p(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = (*block)[0]; + emit_bits_p(entropy, (unsigned int) (temp >> Al), 1); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart_p(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for (k = cinfo->Ss; k <= Se; k++) { + temp = (*block)[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if (temp < 0) + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if (temp == 1) + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = absvalues[k]) == 0) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while (r > 15 && k <= EOB) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + /* Emit ZRL */ + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if (temp > 1) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) (temp & 1); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1); + + /* Emit output bit for newly-nonzero coef */ + temp = ((*block)[jpeg_natural_order[k]] < 0) ? 0 : 1; + emit_bits_p(entropy, (unsigned int) temp, 1); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1)) + emit_eobrun(entropy); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF(void) +finish_pass_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun(entropy); + flush_bits_p(entropy); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF(void) +finish_pass_gather_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun(entropy); + + is_DC_band = (cinfo->Ss == 0); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did, SIZEOF(did)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if (! did[tbl]) { + if (is_DC_band) + htblptr = & cinfo->dc_huff_tbl_ptrs[tbl]; + else + htblptr = & cinfo->ac_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[tbl]); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL(void) +jinit_phuff_encoder (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcprepct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcprepct.c new file mode 100644 index 0000000..fdc4bc2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcprepct.c @@ -0,0 +1,354 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if (pass_mode != JBUF_PASS_THRU) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL(void) +expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows) +{ + register int row; + + for (row = input_rows; row < output_rows; row++) { + jcopy_sample_rows(image_data, input_rows-1, image_data, row, + 1, num_cols); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF(void) +pre_process_data (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if (prep->next_buf_row == cinfo->max_v_samp_factor) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr); + prep->next_buf_row = 0; + (*out_row_group_ctr)++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF(void) +pre_process_context (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + + while (*out_row_group_ctr < out_row_groups_avail) { + if (*in_row_ctr < in_rows_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + /* Pad at top of image, if first time through */ + if (prep->rows_to_go == cinfo->image_height) { + for (ci = 0; ci < cinfo->num_components; ci++) { + int row; + for (row = 1; row <= cinfo->max_v_samp_factor; row++) { + jcopy_sample_rows(prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if (prep->rows_to_go != 0) + break; + /* When at bottom of image, pad to fill the conversion buffer. */ + if (prep->next_buf_row < prep->next_buf_stop) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop); + } + prep->next_buf_row = prep->next_buf_stop; + } + } + /* If we've gotten enough data, downsample a row group. */ + if (prep->next_buf_row == prep->next_buf_stop) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr); + (*out_row_group_ctr)++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if (prep->this_row_group >= buf_height) + prep->this_row_group = 0; + if (prep->next_buf_row >= buf_height) + prep->next_buf_row = 0; + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL(void) +create_context_buffer (j_compress_ptr cinfo) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (cinfo->num_components * 5 * rgroup_height) * + SIZEOF(JSAMPROW)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) (3 * rgroup_height)); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY(fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF(JSAMPROW)); + /* Fill in the above and below wraparound pointers */ + for (i = 0; i < rgroup_height; i++) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL(void) +jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if (need_full_buffer) /* safety check */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + prep = (my_prep_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_prep_controller)); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if (cinfo->downsample->need_context_rows) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prep->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcsample.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcsample.c new file mode 100644 index 0000000..37c658f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jcsample.c @@ -0,0 +1,519 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD(void, downsample1_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data)); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF(void) +start_pass_downsample (j_compress_ptr) +{ + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL(void) +expand_right_edge (JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols) +{ + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) (output_cols - input_cols); + + if (numcols > 0) { + for (row = 0; row < num_rows; row++) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for (count = numcols; count > 0; count--) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF(void) +sep_downsample (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + (out_row_group_index * compptr->v_samp_factor); + (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF(void) +int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix/2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + for (outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand) { + outvalue = 0; + for (v = 0; v < v_expand; v++) { + inptr = input_data[inrow+v] + outcol_h; + for (h = 0; h < h_expand; h++) { + outvalue += (INT32) GETJSAMPLE(*inptr++); + } + } + *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF(void) +fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + /* Copy the data */ + jcopy_sample_rows(input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width); + /* Edge-expand */ + expand_right_edge(output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF(void) +h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1]) + + bias) >> 1); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF(void) +h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]) + + bias) >> 2); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]); + neighsum += neighsum; + neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]); + neighsum += neighsum; + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF(void) +fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow-1]; + below_ptr = input_data[outrow+1]; + + /* Special case for first column */ + colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) + + GETJSAMPLE(*inptr); + membersum = GETJSAMPLE(*inptr++); + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = colsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + membersum = GETJSAMPLE(*inptr++); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL(void) +jinit_downsampler (j_compress_ptr cinfo) +{ + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_downsampler)); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if (cinfo->CCIR601_sampling) + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, and set up method pointers */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ((cinfo->max_h_samp_factor % compptr->h_samp_factor) == 0 && + (cinfo->max_v_samp_factor % compptr->v_samp_factor) == 0) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor && !smoothok) + TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL); +#endif +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jctrans.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jctrans.c new file mode 100644 index 0000000..7e209f9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jctrans.c @@ -0,0 +1,388 @@ +/* + * jctrans.c + * + * Copyright (C) 1995-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transencode_master_selection + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); +LOCAL(void) transencode_coef_controller + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL(void) +jpeg_write_coefficients (j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Mark all tables to be written */ + jpeg_suppress_tables(cinfo, FALSE); + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + transencode_master_selection(cinfo, coef_arrays); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0; /* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL(void) +jpeg_copy_critical_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) +{ + JQUANT_TBL ** qtblptr; + jpeg_component_info *incomp, *outcomp; + JQUANT_TBL *c_quant, *slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if (dstinfo->global_state != CSTATE_START) + ERREXIT1(dstinfo, JERR_BAD_STATE, dstinfo->global_state); + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + /* Initialize all parameters to default values */ + jpeg_set_defaults(dstinfo); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace(dstinfo, srcinfo->jpeg_color_space); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + if (srcinfo->quant_tbl_ptrs[tblno] != NULL) { + qtblptr = & dstinfo->quant_tbl_ptrs[tblno]; + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) dstinfo); + MEMCOPY((*qtblptr)->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF((*qtblptr)->quantval)); + (*qtblptr)->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if (dstinfo->num_components < 1 || dstinfo->num_components > MAX_COMPONENTS) + ERREXIT2(dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS); + for (ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if (tblno < 0 || tblno >= NUM_QUANT_TBLS || + srcinfo->quant_tbl_ptrs[tblno] == NULL) + ERREXIT1(dstinfo, JERR_NO_QUANT_TABLE, tblno); + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if (c_quant != NULL) { + for (coefi = 0; coefi < DCTSIZE2; coefi++) { + if (c_quant->quantval[coefi] != slot_quant->quantval[coefi]) + ERREXIT1(dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno); + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } + /* Also copy JFIF version and resolution information, if available. + * Strictly speaking this isn't "critical" info, but it's nearly + * always appropriate to copy it if available. In particular, + * if the application chooses to copy JFIF 1.02 extension markers from + * the source file, we need to copy the version to make sure we don't + * emit a file that has 1.02 extensions but a claimed version of 1.01. + * We will *not*, however, copy version info from mislabeled "2.01" files. + */ + if (srcinfo->saw_JFIF_marker) { + if (srcinfo->JFIF_major_version == 1) { + dstinfo->JFIF_major_version = srcinfo->JFIF_major_version; + dstinfo->JFIF_minor_version = srcinfo->JFIF_minor_version; + } + dstinfo->density_unit = srcinfo->density_unit; + dstinfo->X_density = srcinfo->X_density; + dstinfo->Y_density = srcinfo->Y_density; + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL(void) +transencode_master_selection (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + /* Although we don't actually use input_components for transcoding, + * jcmaster.c's initial_setup will complain if input_components is 0. + */ + cinfo->input_components = 1; + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, TRUE /* transcode only */); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller(cinfo, coef_arrays); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI, JFIF) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller2; + +typedef my_coef_controller2 * my_coef_ptr2; + + +LOCAL(void) +start_iMCU_row2 (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr2 coef = (my_coef_ptr2) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_coef2 (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr2 coef = (my_coef_ptr2) cinfo->coef; + + if (pass_mode != JBUF_CRANK_DEST) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + coef->iMCU_row_num = 0; + start_iMCU_row2(cinfo); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF(boolean) +compress_output2 (j_compress_ptr cinfo, JSAMPIMAGE) +{ + my_coef_ptr2 coef = (my_coef_ptr2) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yindex+yoffset < compptr->last_row_height) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < blockcnt; xindex++) + MCU_buffer[blkn++] = buffer_ptr++; + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn-1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row2(cinfo); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL(void) +transencode_coef_controller (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + my_coef_ptr2 coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller2)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef2; + coef->pub.compress_data = compress_output2; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + jzero_far((void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapimin.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapimin.c new file mode 100644 index 0000000..bdff21b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapimin.c @@ -0,0 +1,395 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL(void) +jpeg_CreateDecompress (j_decompress_ptr cinfo, int version, size_t structsize) +{ + int i; + + /* Guard against version mismatches between library and caller. */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ + if (version != JPEG_LIB_VERSION) + ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); + if (structsize != SIZEOF(struct jpeg_decompress_struct)) + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize); + + /* For debugging purposes, we zero the whole master structure. + * But the application has already set the err pointer, and may have set + * client_data, so we have to save and restore those fields. + * Note: if application hasn't set client_data, tools like Purify may + * complain here. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + void * client_data = cinfo->client_data; /* ignore Purify complaint here */ + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + cinfo->client_data = client_data; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + cinfo->marker_list = NULL; + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL(void) +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL(void) +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Set default decompression parameters. + */ + +LOCAL(void) +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL(int) +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL(int) +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL(boolean) +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL(boolean) +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapistd.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapistd.c new file mode 100644 index 0000000..f6c7fff --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdapistd.c @@ -0,0 +1,275 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(boolean) output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL(boolean) +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL(JDIMENSION) +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL(JDIMENSION) +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL(boolean) +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL(boolean) +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdatasrc.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdatasrc.c new file mode 100644 index 0000000..bd4770e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdatasrc.c @@ -0,0 +1,212 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + FILE * infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF(void) +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF(boolean) +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + + nbytes = JFREAD(src->infile, src->buffer, INPUT_BUF_SIZE); + + if (nbytes <= 0) { + if (src->start_of_file) /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF(void) +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +term_source (j_decompress_ptr) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL(void) +jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcoefct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcoefct.c new file mode 100644 index 0000000..4f15e63 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcoefct.c @@ -0,0 +1,736 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller3; + +typedef my_coef_controller3 * my_coef_ptr3; + +/* Forward declarations */ +METHODDEF(int) decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF(int) decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL(boolean) smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF(int) decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL(void) +start_iMCU_row3 (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF(void) +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row3(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF(void) +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image, + * which we index according to the component's SOF position. + */ + +METHODDEF(int) +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[compptr->component_index] + + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row3(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF(int) +dummy_consume_data (j_decompress_ptr) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF(int) +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row3(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF(int) +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* Natural-order array positions of the first 5 zigzag-order coefficients */ +#define Q01_POS 1 +#define Q10_POS 8 +#define Q20_POS 16 +#define Q11_POS 9 +#define Q02_POS 2 + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL(boolean) +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + if (qtable->quantval[0] == 0 || + qtable->quantval[Q01_POS] == 0 || + qtable->quantval[Q10_POS] == 0 || + qtable->quantval[Q20_POS] == 0 || + qtable->quantval[Q11_POS] == 0 || + qtable->quantval[Q02_POS] == 0) + return FALSE; + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF(int) +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr3 coef = (my_coef_ptr3) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[Q01_POS]; + Q10 = quanttbl->quantval[Q10_POS]; + Q20 = quanttbl->quantval[Q20_POS]; + Q11 = quanttbl->quantval[Q11_POS]; + Q02 = quanttbl->quantval[Q02_POS]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL(void) +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr3 coef; + + coef = (my_coef_ptr3) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller3)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcolor.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcolor.c new file mode 100644 index 0000000..a4ca256 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdcolor.c @@ -0,0 +1,396 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter2; + +typedef my_color_deconverter2 * my_cconvert_ptr2; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL(void) +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr2 cconvert = (my_cconvert_ptr2) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF(void) +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr2 cconvert = (my_cconvert_ptr2) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF(void) +null_convert2 (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF(void) +grayscale_convert2 (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Convert grayscale to RGB: just duplicate the graylevel three times. + * This is provided to support applications that don't want to cope + * with grayscale as a separate case. + */ + +METHODDEF(void) +gray_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + + while (--num_rows >= 0) { + inptr = input_buf[0][input_row++]; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + /* We can dispense with GETJSAMPLE() here */ + outptr[RGB_RED] = outptr[RGB_GREEN] = outptr[RGB_BLUE] = inptr[col]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF(void) +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr2 cconvert = (my_cconvert_ptr2) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF(void) +start_pass_dcolor (j_decompress_ptr) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL(void) +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr2 cconvert; + int ci; + + cconvert = (my_cconvert_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter2)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert2; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + cconvert->pub.color_convert = gray_rgb_convert; + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert2; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert2; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert2; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdct.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdct.h new file mode 100644 index 0000000..d8df1b4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdct.h @@ -0,0 +1,182 @@ +/* + * jdct.h + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#ifndef __jdct_h__ +#define __jdct_h__ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN(void) jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN(void) jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN(void) jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN(void) jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN(void) jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif + + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jddctmgr.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jddctmgr.c new file mode 100644 index 0000000..0e44eb1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jddctmgr.c @@ -0,0 +1,269 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF(void) +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch (compptr->DCT_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored as ints to ensure access efficiency. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL(void) +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.c new file mode 100644 index 0000000..1468a83 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.c @@ -0,0 +1,625 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state2; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state2 saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + + /* Precalculated info set up by start_pass for use in decode_mcu: */ + + /* Pointers to derived tables to be used for each block within an MCU */ + d_derived_tbl * dc_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + d_derived_tbl * ac_cur_tbls[D_MAX_BLOCKS_IN_MCU]; + /* Whether we care about the DC and AC coefficient values for each block */ + boolean dc_needed[D_MAX_BLOCKS_IN_MCU]; + boolean ac_needed[D_MAX_BLOCKS_IN_MCU]; +} huff_entropy_decoder2; + +typedef huff_entropy_decoder2 * huff_entropy_ptr2; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF(void) +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr2 entropy = (huff_entropy_ptr2) cinfo->entropy; + int ci, blkn, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, TRUE, dctbl, + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, FALSE, actbl, + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Precalculate decoding info for each block in an MCU of this scan */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + /* Precalculate which table to use for each block */ + entropy->dc_cur_tbls[blkn] = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + entropy->ac_cur_tbls[blkn] = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + /* Decide whether we really care about the coefficient values */ + if (compptr->component_needed) { + entropy->dc_needed[blkn] = TRUE; + /* we don't need the ACs if producing a 1/8th-size image */ + entropy->ac_needed[blkn] = (compptr->DCT_scaled_size > 1); + } else { + entropy->dc_needed[blkn] = entropy->ac_needed[blkn] = FALSE; + } + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->pub.insufficient_data = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * This routine also performs some validation checks on the table. + * + * Note this is also used by jdphuff.c. + */ + +GLOBAL(void) +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, + d_derived_tbl ** pdtbl) +{ + JHUFF_TBL *htbl; + d_derived_tbl *dtbl; + int p, i, l, si, numsymbols; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Note that huffsize[] and huffcode[] are filled in code-length order, + * paralleling the order of the symbols themselves in htbl->huffval[]. + */ + + /* Find the input Huffman table */ + if (tblno < 0 || tblno >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + htbl = + isDC ? cinfo->dc_huff_tbl_ptrs[tblno] : cinfo->ac_huff_tbl_ptrs[tblno]; + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno); + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + + p = 0; + for (l = 1; l <= 16; l++) { + i = (int) htbl->bits[l]; + if (i < 0 || p + i > 256) /* protect against table overrun */ + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + while (i--) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + numsymbols = p; + + /* Figure C.2: generate the codes themselves */ + /* We also validate that the counts represent a legal Huffman code tree. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + /* code is now 1 more than the last code used for codelength si; but + * it must still fit in si bits, since no code is allowed to be all ones. + */ + if (((INT32) code) >= (((INT32) 1) << si)) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + /* valoffset[l] = huffval[] index of 1st symbol of code length l, + * minus the minimum code of length l + */ + dtbl->valoffset[l] = (INT32) p - (INT32) huffcode[p]; + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } + + /* Validate symbols as being reasonable. + * For AC tables, we make no check, but accept all byte values 0..255. + * For DC tables, we require the symbols to be in range 0..15. + * (Tighter bounds could be applied depending on the data depth and mode, + * but this is sufficient to ensure safe decoding.) + */ + if (isDC) { + for (i = 0; i < numsymbols; i++) { + int sym = htbl->huffval[i]; + if (sym < 0 || sym > 15) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL(boolean) +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + j_decompress_ptr cinfo = state->cinfo; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + /* We fail to do so only if we hit a marker or are forced to suspend. */ + + if (cinfo->unread_marker == 0) { /* cannot advance past a marker */ + while (bits_left < MIN_GET_BITS) { + register int c; + + /* Attempt to read a byte */ + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + /* Loop here to discard any padding FF's on terminating marker, + * so that we can save a valid unread_marker value. NOTE: we will + * accept multiple FF's followed by a 0 as meaning a single FF data + * byte. This data pattern is not valid according to the standard. + */ + do { + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. + * Save the marker code for later use. + * Fine point: it might appear that we should save the marker into + * bitread working state, not straight into permanent state. But + * once we have hit a marker, we cannot need to suspend within the + * current MCU, because we will read no more bytes from the data + * source. So it is OK to update permanent state right away. + */ + cinfo->unread_marker = c; + /* See if we need to insert some fake zero bits. */ + goto no_more_bytes; + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } /* end while */ + } else { + no_more_bytes: + /* We get here if we've read the marker that terminates the compressed + * data segment. There should be enough bits in the buffer register + * to satisfy the request; if so, no problem. + */ + if (nbits > bits_left) { + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * We use a nonvolatile flag to ensure that only one warning message + * appears per data segment. + */ + if (! cinfo->entropy->insufficient_data) { + WARNMS(cinfo, JWRN_HIT_MARKER); + cinfo->entropy->insufficient_data = TRUE; + } + /* Fill the buffer with zero bits */ + get_buffer <<= MIN_GET_BITS - bits_left; + bits_left = MIN_GET_BITS; + } + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL(int) +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ (int) (code + htbl->valoffset[l]) ]; +} + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL(boolean) +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr2 entropy = (huff_entropy_ptr2) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Reset out-of-data flag, unless read_restart_marker left us smack up + * against a marker. In that case we will end up treating the next data + * segment as empty, and we can avoid producing bogus output pixels by + * leaving the flag set. + */ + if (cinfo->unread_marker == 0) + entropy->pub.insufficient_data = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF(boolean) +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr2 entropy = (huff_entropy_ptr2) cinfo->entropy; + int blkn; + BITREAD_STATE_VARS; + savable_state2 state; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + JBLOCKROW block = MCU_data[blkn]; + d_derived_tbl * dctbl = entropy->dc_cur_tbls[blkn]; + d_derived_tbl * actbl = entropy->ac_cur_tbls[blkn]; + register int s, k, r; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + if (entropy->dc_needed[blkn]) { + /* Convert DC difference to actual value, update last_dc_val */ + int ci = cinfo->MCU_membership[blkn]; + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + } + + if (entropy->ac_needed[blkn]) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL(void) +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr2 entropy; + int i; + + entropy = (huff_entropy_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder2)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.h new file mode 100644 index 0000000..1b13987 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdhuff.h @@ -0,0 +1,206 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifndef __jdhuff_h__ +#define __jdhuff_h__ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + INT32 valoffset[17]; /* huffval[] offset for codes of length k */ + /* valoffset[k] = huffval[] index of 1st symbol of code length k, less + * the smallest code of length k; so given a code of length k, the + * corresponding symbol is huffval[code + valoffset[k]] + */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't define the size + * with something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* Current data source location */ + /* We need a copy, rather than munging the original, in case of suspension */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + /* Bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* Pointer needed by jpeg_fill_bit_buffer. */ + j_decompress_ptr cinfo; /* back link to decompress master record */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN(boolean) jpeg_fill_bit_buffer + JPP((bitread_working_state * state, register bit_buf_type get_buffer, + register int bits_left, int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN(int) jpeg_huff_decode + JPP((bitread_working_state * state, register bit_buf_type get_buffer, + register int bits_left, d_derived_tbl * htbl, int min_bits)); + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdinput.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdinput.c new file mode 100644 index 0000000..621b714 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdinput.c @@ -0,0 +1,381 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF(int) consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL(void) +initial_setup2 (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL(void) +per_scan_setup2 (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL(void) +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF(void) +start_input_pass2 (j_decompress_ptr cinfo) +{ + per_scan_setup2(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF(void) +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF(int) +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + initial_setup2(cinfo); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapimin.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + start_input_pass2(cinfo); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass2; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmainct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmainct.c new file mode 100644 index 0000000..ee23fa8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmainct.c @@ -0,0 +1,512 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller4; + +typedef my_main_controller4 * my_main_ptr4; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF(void) process_data_simple_main2 + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF(void) process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL(void) +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main_->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main_->xbuffer[1] = main_->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main_->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + main_->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL(void) +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main_->xbuffer[0][ci]; + xbuf1 = main_->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main_->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL(void) +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main_->xbuffer[0][ci]; + xbuf1 = main_->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL(void) +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + main_->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main_->xbuffer[main_->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_main2 (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + main_->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + main_->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main_->context_state = CTX_PREPARE_FOR_IMCU; + main_->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main_->pub.process_data = process_data_simple_main2; + } + main_->buffer_full = FALSE; /* Mark buffer empty */ + main_->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main_->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF(void) +process_data_simple_main2 (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main_->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main_->buffer)) + return; /* suspension forced, can do nothing more */ + main_->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, main_->buffer, + &main_->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (main_->rowgroup_ctr >= rowgroups_avail) { + main_->buffer_full = FALSE; + main_->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF(void) +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr4 main_ = (my_main_ptr4) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main_->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + main_->xbuffer[main_->whichptr])) + return; /* suspension forced, can do nothing more */ + main_->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main_->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (main_->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, main_->xbuffer[main_->whichptr], + &main_->rowgroup_ctr, main_->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_->rowgroup_ctr < main_->rowgroups_avail) + return; /* Need to suspend */ + main_->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main_->rowgroup_ctr = 0; + main_->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (main_->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + main_->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, main_->xbuffer[main_->whichptr], + &main_->rowgroup_ctr, main_->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_->rowgroup_ctr < main_->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (main_->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + main_->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main_->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main_->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + main_->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + main_->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF(void) +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL(void) +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr4 main_; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + main_ = (my_main_ptr4) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller4)); + cinfo->main = (struct jpeg_d_main_controller *) main_; + main_->pub.start_pass = start_pass_main2; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + main_->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmarker.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmarker.c new file mode 100644 index 0000000..0a57cca --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmarker.c @@ -0,0 +1,1292 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_marker_reader pub; /* public fields */ + + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* Limit on marker data length to save for each marker type */ + unsigned int length_limit_COM; + unsigned int length_limit_APPn[16]; + + /* Status of COM/APPn marker saving */ + jpeg_saved_marker_ptr cur_marker; /* NULL if not processing a marker */ + unsigned int bytes_read; /* data bytes read so far in marker */ + /* Note: cur_marker is not linked into marker_list until it's all read. */ +} my_marker_reader; + +typedef my_marker_reader * my_marker_ptr2; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- used only in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters + * can fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments + * that might not fit. If we are simply dropping such a marker, we use + * skip_input_data to get past it, and thereby put the problem on the + * source manager's shoulders. If we are saving the marker's contents + * into memory, we use a slightly different convention: when forced to + * suspend, the marker processor updates the restart point to the end of + * what it's consumed (ie, the end of the buffer) before returning FALSE. + * On resumption, cinfo->unread_marker still contains the marker code, + * but the data source will point to the next chunk of marker data. + * The marker processor must retain internal state to deal with this. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL(boolean) +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->JFIF_major_version = 1; /* set default JFIF APP0 values */ + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL(boolean) +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +#ifdef D_ARITH_CODING_SUPPORTED + +LOCAL(boolean) +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + +#else /* ! D_ARITH_CODING_SUPPORTED */ + +#define get_dac(cinfo) skip_variable(cinfo) + +#endif /* D_ARITH_CODING_SUPPORTED */ + + +LOCAL(boolean) +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 16) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + /* Here we just do minimal validation of the counts to avoid walking + * off the end of our table space. jdhuff.c will check more carefully. + */ + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + /* We convert the zigzag-order table to natural array order. */ + quant_ptr->quantval[jpeg_natural_order[i]] = (UINT16) tmp; + } + + if (cinfo->err->trace_level >= 2) { + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + if (length != 0) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Routines for processing APPn and COM markers. + * These are either saved in memory or discarded, per application request. + * APP0 and APP14 are specially checked to see if they are + * JFIF and Adobe markers, respectively. + */ + +#define APP0_DATA_LEN 14 /* Length of interesting data in APP0 */ +#define APP14_DATA_LEN 12 /* Length of interesting data in APP14 */ +#define APPN_DATA_LEN 14 /* Must be the largest of the above!! */ + + +LOCAL(void) +examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP0. + * Take appropriate action if it is a JFIF marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + INT32 totallen = (INT32) datalen + remaining; + + if (datalen >= APP0_DATA_LEN && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x49 && + GETJOCTET(data[3]) == 0x46 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF APP0 marker: save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->JFIF_major_version = GETJOCTET(data[5]); + cinfo->JFIF_minor_version = GETJOCTET(data[6]); + cinfo->density_unit = GETJOCTET(data[7]); + cinfo->X_density = (GETJOCTET(data[8]) << 8) + GETJOCTET(data[9]); + cinfo->Y_density = (GETJOCTET(data[10]) << 8) + GETJOCTET(data[11]); + /* Check version. + * Major version must be 1, anything else signals an incompatible change. + * (We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec.) + * Minor version should be 0..2, but process anyway if newer. + */ + if (cinfo->JFIF_major_version != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version); + /* Generate trace messages */ + TRACEMS5(cinfo, 1, JTRC_JFIF, + cinfo->JFIF_major_version, cinfo->JFIF_minor_version, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + /* Validate thumbnail dimensions and issue appropriate messages */ + if (GETJOCTET(data[12]) | GETJOCTET(data[13])) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, + GETJOCTET(data[12]), GETJOCTET(data[13])); + totallen -= APP0_DATA_LEN; + if (totallen != + ((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) totallen); + } else if (datalen >= 6 && + GETJOCTET(data[0]) == 0x4A && + GETJOCTET(data[1]) == 0x46 && + GETJOCTET(data[2]) == 0x58 && + GETJOCTET(data[3]) == 0x58 && + GETJOCTET(data[4]) == 0) { + /* Found JFIF "JFXX" extension APP0 marker */ + /* The library doesn't actually do anything with these, + * but we try to produce a helpful trace message. + */ + switch (GETJOCTET(data[5])) { + case 0x10: + TRACEMS1(cinfo, 1, JTRC_THUMB_JPEG, (int) totallen); + break; + case 0x11: + TRACEMS1(cinfo, 1, JTRC_THUMB_PALETTE, (int) totallen); + break; + case 0x13: + TRACEMS1(cinfo, 1, JTRC_THUMB_RGB, (int) totallen); + break; + default: + TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION, + GETJOCTET(data[5]), (int) totallen); + break; + } + } else { + /* Start of APP0 does not match "JFIF" or "JFXX", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) totallen); + } +} + + +LOCAL(void) +examine_app14 (j_decompress_ptr cinfo, JOCTET FAR * data, + unsigned int datalen, INT32 remaining) +/* Examine first few bytes from an APP14. + * Take appropriate action if it is an Adobe marker. + * datalen is # of bytes at data[], remaining is length of rest of marker data. + */ +{ + unsigned int version, flags0, flags1, transform; + + if (datalen >= APP14_DATA_LEN && + GETJOCTET(data[0]) == 0x41 && + GETJOCTET(data[1]) == 0x64 && + GETJOCTET(data[2]) == 0x6F && + GETJOCTET(data[3]) == 0x62 && + GETJOCTET(data[4]) == 0x65) { + /* Found Adobe APP14 marker */ + version = (GETJOCTET(data[5]) << 8) + GETJOCTET(data[6]); + flags0 = (GETJOCTET(data[7]) << 8) + GETJOCTET(data[8]); + flags1 = (GETJOCTET(data[9]) << 8) + GETJOCTET(data[10]); + transform = GETJOCTET(data[11]); + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe", or too short */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) (datalen + remaining)); + } +} + + +METHODDEF(boolean) +get_interesting_appn (j_decompress_ptr cinfo) +/* Process an APP0 or APP14 marker without saving it */ +{ + INT32 length; + JOCTET b[APPN_DATA_LEN]; + unsigned int i, numtoread; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* get the interesting part of the marker data */ + if (length >= APPN_DATA_LEN) + numtoread = APPN_DATA_LEN; + else if (length > 0) + numtoread = (unsigned int) length; + else + numtoread = 0; + for (i = 0; i < numtoread; i++) + INPUT_BYTE(cinfo, b[i], return FALSE); + length -= numtoread; + + /* process it */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + case M_APP14: + examine_app14(cinfo, (JOCTET FAR *) b, numtoread, length); + break; + default: + /* can't get here unless jpeg_save_markers chooses wrong processor */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +#ifdef SAVE_MARKERS_SUPPORTED + +METHODDEF(boolean) +save_marker (j_decompress_ptr cinfo) +/* Save an APPn or COM marker into the marker list */ +{ + my_marker_ptr2 marker = (my_marker_ptr2) cinfo->marker; + jpeg_saved_marker_ptr cur_marker = marker->cur_marker; + unsigned int bytes_read, data_length; + JOCTET FAR * data; + INT32 length = 0; + INPUT_VARS(cinfo); + + if (cur_marker == NULL) { + /* begin reading a marker */ + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + if (length >= 0) { /* watch out for bogus length word */ + /* figure out how much we want to save */ + unsigned int limit; + if (cinfo->unread_marker == (int) M_COM) + limit = marker->length_limit_COM; + else + limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0]; + if ((unsigned int) length < limit) + limit = (unsigned int) length; + /* allocate and initialize the marker item */ + cur_marker = (jpeg_saved_marker_ptr) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_struct) + limit); + cur_marker->next = NULL; + cur_marker->marker = (UINT8) cinfo->unread_marker; + cur_marker->original_length = (unsigned int) length; + cur_marker->data_length = limit; + /* data area is just beyond the jpeg_marker_struct */ + data = cur_marker->data = (JOCTET FAR *) (cur_marker + 1); + marker->cur_marker = cur_marker; + marker->bytes_read = 0; + bytes_read = 0; + data_length = limit; + } else { + /* deal with bogus length word */ + bytes_read = data_length = 0; + data = NULL; + } + } else { + /* resume reading a marker */ + bytes_read = marker->bytes_read; + data_length = cur_marker->data_length; + data = cur_marker->data + bytes_read; + } + + while (bytes_read < data_length) { + INPUT_SYNC(cinfo); /* move the restart point to here */ + marker->bytes_read = bytes_read; + /* If there's not at least one byte in buffer, suspend */ + MAKE_BYTE_AVAIL(cinfo, return FALSE); + /* Copy bytes with reasonable rapidity */ + while (bytes_read < data_length && bytes_in_buffer > 0) { + *data++ = *next_input_byte++; + bytes_in_buffer--; + bytes_read++; + } + } + + /* Done reading what we want to read */ + if (cur_marker != NULL) { /* will be NULL if bogus length word */ + /* Add new marker to end of list */ + if (cinfo->marker_list == NULL) { + cinfo->marker_list = cur_marker; + } else { + jpeg_saved_marker_ptr prev = cinfo->marker_list; + while (prev->next != NULL) + prev = prev->next; + prev->next = cur_marker; + } + /* Reset pointer & calc remaining data length */ + data = cur_marker->data; + length = cur_marker->original_length - data_length; + } + /* Reset to initial state for next marker */ + marker->cur_marker = NULL; + + /* Process the marker if interesting; else just make a generic trace msg */ + switch (cinfo->unread_marker) { + case M_APP0: + examine_app0(cinfo, data, data_length, length); + break; + case M_APP14: + examine_app14(cinfo, data, data_length, length); + break; + default: + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, + (int) (data_length + length)); + break; + } + + /* skip any remaining data -- could be lots */ + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +METHODDEF(boolean) +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + if (length > 0) + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL(boolean) +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL(boolean) +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF(int) +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*((my_marker_ptr2) cinfo->marker)->process_APPn[ + cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*((my_marker_ptr2) cinfo->marker)->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF(boolean) +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 3, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL(boolean) +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF(void) +reset_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr2 marker = (my_marker_ptr2) cinfo->marker; + + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + marker->pub.saw_SOI = FALSE; /* set internal state too */ + marker->pub.saw_SOF = FALSE; + marker->pub.discarded_bytes = 0; + marker->cur_marker = NULL; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL(void) +jinit_marker_reader (j_decompress_ptr cinfo) +{ + my_marker_ptr2 marker; + int i; + + /* Create subobject in permanent pool */ + marker = (my_marker_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_marker_reader)); + cinfo->marker = (struct jpeg_marker_reader *) marker; + /* Initialize public method pointers */ + marker->pub.reset_marker_reader = reset_marker_reader; + marker->pub.read_markers = read_markers; + marker->pub.read_restart_marker = read_restart_marker; + /* Initialize COM/APPn processing. + * By default, we examine and then discard APP0 and APP14, + * but simply discard COM and all other APPn. + */ + marker->process_COM = skip_variable; + marker->length_limit_COM = 0; + for (i = 0; i < 16; i++) { + marker->process_APPn[i] = skip_variable; + marker->length_limit_APPn[i] = 0; + } + marker->process_APPn[0] = get_interesting_appn; + marker->process_APPn[14] = get_interesting_appn; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} + + +/* + * Control saving of COM and APPn markers into marker_list. + */ + +#ifdef SAVE_MARKERS_SUPPORTED + +GLOBAL(void) +jpeg_save_markers (j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit) +{ + my_marker_ptr2 marker = (my_marker_ptr2) cinfo->marker; + long maxlength; + jpeg_marker_parser_method processor; + + /* Length limit mustn't be larger than what we can allocate + * (should only be a concern in a 16-bit environment). + */ + maxlength = cinfo->mem->max_alloc_chunk - SIZEOF(struct jpeg_marker_struct); + if (((long) length_limit) > maxlength) + length_limit = (unsigned int) maxlength; + + /* Choose processor routine to use. + * APP0/APP14 have special requirements. + */ + if (length_limit) { + processor = save_marker; + /* If saving APP0/APP14, save at least enough for our internal use. */ + if (marker_code == (int) M_APP0 && length_limit < APP0_DATA_LEN) + length_limit = APP0_DATA_LEN; + else if (marker_code == (int) M_APP14 && length_limit < APP14_DATA_LEN) + length_limit = APP14_DATA_LEN; + } else { + processor = skip_variable; + /* If discarding APP0/APP14, use our regular on-the-fly processor. */ + if (marker_code == (int) M_APP0 || marker_code == (int) M_APP14) + processor = get_interesting_appn; + } + + if (marker_code == (int) M_COM) { + marker->process_COM = processor; + marker->length_limit_COM = length_limit; + } else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) { + marker->process_APPn[marker_code - (int) M_APP0] = processor; + marker->length_limit_APPn[marker_code - (int) M_APP0] = length_limit; + } else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + +#endif /* SAVE_MARKERS_SUPPORTED */ + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL(void) +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + my_marker_ptr2 marker = (my_marker_ptr2) cinfo->marker; + + if (marker_code == (int) M_COM) + marker->process_COM = routine; + else if (marker_code >= (int) M_APP0 && marker_code <= (int) M_APP15) + marker->process_APPn[marker_code - (int) M_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmaster.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmaster.c new file mode 100644 index 0000000..8c7ec43 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmaster.c @@ -0,0 +1,557 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr6; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL(boolean) +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL(void) +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#ifdef IDCT_SCALING_SUPPORTED + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL(void) +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL(void) +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr6 master = (my_master_ptr6) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapistd.c will crank the pass to completion.) + */ + +METHODDEF(void) +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr6 master = (my_master_ptr6) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF(void) +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr6 master = (my_master_ptr6) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL(void) +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr6 master = (my_master_ptr6) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL(void) +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr6 master; + + master = (my_master_ptr6) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmerge.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmerge.c new file mode 100644 index 0000000..faee6ce --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdmerge.c @@ -0,0 +1,400 @@ +/* + * jdmerge.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains code for merged upsampling/color conversion. + * + * This file combines functions from jdsample.c and jdcolor.c; + * read those files first to understand what's going on. + * + * When the chroma components are to be upsampled by simple replication + * (ie, box filtering), we can save some work in color conversion by + * calculating all the output pixels corresponding to a pair of chroma + * samples at one time. In the conversion equations + * R = Y + K1 * Cr + * G = Y + K2 * Cb + K3 * Cr + * B = Y + K4 * Cb + * only the Y term varies among the group of pixels corresponding to a pair + * of chroma samples, so the rest of the terms can be calculated just once. + * At typical sampling ratios, this eliminates half or three-quarters of the + * multiplications needed for color conversion. + * + * This file currently provides implementations for the following cases: + * YCbCr => RGB color conversion only. + * Sampling ratios of 2h1v or 2h2v. + * No scaling needed at upsample time. + * Corner-aligned (non-CCIR601) sampling alignment. + * Other special cases could be added, but in most applications these are + * the only common cases. (For uncommon cases we fall back on the more + * general code in jdsample.c and jdcolor.c.) + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef UPSAMPLE_MERGING_SUPPORTED + + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Pointer to routine to do actual upsampling/conversion of one row group */ + JMETHOD(void, upmethod, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf)); + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + + /* For 2:1 vertical sampling, we produce two output rows at a time. + * We need a "spare" row buffer to hold the second output row if the + * application provides just a one-row buffer; we also use the spare + * to discard the dummy last row if the image height is odd. + */ + JSAMPROW spare_row; + boolean spare_full; /* T if spare buffer is occupied */ + + JDIMENSION out_row_width; /* samples per output row */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + * This is taken directly from jdcolor.c; see that file for more info. + */ + +LOCAL(void) +build_ycc_rgb_table2 (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int i; + INT32 x; + SHIFT_TEMPS + + upsample->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + upsample->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + upsample->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + upsample->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + upsample->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + upsample->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_merged_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the spare buffer empty */ + upsample->spare_full = FALSE; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * The control routine just handles the row buffering considerations. + */ + +METHODDEF(void) +merged_2v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 2:1 vertical sampling case: may need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPROW work_ptrs[2]; + JDIMENSION num_rows; /* number of rows returned to caller */ + + if (upsample->spare_full) { + /* If we have a spare row saved from a previous cycle, just return it. */ + jcopy_sample_rows(& upsample->spare_row, 0, output_buf + *out_row_ctr, 0, + 1, upsample->out_row_width); + num_rows = 1; + upsample->spare_full = FALSE; + } else { + /* Figure number of rows to return to caller. */ + num_rows = 2; + /* Not more than the distance to the end of the image. */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + /* Create output pointer array for upsampler. */ + work_ptrs[0] = output_buf[*out_row_ctr]; + if (num_rows > 1) { + work_ptrs[1] = output_buf[*out_row_ctr + 1]; + } else { + work_ptrs[1] = upsample->spare_row; + upsample->spare_full = TRUE; + } + /* Now do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, work_ptrs); + } + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (! upsample->spare_full) + (*in_row_group_ctr)++; +} + + +METHODDEF(void) +merged_1v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION) +/* 1:1 vertical sampling case: much easier, never need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Just do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, + output_buf + *out_row_ctr); + /* Adjust counts */ + (*out_row_ctr)++; + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by the control routines to do + * the actual upsampling/conversion. One row group is processed per call. + * + * Note: since we may be writing directly into application-supplied buffers, + * we have to be honest about the output width; we can't assume the buffer + * has been rounded up to an even width. + */ + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. + */ + +METHODDEF(void) +h2v1_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr0 = input_buf[0][in_row_group_ctr]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr = output_buf[0]; + /* Loop for each pair of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 2 Y values and emit 2 pixels */ + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr0); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. + */ + +METHODDEF(void) +h2v2_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr0, outptr1; + JSAMPROW inptr00, inptr01, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr00 = input_buf[0][in_row_group_ctr*2]; + inptr01 = input_buf[0][in_row_group_ctr*2 + 1]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr0 = output_buf[0]; + outptr1 = output_buf[1]; + /* Loop for each group of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 4 Y values and emit 4 pixels */ + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr00); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + y = GETJSAMPLE(*inptr01); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Module initialization routine for merged upsampling/color conversion. + * + * NB: this is called under the conditions determined by use_merged_upsample() + * in jdmaster.c. That routine MUST correspond to the actual capabilities + * of this module; no safety checks are made here. + */ + +GLOBAL(void) +jinit_merged_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_merged_upsample; + upsample->pub.need_context_rows = FALSE; + + upsample->out_row_width = cinfo->output_width * cinfo->out_color_components; + + if (cinfo->max_v_samp_factor == 2) { + upsample->pub.upsample = merged_2v_upsample; + upsample->upmethod = h2v2_merged_upsample; + /* Allocate a spare row buffer */ + upsample->spare_row = (JSAMPROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) (upsample->out_row_width * SIZEOF(JSAMPLE))); + } else { + upsample->pub.upsample = merged_1v_upsample; + upsample->upmethod = h2v1_merged_upsample; + /* No spare row needed */ + upsample->spare_row = NULL; + } + + build_ycc_rgb_table2(cinfo); +} + +#endif /* UPSAMPLE_MERGING_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdphuff.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdphuff.c new file mode 100644 index 0000000..c700968 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdphuff.c @@ -0,0 +1,642 @@ +/* + * jdphuff.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines for progressive JPEG. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdhuff.c */ + + +#ifdef D_PROGRESSIVE_SUPPORTED + +/* + * Expanded entropy decoder object for progressive Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state3; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).EOBRUN = (src).EOBRUN, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state3 saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + d_derived_tbl * ac_derived_tbl; /* active table during an AC scan */ +} phuff_entropy_decoder; + +typedef phuff_entropy_decoder * phuff_entropy_ptr2; + +/* Forward declarations */ +METHODDEF(boolean) decode_mcu_DC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_AC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_DC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF(boolean) decode_mcu_AC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF(void) +start_pass_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + boolean is_DC_band, bad; + int ci, coefi, tbl; + int *coef_bit_ptr; + jpeg_component_info * compptr; + + is_DC_band = (cinfo->Ss == 0); + + /* Validate scan parameters */ + bad = FALSE; + if (is_DC_band) { + if (cinfo->Se != 0) + bad = TRUE; + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if (cinfo->Ss > cinfo->Se || cinfo->Se >= DCTSIZE2) + bad = TRUE; + /* AC scans may have only one component */ + if (cinfo->comps_in_scan != 1) + bad = TRUE; + } + if (cinfo->Ah != 0) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if (cinfo->Al != cinfo->Ah-1) + bad = TRUE; + } + if (cinfo->Al > 13) /* need not check for < 0 */ + bad = TRUE; + /* Arguably the maximum Al value should be less than 13 for 8-bit precision, + * but the spec doesn't say so, and we try to be liberal about what we + * accept. Note: large Al values could result in out-of-range DC + * coefficients during early scans, leading to bizarre displays due to + * overflows in the IDCT math. But we won't crash. + */ + if (bad) + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + int cindex = cinfo->cur_comp_info[ci]->component_index; + coef_bit_ptr = & cinfo->coef_bits[cindex][0]; + if (!is_DC_band && coef_bit_ptr[0] < 0) /* AC without prior DC scan */ + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0); + for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { + int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; + if (cinfo->Ah != expected) + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + coef_bit_ptr[coefi] = cinfo->Al; + } + } + + /* Select MCU decoding routine */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_first; + else + entropy->pub.decode_mcu = decode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_refine; + else + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Make sure requested tables are present, and compute derived tables. + * We may build same derived table more than once, but it's not expensive. + */ + if (is_DC_band) { + if (cinfo->Ah == 0) { /* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, + & entropy->derived_tbls[tbl]); + } + } else { + tbl = compptr->ac_tbl_no; + jpeg_make_d_derived_tbl(cinfo, FALSE, tbl, + & entropy->derived_tbls[tbl]); + /* remember the single active table */ + entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->pub.insufficient_data = FALSE; + + /* Initialize private state variables */ + entropy->saved.EOBRUN = 0; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL(boolean) +process_restartp (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Re-init EOB run count, too */ + entropy->saved.EOBRUN = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Reset out-of-data flag, unless read_restart_marker left us smack up + * against a marker. In that case we will end up treating the next data + * segment as empty, and we can avoid producing bogus output pixels by + * leaving the flag set. + */ + if (cinfo->unread_marker == 0) + entropy->pub.insufficient_data = FALSE; + + return TRUE; +} + + +/* + * Huffman MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + * + * We return FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * spectral selection, since we'll just re-assign them on the next call. + * Successive approximation AC refinement has to be more careful, however.) + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + int Al = cinfo->Al; + register int s, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state3 state; + d_derived_tbl * tbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restartp(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + tbl = entropy->derived_tbls[compptr->dc_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, tbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */ + (*block)[0] = (JCOEF) (s << Al); + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF(boolean) +decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + int Se = cinfo->Se; + int Al = cinfo->Al; + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restartp(cinfo)) + return FALSE; + } + + /* If we've run out of data, just leave the MCU set to zeroes. + * This way, we return uniform gray for the remainder of the segment. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state. + * We can avoid loading/saving bitread state if in an EOB run. + */ + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + + if (EOBRUN > 0) /* if it's a band of zeroes... */ + EOBRUN--; /* ...process it now (we do nothing) */ + else { + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + for (k = cinfo->Ss; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, return FALSE, label2); + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al); + } else { + if (r == 15) { /* ZRL */ + k += 15; /* skip 15 zeroes in band */ + } else { /* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if (r) { /* EOBr, r > 0 */ + CHECK_BIT_BUFFER(br_state, r, return FALSE); + r = GET_BITS(r); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } + } + + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + } + + /* Completed MCU, so update state */ + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF(boolean) +decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int blkn; + JBLOCKROW block; + BITREAD_STATE_VARS; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restartp(cinfo)) + return FALSE; + } + + /* Not worth the cycles to check insufficient_data here, + * since we will not change the data anyway if we read zeroes. + */ + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* Encoded data is simply the next bit of the two's-complement DC value */ + CHECK_BIT_BUFFER(br_state, 1, return FALSE); + if (GET_BITS(1)) + (*block)[0] |= p1; + /* Note: since we use |=, repeating the assignment later is safe */ + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF(boolean) +decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr2 entropy = (phuff_entropy_ptr2) cinfo->entropy; + int Se = cinfo->Se; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + JCOEFPTR thiscoef; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + int num_newnz; + int newnz_pos[DCTSIZE2]; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restartp(cinfo)) + return FALSE; + } + + /* If we've run out of data, don't modify the MCU. + */ + if (! entropy->pub.insufficient_data) { + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + /* If we are forced to suspend, we must undo the assignments to any newly + * nonzero coefficients in the block, because otherwise we'd get confused + * next time about which coefficients were already nonzero. + * But we need not undo addition of bits to already-nonzero coefficients; + * instead, we can test the current bit to see if we already did it. + */ + num_newnz = 0; + + /* initialize coefficient loop counter to start of band */ + k = cinfo->Ss; + + if (EOBRUN == 0) { + for (; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, goto undoit, label3); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) /* size of new coef should always be 1 */ + WARNMS(cinfo, JWRN_HUFF_BAD_CODE); + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) + s = p1; /* newly nonzero coef is positive */ + else + s = m1; /* newly nonzero coef is negative */ + } else { + if (r != 15) { + EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ + if (r) { + CHECK_BIT_BUFFER(br_state, r, goto undoit); + r = GET_BITS(r); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already set it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } else { + if (--r < 0) + break; /* reached target zero coefficient */ + } + k++; + } while (k <= Se); + if (s) { + int pos = jpeg_natural_order[k]; + /* Output newly nonzero coefficient */ + (*block)[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } + } + } + + if (EOBRUN > 0) { + /* Scan any remaining coefficient positions after the end-of-band + * (the last newly nonzero coefficient, if any). Append a correction + * bit to each already-nonzero coefficient. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + for (; k <= Se; k++) { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } + } + /* Count one block completed in EOB run */ + EOBRUN--; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + } + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; + +undoit: + /* Re-zero any output coefficients that we made newly nonzero */ + while (num_newnz > 0) + (*block)[newnz_pos[--num_newnz]] = 0; + + return FALSE; +} + + +/* + * Module initialization routine for progressive Huffman entropy decoding. + */ + +GLOBAL(void) +jinit_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr2 entropy; + int *coef_bit_ptr; + int ci, i; + + entropy = (phuff_entropy_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_phuff_decoder; + + /* Mark derived tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + } + + /* Create progression status table */ + cinfo->coef_bits = (int (*)[DCTSIZE2]) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components*DCTSIZE2*SIZEOF(int)); + coef_bit_ptr = & cinfo->coef_bits[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (i = 0; i < DCTSIZE2; i++) + *coef_bit_ptr++ = -1; +} + +#endif /* D_PROGRESSIVE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdpostct.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdpostct.c new file mode 100644 index 0000000..2d93839 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdpostct.c @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF(void) post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF(void) post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF(void) post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF(void) +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF(void) +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY, JDIMENSION *out_row_ctr, + JDIMENSION) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF(void) +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE, JDIMENSION *, + JDIMENSION, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL(void) +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdsample.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdsample.c new file mode 100644 index 0000000..c0a142a --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdsample.c @@ -0,0 +1,478 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler2; + +typedef my_upsampler2 * my_upsample_ptr2; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF(void) +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr2 upsample = (my_upsample_ptr2) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF(void) +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr2 upsample = (my_upsample_ptr2) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF(void) +fullsize_upsample (j_decompress_ptr, jpeg_component_info *, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF(void) +noop_upsample (j_decompress_ptr, jpeg_component_info *, + JSAMPARRAY, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF(void) +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr2 upsample = (my_upsample_ptr2) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info *, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF(void) +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info *, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF(void) +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE(*inptr++); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE(*inptr++) * 3; + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + } + + /* Special case for last column */ + invalue = GETJSAMPLE(*inptr); + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF(void) +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + for (v = 0; v < 2; v++) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL(void) +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr2 upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler2)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special cases for 2h1v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) + upsample->methods[ci] = h2v1_fancy_upsample; + else + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special cases for 2h2v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdtrans.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdtrans.c new file mode 100644 index 0000000..12c193c --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jdtrans.c @@ -0,0 +1,143 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL(void) transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * An alternative usage is to simply obtain access to the coefficient arrays + * during a buffered-image-mode decompression operation. This is allowed + * after any jpeg_finish_output() call. The arrays can be accessed until + * jpeg_finish_decompress() is called. (Note that any call to the library + * may reposition the arrays, so don't rely on access_virt_barray() results + * to stay valid across library calls.) + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL(jvirt_barray_ptr *) +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } + if (cinfo->global_state == DSTATE_RDCOEFS) { + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + } + /* At this point we should be in state DSTATE_STOPPING if being used + * standalone, or in state DSTATE_BUFIMAGE if being invoked to get access + * to the coefficients during a full buffered-image-mode decompression. + */ + if ((cinfo->global_state == DSTATE_STOPPING || + cinfo->global_state == DSTATE_BUFIMAGE) && cinfo->buffered_image) { + return cinfo->coef->coef_arrays; + } + /* Oops, improper usage */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return NULL; /* keep compiler happy */ +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL(void) +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* This is effectively a buffered-image operation. */ + cinfo->buffered_image = TRUE; + + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.c new file mode 100644 index 0000000..c98aed7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.c @@ -0,0 +1,252 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * If you define USE_WINDOWS_MESSAGEBOX in jconfig.h or in the makefile, + * you get a Windows-specific hack to display error messages in a dialog box. + * It ain't much, but it beats dropping error messages into the bit bucket, + * which is what happens to output to stderr under most Windows C compilers. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#ifdef USE_WINDOWS_MESSAGEBOX +#include +#endif + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF(void) +error_exit (j_common_ptr cinfo) +{ + /* Always display the message */ + (*cinfo->err->output_message) (cinfo); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + exit(EXIT_FAILURE); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + * + * On Windows, printing to stderr is generally completely useless, + * so we provide optional code to produce an error-dialog popup. + * Most Windows applications will still prefer to override this routine, + * but if they don't, it'll do something at least marginally useful. + * + * NOTE: to use the library in an environment that doesn't support the + * C stdio library, you may have to delete the call to fprintf() entirely, + * not just not use this routine. + */ + +METHODDEF(void) +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + +#ifdef USE_WINDOWS_MESSAGEBOX + /* Display it in a message dialog box */ + MessageBox(GetActiveWindow(), buffer, "JPEG Library Error", + MB_OK | MB_ICONERROR); +#else + /* Send it to stderr, adding a newline */ + fprintf(stderr, "%s\n", buffer); +#endif +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF(void) +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF(void) +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF(void) +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL(struct jpeg_error_mgr *) +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.h new file mode 100644 index 0000000..79084f2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jerror.h @@ -0,0 +1,291 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCT_COEF, "DCT coefficient out of range") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_HUFF_TABLE, "Bogus Huffman table definition") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_LIB_VERSION, + "Wrong JPEG library version: library is %d, caller expects %d") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_STRUCT_SIZE, + "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_EXTENSION, + "JFIF extension marker: type 0x%02x, length %u") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_THUMB_JPEG, + "JFIF extension marker: JPEG-compressed thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_PALETTE, + "JFIF extension marker: palette thumbnail image, length %u") +JMESSAGE(JTRC_THUMB_RGB, + "JFIF extension marker: RGB thumbnail image, length %u") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS5(cinfo,lvl,code,p1,p2,p3,p4,p5) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctflt.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctflt.c new file mode 100644 index 0000000..102f7d5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctflt.c @@ -0,0 +1,168 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_float (FAST_FLOAT * data) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctfst.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctfst.c new file mode 100644 index 0000000..f26221d --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctfst.c @@ -0,0 +1,224 @@ +/* + * jfdctfst.c + * + * Copyright (C) 1994-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jfdctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * Again to save a few shifts, the intermediate results between pass 1 and + * pass 2 are not upscaled, but are represented only to integral precision. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#define CONST_BITS 8 + + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_0_382683433 ((INT32) 98) /* FIX(0.382683433) */ +#define FIX_0_541196100 ((INT32) 139) /* FIX(0.541196100) */ +#define FIX_0_707106781 ((INT32) 181) /* FIX(0.707106781) */ +#define FIX_1_306562965 ((INT32) 334) /* FIX(1.306562965) */ +#else +#define FIX_0_382683433 FIX(0.382683433) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_707106781 FIX(0.707106781) +#define FIX_1_306562965 FIX(1.306562965) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_ifast (DCTELEM * data) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctint.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctint.c new file mode 100644 index 0000000..a905849 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jfdctint.c @@ -0,0 +1,283 @@ +/* + * jfdctint.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D DCT step produces outputs which are a factor of sqrt(N) + * larger than the true DCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D DCT, + * because the y0 and y4 outputs need not be divided by sqrt(N). + * In the IJG code, this factor of 8 is removed by the quantization step + * (in jcdctmgr.c), NOT in this module. + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (For 12-bit sample data, the intermediate + * array is INT32 anyway.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL(void) +jpeg_fdct_islow (DCTELEM * data) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = (DCTELEM) ((tmp10 + tmp11) << PASS1_BITS); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS-PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, CONST_BITS-PASS1_BITS); + dataptr[1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp11, PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp10 - tmp11, PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS+PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctflt.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctflt.c new file mode 100644 index 0000000..4a89578 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctflt.c @@ -0,0 +1,242 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*4] = tmp3 + tmp4; + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctfst.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctfst.c new file mode 100644 index 0000000..41790ea --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctfst.c @@ -0,0 +1,368 @@ +/* + * jidctfst.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jidctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * The dequantized coefficients are not integers because the AA&N scaling + * factors have been incorporated. We represent them scaled up by PASS1_BITS, + * so that the first and second IDCT rounds have the same input scaling. + * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to + * avoid a descaling shift; this compromises accuracy rather drastically + * for small quantization table entries, but it saves a lot of shifts. + * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, + * so we use a much larger scaling factor to preserve accuracy. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 8 +#define PASS1_BITS 2 +#else +#define CONST_BITS 8 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ +#else +#define FIX_1_082392200 FIX(1.082392200) +#define FIX_1_414213562 FIX(1.414213562) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_613125930 FIX(2.613125930) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a DCTELEM result. For 8-bit data a 16x16->16 + * multiplication will do. For 12-bit data, the multiplier table is + * declared INT32, so a 32-bit multiply will be used. + */ + +#if BITS_IN_JSAMPLE == 8 +#define DEQUANTIZE(coef,quantval) (((IFAST_MULT_TYPE) (coef)) * (quantval)) +#else +#define DEQUANTIZE(coef,quantval) \ + DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) +#endif + + +/* Like DESCALE, but applies to a DCTELEM and produces an int. + * We assume that int right shift is unsigned if INT32 right shift is. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS DCTELEM ishift_temp; +#if BITS_IN_JSAMPLE == 8 +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#else +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#endif +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~((DCTELEM) 0)) << (DCTELEMBITS-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +#ifdef USE_ACCURATE_ROUNDING +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT((x) + (1 << ((n)-1)), n)) +#else +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT(x, n)) +#endif + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (IFAST_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = (int) DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = (int) (tmp0 + tmp7); + wsptr[DCTSIZE*7] = (int) (tmp0 - tmp7); + wsptr[DCTSIZE*1] = (int) (tmp1 + tmp6); + wsptr[DCTSIZE*6] = (int) (tmp1 - tmp6); + wsptr[DCTSIZE*2] = (int) (tmp2 + tmp5); + wsptr[DCTSIZE*5] = (int) (tmp2 - tmp5); + wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4); + wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]); + tmp11 = ((DCTELEM) wsptr[0] - (DCTELEM) wsptr[4]); + + tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]); + tmp12 = MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562) + - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3]; + z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3]; + z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; + z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctint.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctint.c new file mode 100644 index 0000000..63ad6d3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctint.c @@ -0,0 +1,389 @@ +/* + * jidctint.c + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D IDCT step produces outputs which are a factor of sqrt(N) + * larger than the true IDCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D IDCT, + * because the y0 and y4 inputs need not be divided by sqrt(N). + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (To scale up 12-bit sample data further, an + * intermediate INT32 array would be needed.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL(void) +jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*7] = (int) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*6] = (int) DESCALE(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*5] = (int) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*4] = (int) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + tmp0 = ((INT32) wsptr[0] + (INT32) wsptr[4]) << CONST_BITS; + tmp1 = ((INT32) wsptr[0] - (INT32) wsptr[4]) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctred.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctred.c new file mode 100644 index 0000000..6a923f4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jidctred.c @@ -0,0 +1,398 @@ +/* + * jidctred.c + * + * Copyright (C) 1994-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains inverse-DCT routines that produce reduced-size output: + * either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block. + * + * The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M) + * algorithm used in jidctint.c. We simply replace each 8-to-8 1-D IDCT step + * with an 8-to-4 step that produces the four averages of two adjacent outputs + * (or an 8-to-2 step producing two averages of four outputs, for 2x2 output). + * These steps were derived by computing the corresponding values at the end + * of the normal LL&M code, then simplifying as much as possible. + * + * 1x1 is trivial: just take the DC coefficient divided by 8. + * + * See jidctint.c for additional comments. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef IDCT_SCALING_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling is the same as in jidctint.c. */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_211164243 ((INT32) 1730) /* FIX(0.211164243) */ +#define FIX_0_509795579 ((INT32) 4176) /* FIX(0.509795579) */ +#define FIX_0_601344887 ((INT32) 4926) /* FIX(0.601344887) */ +#define FIX_0_720959822 ((INT32) 5906) /* FIX(0.720959822) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_850430095 ((INT32) 6967) /* FIX(0.850430095) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_061594337 ((INT32) 8697) /* FIX(1.061594337) */ +#define FIX_1_272758580 ((INT32) 10426) /* FIX(1.272758580) */ +#define FIX_1_451774981 ((INT32) 11893) /* FIX(1.451774981) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_2_172734803 ((INT32) 17799) /* FIX(2.172734803) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_624509785 ((INT32) 29692) /* FIX(3.624509785) */ +#else +#define FIX_0_211164243 FIX(0.211164243) +#define FIX_0_509795579 FIX(0.509795579) +#define FIX_0_601344887 FIX(0.601344887) +#define FIX_0_720959822 FIX(0.720959822) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_850430095 FIX(0.850430095) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_061594337 FIX(1.061594337) +#define FIX_1_272758580 FIX(1.272758580) +#define FIX_1_451774981 FIX(1.451774981) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_172734803 FIX(2.172734803) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_624509785 FIX(3.624509785) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 4x4 output block. + */ + +GLOBAL(void) +jpeg_idct_4x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process column 4, because second pass won't use it */ + if (ctr == DCTSIZE-4) + continue; + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*5] == 0 && + inptr[DCTSIZE*6] == 0 && inptr[DCTSIZE*7] == 0) { + /* AC terms all zero; we need not examine term 4 for 4x4 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= (CONST_BITS+1); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp2 = MULTIPLY(z2, FIX_1_847759065) + MULTIPLY(z3, - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + z2 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp10 - tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp12 + tmp0, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 - tmp0, CONST_BITS-PASS1_BITS+1); + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp0 = ((INT32) wsptr[0]) << (CONST_BITS+1); + + tmp2 = MULTIPLY((INT32) wsptr[2], FIX_1_847759065) + + MULTIPLY((INT32) wsptr[6], - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = (INT32) wsptr[7]; + z2 = (INT32) wsptr[5]; + z3 = (INT32) wsptr[3]; + z4 = (INT32) wsptr[1]; + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp10 - tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp12 + tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 - tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 2x2 output block. + */ + +GLOBAL(void) +jpeg_idct_2x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp10, z1; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process columns 2,4,6 */ + if (ctr == DCTSIZE-2 || ctr == DCTSIZE-4 || ctr == DCTSIZE-6) + continue; + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*3] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*7] == 0) { + /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + + continue; + } + + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp10 = z1 << (CONST_BITS+2); + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp0 = MULTIPLY(z1, - FIX_0_720959822); /* sqrt(2) * (c7-c5+c3-c1) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp0 += MULTIPLY(z1, FIX_0_850430095); /* sqrt(2) * (-c1+c3+c5+c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp0 += MULTIPLY(z1, - FIX_1_272758580); /* sqrt(2) * (-c1+c3-c5-c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp0 += MULTIPLY(z1, FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp0, CONST_BITS-PASS1_BITS+2); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp10 - tmp0, CONST_BITS-PASS1_BITS+2); + } + + /* Pass 2: process 2 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 2; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[3] == 0 && wsptr[5] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((INT32) wsptr[0]) << (CONST_BITS+2); + + /* Odd part */ + + tmp0 = MULTIPLY((INT32) wsptr[7], - FIX_0_720959822) /* sqrt(2) * (c7-c5+c3-c1) */ + + MULTIPLY((INT32) wsptr[5], FIX_0_850430095) /* sqrt(2) * (-c1+c3+c5+c7) */ + + MULTIPLY((INT32) wsptr[3], - FIX_1_272758580) /* sqrt(2) * (-c1+c3-c5-c7) */ + + MULTIPLY((INT32) wsptr[1], FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 1x1 output block. + */ + +GLOBAL(void) +jpeg_idct_1x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + int dcval; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* We hardly need an inverse DCT routine for this: just take the + * average pixel value, which is one-eighth of the DC coefficient. + */ + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + dcval = DEQUANTIZE(coef_block[0], quantptr[0]); + dcval = (int) DESCALE((INT32) dcval, 3); + + output_buf[0][output_col] = range_limit[dcval & RANGE_MASK]; +} + +#endif /* IDCT_SCALING_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jinclude.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jinclude.h new file mode 100644 index 0000000..ab66008 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jinclude.h @@ -0,0 +1,192 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#ifndef __jinclude_h__ +#define __jinclude_h__ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) + + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemmgr.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemmgr.c new file mode 100644 index 0000000..c11609b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemmgr.c @@ -0,0 +1,1118 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL(void) +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL(void) +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF(void *) +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF(void FAR *) +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF(JSAMPARRAY) +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF(JBLOCKARRAY) +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF(jvirt_sarray_ptr) +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF(jvirt_barray_ptr) +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF(void) +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL(void) +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL(void) +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF(JSAMPARRAY) +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF(JBLOCKARRAY) +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF(void) +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF(void) +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL(void) +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Make MAX_ALLOC_CHUNK accessible to other modules */ + mem->pub.max_alloc_chunk = MAX_ALLOC_CHUNK; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemnobs.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemnobs.c new file mode 100644 index 0000000..e74edbc --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemnobs.c @@ -0,0 +1,109 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +extern void * malloc JPP((size_t size)); +extern void free JPP((void *ptr)); +#endif + + +/* + * Memory allocation and freeing are controlled by the regular library + * routines malloc() and free(). + */ + +GLOBAL(void *) +jpeg_get_small (j_common_ptr , size_t sizeofobject) +{ + return (void *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_small (j_common_ptr , void * object, size_t) +{ + free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL(void FAR *) +jpeg_get_large (j_common_ptr, size_t sizeofobject) +{ + return (void FAR *) malloc(sizeofobject); +} + +GLOBAL(void) +jpeg_free_large (j_common_ptr, void FAR * object, size_t) +{ + free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL(long) +jpeg_mem_available (j_common_ptr, long, + long max_bytes_needed, long) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL(void) +jpeg_open_backing_store (j_common_ptr cinfo, struct backing_store_struct *, + long ) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL(long) +jpeg_mem_init (j_common_ptr) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL(void) +jpeg_mem_term (j_common_ptr) +{ + /* no work */ +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemsys.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemsys.h new file mode 100644 index 0000000..d834ea4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmemsys.h @@ -0,0 +1,203 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR + * and USE_MAC_MEMMGR. + */ + +#ifndef __jmemsys_h__ +#define __jmemsys_h__ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN(void *) jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN(void FAR *) jpeg_get_large JPP((j_common_ptr cinfo, + size_t sizeofobject)); +EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +#ifdef USE_MAC_MEMMGR /* Mac-specific junk */ +#include +#endif /* USE_MAC_MEMMGR */ + + +//typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + struct backing_store_struct *info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + struct backing_store_struct *info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + struct backing_store_struct *info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else +#ifdef USE_MAC_MEMMGR + /* For the Mac manager (jmemmac.c), we need: */ + short temp_file; /* file reference number to temp file */ + FSSpec tempSpec; /* the FSSpec for the temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +#endif +} backing_store_info; + + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN(void) jpeg_open_backing_store JPP((j_common_ptr cinfo, + struct backing_store_struct *info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN(long) jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_mem_term JPP((j_common_ptr cinfo)); + + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmorecfg.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmorecfg.h new file mode 100644 index 0000000..c856e22 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jmorecfg.h @@ -0,0 +1,363 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +typedef long INT32; +#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These macros are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions; + * in particular, you'll need to do that to make the library a Windows DLL. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +/* a function called through method pointers: */ +#define METHODDEF(type) static type +/* a function used only in its module: */ +#define LOCAL(type) static type +/* a function referenced thru EXTERNs: */ +#define GLOBAL(type) type +/* a reference to a GLOBAL function: */ +#define EXTERN(type) extern type + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + * Again, you can customize this if you need special linkage keywords. + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#define FAR far +#else +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +#ifndef HAVE_BOOLEAN +typedef int boolean; +#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpegint.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpegint.h new file mode 100644 index 0000000..685a361 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpegint.h @@ -0,0 +1,392 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + /* These routines are exported to allow insertion of extra markers */ + /* Probably only COM and APPn markers should be written this way */ + JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, + unsigned int datalen)); + JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + /* This is here to share code between baseline and progressive decoders; */ + /* other modules probably should not use it */ + boolean insufficient_data; /* set TRUE after emitting warning */ +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN(long) jdiv_round_up JPP((long a, long b)); +EXTERN(long) jround_up JPP((long a, long b)); +EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +#if 0 /* This table is not actually needed in v6a */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +#endif +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpeglib.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpeglib.h new file mode 100644 index 0000000..bf6ce08 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jpeglib.h @@ -0,0 +1,1096 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jconfig.h" /* widely used configuration options */ +#endif +#include "jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 62 /* Version 6b */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This array gives the coefficient quantizers in natural array order + * (not the zigzag order in which they are stored in a JPEG DQT marker). + * CAUTION: IJG versions prior to v6a kept this array in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + +/* The decompressor can save APPn and COM markers in a list of these: */ + +typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr; + +struct jpeg_marker_struct { + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET FAR * data; /* the data contained in the marker */ + /* the marker length word is not counted in data_length or original_length */ +}; + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + void * client_data; /* Available for use by application */\ + boolean is_decompressor; /* So common code can tell which is which */\ + int global_state /* For checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + UINT8 JFIF_minor_version; + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; + jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */ + int script_space_size; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ + UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_minor_version; + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Aside from the specific data retained from APPn markers known to the + * library, the uninterpreted contents of any or all APPn and COM markers + * can be saved in a list for examination by the application. + */ + jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; + + /* Maximum allocation request accepted by alloc_large. */ + long max_alloc_chunk; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_CreateCompress jCreaCompress +#define jpeg_CreateDecompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_m_header jWrtMHeader +#define jpeg_write_m_byte jWrtMByte +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_save_markers jSaveMarkers +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN(struct jpeg_error_mgr *) jpeg_std_error + JPP((struct jpeg_error_mgr * err)); + +/* Initialization of JPEG compression objects. + * jpeg_create_compress() and jpeg_create_decompress() are the exported + * names that applications should call. These expand to calls on + * jpeg_CreateCompress and jpeg_CreateDecompress with additional information + * passed for version mismatch checking. + * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx. + */ +#define jpeg_create_compress(cinfo) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_compress_struct)) +#define jpeg_create_decompress(cinfo) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t) sizeof(struct jpeg_decompress_struct)) +EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo, + int version, size_t structsize)); +EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo, + int version, size_t structsize)); +/* Destruction of JPEG compression objects */ +EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile)); + +/* Default parameter setup for compression */ +EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN(int) jpeg_quality_scaling JPP((int quality)); +EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN(void) jpeg_write_marker + JPP((j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen)); +/* Same, but piecemeal. */ +EXTERN(void) jpeg_write_m_header + JPP((j_compress_ptr cinfo, int marker, unsigned int datalen)); +EXTERN(void) jpeg_write_m_byte + JPP((j_compress_ptr cinfo, int val)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Control saving of COM and APPn markers into marker_list. */ +EXTERN(void) jpeg_save_markers + JPP((j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN(void) jpeg_set_marker_processor + JPP((j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN(void) jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN(void) jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN(void) jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#endif /* JPEGLIB_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant1.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant1.c new file mode 100644 index 0000000..7153f46 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant1.c @@ -0,0 +1,856 @@ +/* + * jquant1.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 1-pass color quantization (color mapping) routines. + * These routines provide mapping to a fixed color map using equally spaced + * color values. Optional Floyd-Steinberg or ordered dithering is available. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_1PASS_SUPPORTED + + +/* + * The main purpose of 1-pass quantization is to provide a fast, if not very + * high quality, colormapped output capability. A 2-pass quantizer usually + * gives better visual quality; however, for quantized grayscale output this + * quantizer is perfectly adequate. Dithering is highly recommended with this + * quantizer, though you can turn it off if you really want to. + * + * In 1-pass quantization the colormap must be chosen in advance of seeing the + * image. We use a map consisting of all combinations of Ncolors[i] color + * values for the i'th component. The Ncolors[] values are chosen so that + * their product, the total number of colors, is no more than that requested. + * (In most cases, the product will be somewhat less.) + * + * Since the colormap is orthogonal, the representative value for each color + * component can be determined without considering the other components; + * then these indexes can be combined into a colormap index by a standard + * N-dimensional-array-subscript calculation. Most of the arithmetic involved + * can be precalculated and stored in the lookup table colorindex[]. + * colorindex[i][j] maps pixel value j in component i to the nearest + * representative value (grid plane) for that component; this index is + * multiplied by the array stride for component i, so that the + * index of the colormap entry closest to a given pixel value is just + * sum( colorindex[component-number][pixel-component-value] ) + * Aside from being fast, this scheme allows for variable spacing between + * representative values with no additional lookup cost. + * + * If gamma correction has been applied in color conversion, it might be wise + * to adjust the color grid spacing so that the representative colors are + * equidistant in linear space. At this writing, gamma correction is not + * implemented by jdcolor, so nothing is done here. + */ + + +/* Declarations for ordered dithering. + * + * We use a standard 16x16 ordered dither array. The basic concept of ordered + * dithering is described in many references, for instance Dale Schumacher's + * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). + * In place of Schumacher's comparisons against a "threshold" value, we add a + * "dither" value to the input pixel and then round the result to the nearest + * output value. The dither value is equivalent to (0.5 - threshold) times + * the distance between output values. For ordered dithering, we assume that + * the output colors are equally spaced; if not, results will probably be + * worse, since the dither may be too much or too little at a given point. + * + * The normal calculation would be to form pixel value + dither, range-limit + * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual. + * We can skip the separate range-limiting step by extending the colorindex + * table in both directions. + */ + +#define ODITHER_SIZE 16 /* dimension of dither matrix */ +/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ +#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ +#define ODITHER_MASK (ODITHER_SIZE-1) /* mask for wrapping around counters */ + +typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; +typedef int (*ODITHER_MATRIX_PTR)[ODITHER_SIZE]; + +static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { + /* Bayer's order-4 dither array. Generated by the code given in + * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. + * The values in this array must range from 0 to ODITHER_CELLS-1. + */ + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array is indexed [component#][position]. + * We provide (#columns + 2) entries per component; the extra entry at each + * end saves us from special-casing the first and last pixels. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +#define MAX_Q_COMPS 4 /* max components I can handle */ + +typedef struct { + struct jpeg_color_quantizer pub; /* public fields */ + + /* Initially allocated colormap is saved here */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ + + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + /* colorindex[i][j] = index of color closest to pixel value j in component i, + * premultiplied as described above. Since colormap indexes must fit into + * JSAMPLEs, the entries of this array will too. + */ + boolean is_padded; /* is the colorindex padded for odither? */ + + int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ + + /* Variables for ordered dithering */ + int row_index; /* cur row's vertical index in dither matrix */ + ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Policy-making subroutines for create_colormap and create_colorindex. + * These routines determine the colormap to be used. The rest of the module + * only assumes that the colormap is orthogonal. + * + * * select_ncolors decides how to divvy up the available colors + * among the components. + * * output_value defines the set of representative values for a component. + * * largest_input_value defines the mapping from input values to + * representative values for a component. + * Note that the latter two routines may impose different policies for + * different components, though this is not currently done. + */ + + +LOCAL(int) +select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) +/* Determine allocation of desired colors to components, */ +/* and fill in Ncolors[] array to indicate choice. */ +/* Return value is total number of colors (product of Ncolors[] values). */ +{ + int nc = cinfo->out_color_components; /* number of color components */ + int max_colors = cinfo->desired_number_of_colors; + int total_colors, iroot, i, j; + boolean changed; + long temp; + static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE }; + + /* We can allocate at least the nc'th root of max_colors per component. */ + /* Compute floor(nc'th root of max_colors). */ + iroot = 1; + do { + iroot++; + temp = iroot; /* set temp = iroot ** nc */ + for (i = 1; i < nc; i++) + temp *= iroot; + } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */ + iroot--; /* now iroot = floor(root) */ + + /* Must have at least 2 color values per component */ + if (iroot < 2) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, (int) temp); + + /* Initialize to iroot color values for each component */ + total_colors = 1; + for (i = 0; i < nc; i++) { + Ncolors[i] = iroot; + total_colors *= iroot; + } + /* We may be able to increment the count for one or more components without + * exceeding max_colors, though we know not all can be incremented. + * Sometimes, the first component can be incremented more than once! + * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) + * In RGB colorspace, try to increment G first, then R, then B. + */ + do { + changed = FALSE; + for (i = 0; i < nc; i++) { + j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i); + /* calculate new total_colors if Ncolors[j] is incremented */ + temp = total_colors / Ncolors[j]; + temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ + if (temp > (long) max_colors) + break; /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ + total_colors = (int) temp; + changed = TRUE; + } + } while (changed); + + return total_colors; +} + + +LOCAL(int) +output_value (j_decompress_ptr, int, int j, int maxj) +/* Return j'th output value, where j will range from 0 to maxj */ +/* The output values must fall in 0..MAXJSAMPLE in increasing order */ +{ + /* We always provide values 0 and MAXJSAMPLE for each component; + * any additional values are equally spaced between these limits. + * (Forcing the upper and lower values to the limits ensures that + * dithering can't produce a color outside the selected gamut.) + */ + return (int) (((INT32) j * MAXJSAMPLE + maxj/2) / maxj); +} + + +LOCAL(int) +largest_input_value (j_decompress_ptr, int, int j, int maxj) +/* Return largest input value that should map to j'th output value */ +/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */ +{ + /* Breakpoints are halfway between values returned by output_value */ + return (int) (((INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj)); +} + + +/* + * Create the colormap. + */ + +LOCAL(void) +create_colormap (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ + int i,j,k, nci, blksize, blkdist, ptr, val; + + /* Select number of colors for each component */ + total_colors = select_ncolors(cinfo, cquantize->Ncolors); + + /* Report selected color counts */ + if (cinfo->out_color_components == 3) + TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS, + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2]); + else + TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors); + + /* Allocate and fill in the colormap. */ + /* The colors are ordered in the map in standard row-major order, */ + /* i.e. rightmost (highest-indexed) color changes most rapidly. */ + + colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + /* blkdist is distance between groups of identical entries for a component */ + blkdist = total_colors; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colormap entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blkdist / nci; + for (j = 0; j < nci; j++) { + /* Compute j'th output value (out of nci) for component */ + val = output_value(cinfo, i, j, nci-1); + /* Fill in all colormap entries that have this value of this component */ + for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) { + /* fill in blksize entries beginning at ptr */ + for (k = 0; k < blksize; k++) + colormap[i][ptr+k] = (JSAMPLE) val; + } + } + blkdist = blksize; /* blksize of this color is blkdist of next */ + } + + /* Save the colormap in private storage, + * where it will survive color quantization mode changes. + */ + cquantize->sv_colormap = colormap; + cquantize->sv_actual = total_colors; +} + + +/* + * Create the color index table. + */ + +LOCAL(void) +create_colorindex (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPROW indexptr; + int i,j,k, nci, blksize, val, pad; + + /* For ordered dither, we pad the color index tables by MAXJSAMPLE in + * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE). + * This is not necessary in the other dithering modes. However, we + * flag whether it was done in case user changes dithering mode. + */ + if (cinfo->dither_mode == JDITHER_ORDERED) { + pad = MAXJSAMPLE*2; + cquantize->is_padded = TRUE; + } else { + pad = 0; + cquantize->is_padded = FALSE; + } + + cquantize->colorindex = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (MAXJSAMPLE+1 + pad), + (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + blksize = cquantize->sv_actual; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colorindex entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blksize / nci; + + /* adjust colorindex pointers to provide padding at negative indexes. */ + if (pad) + cquantize->colorindex[i] += MAXJSAMPLE; + + /* in loop, val = index of current output value, */ + /* and k = largest j that maps to current val */ + indexptr = cquantize->colorindex[i]; + val = 0; + k = largest_input_value(cinfo, i, 0, nci-1); + for (j = 0; j <= MAXJSAMPLE; j++) { + while (j > k) /* advance val if past boundary */ + k = largest_input_value(cinfo, i, ++val, nci-1); + /* premultiply so that no multiplication needed in main processing */ + indexptr[j] = (JSAMPLE) (val * blksize); + } + /* Pad at both ends if necessary */ + if (pad) + for (j = 1; j <= MAXJSAMPLE; j++) { + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; + } + } +} + + +/* + * Create an ordered-dither array for a component having ncolors + * distinct output values. + */ + +LOCAL(ODITHER_MATRIX_PTR) +make_odither_array (j_decompress_ptr cinfo, int ncolors) +{ + ODITHER_MATRIX_PTR odither; + int j,k; + INT32 num,den; + + odither = (ODITHER_MATRIX_PTR) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(ODITHER_MATRIX)); + /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). + * Hence the dither value for the matrix cell with fill order f + * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). + * On 16-bit-int machine, be careful to avoid overflow. + */ + den = 2 * ODITHER_CELLS * ((INT32) (ncolors - 1)); + for (j = 0; j < ODITHER_SIZE; j++) { + for (k = 0; k < ODITHER_SIZE; k++) { + num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k]))) + * MAXJSAMPLE; + /* Ensure round towards zero despite C's lack of consistency + * about rounding negative values in integer division... + */ + odither[j][k] = (int) (num<0 ? -((-num)/den) : num/den); + } + } + return odither; +} + + +/* + * Create the ordered-dither tables. + * Components having the same number of representative colors may + * share a dither table. + */ + +LOCAL(void) +create_odither_tables (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + ODITHER_MATRIX_PTR odither; + int i, j, nci; + + for (i = 0; i < cinfo->out_color_components; i++) { + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + odither = NULL; /* search for matching prior component */ + for (j = 0; j < i; j++) { + if (nci == cquantize->Ncolors[j]) { + odither = cquantize->odither[j]; + break; + } + } + if (odither == NULL) /* need a new table? */ + odither = make_odither_array(cinfo, nci); + cquantize->odither[i] = odither; + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colorindex = cquantize->colorindex; + register int pixcode, ci; + register JSAMPROW ptrin, ptrout; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + register int nc = cinfo->out_color_components; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = 0; + for (ci = 0; ci < nc; ci++) { + pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); + } + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW ptrin, ptrout; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*ptrin++)]); + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF(void) +quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + int * dither; /* points to active row of dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ + int nc = cinfo->out_color_components; + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + row_index = cquantize->row_index; + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + colorindex_ci = cquantize->colorindex[ci]; + dither = cquantize->odither[ci][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = (col_index + 1) & ODITHER_MASK; + } + } + /* Advance row index for next row */ + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int * dither0; /* points to active row of dither matrix */ + int * dither1; + int * dither2; + int row_index, col_index; /* current indexes into dither matrix */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + row_index = cquantize->row_index; + input_ptr = input_buf[row]; + output_ptr = output_buf[row]; + dither0 = cquantize->odither[0][row_index]; + dither1 = cquantize->odither[1][row_index]; + dither2 = cquantize->odither[2][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) + + dither0[col_index]]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) + + dither1[col_index]]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) + + dither2[col_index]]); + *output_ptr++ = (JSAMPLE) pixcode; + col_index = (col_index + 1) & ODITHER_MASK; + } + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF(void) +quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register LOCFSERROR cur; /* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR delta; + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + JSAMPROW colormap_ci; + int pixcode; + int nc = cinfo->out_color_components; + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + input_ptr += (width-1) * nc; /* so point to rightmost pixel */ + output_ptr += width-1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ + } else { + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci]; /* => entry before first column */ + } + colorindex_ci = cquantize->colorindex[ci]; + colormap_ci = cquantize->sv_colormap[ci]; + /* Preset error values: no error propagated to first pixel from left */ + cur = 0; + /* and no error propagated to row below yet */ + belowerr = bpreverr = 0; + + for (col = width; col > 0; col--) { + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE(*input_ptr); + cur = GETJSAMPLE(range_limit[cur]); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE(colorindex_ci[cur]); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE(colormap_ci[pixcode]); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr + cur); + cur += delta; /* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta; /* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc; /* advance input ptr to next column */ + output_ptr += dir; /* advance output ptr to next column */ + errorptr += dir; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error value into the + * final fserrors[] entry. Note we need not unload belowerr because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr; /* unload prev err into array */ + } + cquantize->on_odd_row = (cquantize->on_odd_row ? FALSE : TRUE); + } +} + + +/* + * Allocate workspace for Floyd-Steinberg errors. + */ + +LOCAL(void) +alloc_fs_workspace (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) { + cquantize->fserrors[i] = (FSERRPTR) + (*cinfo->mem->alloc_large)((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + } +} + + +/* + * Initialize for one-pass color quantization. + */ + +METHODDEF(void) +start_pass_1_quant (j_decompress_ptr cinfo, boolean) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + /* Install my colormap. */ + cinfo->colormap = cquantize->sv_colormap; + cinfo->actual_number_of_colors = cquantize->sv_actual; + + /* Initialize for desired dithering mode. */ + switch (cinfo->dither_mode) { + case JDITHER_NONE: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = color_quantize3; + else + cquantize->pub.color_quantize = color_quantize; + break; + case JDITHER_ORDERED: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = quantize3_ord_dither; + else + cquantize->pub.color_quantize = quantize_ord_dither; + cquantize->row_index = 0; /* initialize state for ordered dither */ + /* If user changed to ordered dither from another mode, + * we must recreate the color index table with padding. + * This will cost extra space, but probably isn't very likely. + */ + if (! cquantize->is_padded) + create_colorindex(cinfo); + /* Create ordered-dither tables if we didn't already. */ + if (cquantize->odither[0] == NULL) + create_odither_tables(cinfo); + break; + case JDITHER_FS: + cquantize->pub.color_quantize = quantize_fs_dither; + cquantize->on_odd_row = FALSE; /* initialize state for F-S dither */ + /* Allocate Floyd-Steinberg workspace if didn't already. */ + if (cquantize->fserrors[0] == NULL) + alloc_fs_workspace(cinfo); + /* Initialize the propagated errors to zero. */ + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) + jzero_far((void FAR *) cquantize->fserrors[i], arraysize); + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } +} + + +/* + * Finish up at the end of the pass. + */ + +METHODDEF(void) +finish_pass_1_quant (j_decompress_ptr) +{ + /* no work in 1-pass case */ +} + + +/* + * Switch to a new external colormap between output passes. + * Shouldn't get to this module! + */ + +METHODDEF(void) +new_color_map_1_quant (j_decompress_ptr cinfo) +{ + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + + +/* + * Module initialization routine for 1-pass color quantization. + */ + +GLOBAL(void) +jinit_1pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_1_quant; + cquantize->pub.finish_pass = finish_pass_1_quant; + cquantize->pub.new_color_map = new_color_map_1_quant; + cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */ + cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ + + /* Make sure my internal arrays won't overflow */ + if (cinfo->out_color_components > MAX_Q_COMPS) + ERREXIT1(cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (cinfo->desired_number_of_colors > (MAXJSAMPLE+1)) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1); + + /* Create the colormap and color index table. */ + create_colormap(cinfo); + create_colorindex(cinfo); + + /* Allocate Floyd-Steinberg workspace now if requested. + * We do this now since it is FAR storage and may affect the memory + * manager's space calculations. If the user changes to FS dither + * mode in a later pass, we will allocate the space then, and will + * possibly overrun the max_memory_to_use setting. + */ + if (cinfo->dither_mode == JDITHER_FS) + alloc_fs_workspace(cinfo); +} + +#endif /* QUANT_1PASS_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant2.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant2.c new file mode 100644 index 0000000..6c2524e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jquant2.c @@ -0,0 +1,1310 @@ +/* + * jquant2.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 2-pass color quantization (color mapping) routines. + * These routines provide selection of a custom color map for an image, + * followed by mapping of the image to that color map, with optional + * Floyd-Steinberg dithering. + * It is also possible to use just the second pass to map to an arbitrary + * externally-given color map. + * + * Note: ordered dithering is not supported, since there isn't any fast + * way to compute intercolor distances; it's unclear that ordered dither's + * fundamental assumptions even hold with an irregularly spaced color map. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_2PASS_SUPPORTED + + +/* + * This module implements the well-known Heckbert paradigm for color + * quantization. Most of the ideas used here can be traced back to + * Heckbert's seminal paper + * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", + * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. + * + * In the first pass over the image, we accumulate a histogram showing the + * usage count of each possible color. To keep the histogram to a reasonable + * size, we reduce the precision of the input; typical practice is to retain + * 5 or 6 bits per color, so that 8 or 4 different input values are counted + * in the same histogram cell. + * + * Next, the color-selection step begins with a box representing the whole + * color space, and repeatedly splits the "largest" remaining box until we + * have as many boxes as desired colors. Then the mean color in each + * remaining box becomes one of the possible output colors. + * + * The second pass over the image maps each input pixel to the closest output + * color (optionally after applying a Floyd-Steinberg dithering correction). + * This mapping is logically trivial, but making it go fast enough requires + * considerable care. + * + * Heckbert-style quantizers vary a good deal in their policies for choosing + * the "largest" box and deciding where to cut it. The particular policies + * used here have proved out well in experimental comparisons, but better ones + * may yet be found. + * + * In earlier versions of the IJG code, this module quantized in YCbCr color + * space, processing the raw upsampled data without a color conversion step. + * This allowed the color conversion math to be done only once per colormap + * entry, not once per pixel. However, that optimization precluded other + * useful optimizations (such as merging color conversion with upsampling) + * and it also interfered with desired capabilities such as quantizing to an + * externally-supplied colormap. We have therefore abandoned that approach. + * The present code works in the post-conversion color space, typically RGB. + * + * To improve the visual quality of the results, we actually work in scaled + * RGB space, giving G distances more weight than R, and R in turn more than + * B. To do everything in integer math, we must use integer scale factors. + * The 2/3/1 scale factors used here correspond loosely to the relative + * weights of the colors in the NTSC grayscale equation. + * If you want to use this code to quantize a non-RGB color space, you'll + * probably need to change these scale factors. + */ + +#define R_SCALE 2 /* scale R distances by this much */ +#define G_SCALE 3 /* scale G distances by this much */ +#define B_SCALE 1 /* and B by this much */ + +/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined + * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B + * and B,G,R orders. If you define some other weird order in jmorecfg.h, + * you'll get compile errors until you extend this logic. In that case + * you'll probably want to tweak the histogram sizes too. + */ + +#if RGB_RED == 0 +#define C0_SCALE R_SCALE +#endif +#if RGB_BLUE == 0 +#define C0_SCALE B_SCALE +#endif +#if RGB_GREEN == 1 +#define C1_SCALE G_SCALE +#endif +#if RGB_RED == 2 +#define C2_SCALE R_SCALE +#endif +#if RGB_BLUE == 2 +#define C2_SCALE B_SCALE +#endif + + +/* + * First we have the histogram data structure and routines for creating it. + * + * The number of bits of precision can be adjusted by changing these symbols. + * We recommend keeping 6 bits for G and 5 each for R and B. + * If you have plenty of memory and cycles, 6 bits all around gives marginally + * better results; if you are short of memory, 5 bits all around will save + * some space but degrade the results. + * To maintain a fully accurate histogram, we'd need to allocate a "long" + * (preferably unsigned long) for each cell. In practice this is overkill; + * we can get by with 16 bits per cell. Few of the cell counts will overflow, + * and clamping those that do overflow to the maximum value will give close- + * enough results. This reduces the recommended histogram size from 256Kb + * to 128Kb, which is a useful savings on PC-class machines. + * (In the second pass the histogram space is re-used for pixel mapping data; + * in that capacity, each cell must be able to store zero to the number of + * desired colors. 16 bits/cell is plenty for that too.) + * Since the JPEG code is intended to run in small memory model on 80x86 + * machines, we can't just allocate the histogram in one chunk. Instead + * of a true 3-D array, we use a row of pointers to 2-D arrays. Each + * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and + * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that + * on 80x86 machines, the pointer row is in near memory but the actual + * arrays are in far memory (same arrangement as we use for image arrays). + */ + +#define MAXNUMCOLORS (MAXJSAMPLE+1) /* maximum size of colormap */ + +/* These will do the right thing for either R,G,B or B,G,R color order, + * but you may not like the results for other color orders. + */ +#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */ +#define HIST_C1_BITS 6 /* bits of precision in G histogram */ +#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */ + +/* Number of elements along histogram axes. */ +#define HIST_C0_ELEMS (1<cquantize; + register JSAMPROW ptr; + register histptr histp; + register hist3d histogram = cquantize->histogram; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptr = input_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the histogram */ + histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT] + [GETJSAMPLE(ptr[1]) >> C1_SHIFT] + [GETJSAMPLE(ptr[2]) >> C2_SHIFT]; + /* increment, check for overflow and undo increment if so. */ + if (++(*histp) <= 0) + (*histp)--; + ptr += 3; + } + } +} + + +/* + * Next we have the really interesting routines: selection of a colormap + * given the completed histogram. + * These routines work with a list of "boxes", each representing a rectangular + * subset of the input color space (to histogram precision). + */ + +typedef struct { + /* The bounds of the box (inclusive); expressed as histogram indexes */ + int c0min, c0max; + int c1min, c1max; + int c2min, c2max; + /* The volume (actually 2-norm) of the box */ + INT32 volume; + /* The number of nonzero histogram cells within this box */ + long colorcount; +} box; + +typedef box * boxptr; + + +LOCAL(boxptr) +find_biggest_color_pop (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest color population */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register long maxc = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->colorcount > maxc && boxp->volume > 0) { + which = boxp; + maxc = boxp->colorcount; + } + } + return which; +} + + +LOCAL(boxptr) +find_biggest_volume (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest (scaled) volume */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register INT32 maxv = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->volume > maxv) { + which = boxp; + maxv = boxp->volume; + } + } + return which; +} + + +LOCAL(void) +update_box (j_decompress_ptr cinfo, boxptr boxp) +/* Shrink the min/max bounds of a box to enclose only nonzero elements, */ +/* and recompute its volume and population */ +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + INT32 dist0,dist1,dist2; + long ccount; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + if (c0max > c0min) + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0min = c0min = c0; + goto have_c0min; + } + } + have_c0min: + if (c0max > c0min) + for (c0 = c0max; c0 >= c0min; c0--) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0max = c0max = c0; + goto have_c0max; + } + } + have_c0max: + if (c1max > c1min) + for (c1 = c1min; c1 <= c1max; c1++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1min = c1min = c1; + goto have_c1min; + } + } + have_c1min: + if (c1max > c1min) + for (c1 = c1max; c1 >= c1min; c1--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1max = c1max = c1; + goto have_c1max; + } + } + have_c1max: + if (c2max > c2min) + for (c2 = c2min; c2 <= c2max; c2++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2min = c2min = c2; + goto have_c2min; + } + } + have_c2min: + if (c2max > c2min) + for (c2 = c2max; c2 >= c2min; c2--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2max = c2max = c2; + goto have_c2max; + } + } + have_c2max: + + /* Update box volume. + * We use 2-norm rather than real volume here; this biases the method + * against making long narrow boxes, and it has the side benefit that + * a box is splittable iff norm > 0. + * Since the differences are expressed in histogram-cell units, + * we have to shift back to JSAMPLE units to get consistent distances; + * after which, we scale according to the selected distance scale factors. + */ + dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE; + dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE; + dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE; + boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2; + + /* Now scan remaining volume of box and compute population */ + ccount = 0; + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++, histp++) + if (*histp != 0) { + ccount++; + } + } + boxp->colorcount = ccount; +} + + +LOCAL(int) +median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes, + int desired_colors) +/* Repeatedly select and split the largest box until we have enough boxes */ +{ + int n,lb; + int c0,c1,c2,cmax; + register boxptr b1,b2; + + while (numboxes < desired_colors) { + /* Select box to split. + * Current algorithm: by population for first half, then by volume. + */ + if (numboxes*2 <= desired_colors) { + b1 = find_biggest_color_pop(boxlist, numboxes); + } else { + b1 = find_biggest_volume(boxlist, numboxes); + } + if (b1 == NULL) /* no splittable boxes left! */ + break; + b2 = &boxlist[numboxes]; /* where new box will go */ + /* Copy the color bounds to the new box. */ + b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max; + b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min; + /* Choose which axis to split the box on. + * Current algorithm: longest scaled axis. + * See notes in update_box about scaling distances. + */ + c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE; + c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE; + c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE; + /* We want to break any ties in favor of green, then red, blue last. + * This code does the right thing for R,G,B or B,G,R color orders only. + */ +#if RGB_RED == 0 + cmax = c1; n = 1; + if (c0 > cmax) { cmax = c0; n = 0; } + if (c2 > cmax) { n = 2; } +#else + cmax = c1; n = 1; + if (c2 > cmax) { cmax = c2; n = 2; } + if (c0 > cmax) { n = 0; } +#endif + /* Choose split point along selected axis, and update box bounds. + * Current algorithm: split at halfway point. + * (Since the box has been shrunk to minimum volume, + * any split will produce two nonempty subboxes.) + * Note that lb value is max for lower box, so must be < old max. + */ + switch (n) { + case 0: + lb = (b1->c0max + b1->c0min) / 2; + b1->c0max = lb; + b2->c0min = lb+1; + break; + case 1: + lb = (b1->c1max + b1->c1min) / 2; + b1->c1max = lb; + b2->c1min = lb+1; + break; + case 2: + lb = (b1->c2max + b1->c2min) / 2; + b1->c2max = lb; + b2->c2min = lb+1; + break; + } + /* Update stats for boxes */ + update_box(cinfo, b1); + update_box(cinfo, b2); + numboxes++; + } + return numboxes; +} + + +LOCAL(void) +compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor) +/* Compute representative color for a box, put it in colormap[icolor] */ +{ + /* Current algorithm: mean weighted by pixels (not colors) */ + /* Note it is important to get the rounding correct! */ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + long count; + long total = 0; + long c0total = 0; + long c1total = 0; + long c2total = 0; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) { + if ((count = *histp++) != 0) { + total += count; + c0total += ((c0 << C0_SHIFT) + ((1<>1)) * count; + c1total += ((c1 << C1_SHIFT) + ((1<>1)) * count; + c2total += ((c2 << C2_SHIFT) + ((1<>1)) * count; + } + } + } + + cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total); + cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total); + cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total); +} + + +LOCAL(void) +select_colors (j_decompress_ptr cinfo, int desired_colors) +/* Master routine for color selection */ +{ + boxptr boxlist; + int numboxes; + int i; + + /* Allocate workspace for box list */ + boxlist = (boxptr) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF(box)); + /* Initialize one box containing whole space */ + numboxes = 1; + boxlist[0].c0min = 0; + boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT; + boxlist[0].c1min = 0; + boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT; + boxlist[0].c2min = 0; + boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT; + /* Shrink it to actually-used volume and set its statistics */ + update_box(cinfo, & boxlist[0]); + /* Perform median-cut to produce final box list */ + numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors); + /* Compute the representative color for each box, fill colormap */ + for (i = 0; i < numboxes; i++) + compute_color(cinfo, & boxlist[i], i); + cinfo->actual_number_of_colors = numboxes; + TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes); +} + + +/* + * These routines are concerned with the time-critical task of mapping input + * colors to the nearest color in the selected colormap. + * + * We re-use the histogram space as an "inverse color map", essentially a + * cache for the results of nearest-color searches. All colors within a + * histogram cell will be mapped to the same colormap entry, namely the one + * closest to the cell's center. This may not be quite the closest entry to + * the actual input color, but it's almost as good. A zero in the cache + * indicates we haven't found the nearest color for that cell yet; the array + * is cleared to zeroes before starting the mapping pass. When we find the + * nearest color for a cell, its colormap index plus one is recorded in the + * cache for future use. The pass2 scanning routines call fill_inverse_cmap + * when they need to use an unfilled entry in the cache. + * + * Our method of efficiently finding nearest colors is based on the "locally + * sorted search" idea described by Heckbert and on the incremental distance + * calculation described by Spencer W. Thomas in chapter III.1 of Graphics + * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that + * the distances from a given colormap entry to each cell of the histogram can + * be computed quickly using an incremental method: the differences between + * distances to adjacent cells themselves differ by a constant. This allows a + * fairly fast implementation of the "brute force" approach of computing the + * distance from every colormap entry to every histogram cell. Unfortunately, + * it needs a work array to hold the best-distance-so-far for each histogram + * cell (because the inner loop has to be over cells, not colormap entries). + * The work array elements have to be INT32s, so the work array would need + * 256Kb at our recommended precision. This is not feasible in DOS machines. + * + * To get around these problems, we apply Thomas' method to compute the + * nearest colors for only the cells within a small subbox of the histogram. + * The work array need be only as big as the subbox, so the memory usage + * problem is solved. Furthermore, we need not fill subboxes that are never + * referenced in pass2; many images use only part of the color gamut, so a + * fair amount of work is saved. An additional advantage of this + * approach is that we can apply Heckbert's locality criterion to quickly + * eliminate colormap entries that are far away from the subbox; typically + * three-fourths of the colormap entries are rejected by Heckbert's criterion, + * and we need not compute their distances to individual cells in the subbox. + * The speed of this approach is heavily influenced by the subbox size: too + * small means too much overhead, too big loses because Heckbert's criterion + * can't eliminate as many colormap entries. Empirically the best subbox + * size seems to be about 1/512th of the histogram (1/8th in each direction). + * + * Thomas' article also describes a refined method which is asymptotically + * faster than the brute-force method, but it is also far more complex and + * cannot efficiently be applied to small subboxes. It is therefore not + * useful for programs intended to be portable to DOS machines. On machines + * with plenty of memory, filling the whole histogram in one shot with Thomas' + * refined method might be faster than the present code --- but then again, + * it might not be any faster, and it's certainly more complicated. + */ + + +/* log2(histogram cells in update box) for each axis; this can be adjusted */ +#define BOX_C0_LOG (HIST_C0_BITS-3) +#define BOX_C1_LOG (HIST_C1_BITS-3) +#define BOX_C2_LOG (HIST_C2_BITS-3) + +#define BOX_C0_ELEMS (1<actual_number_of_colors; + int maxc0, maxc1, maxc2; + int centerc0, centerc1, centerc2; + int i, x, ncolors; + INT32 minmaxdist, min_dist, max_dist, tdist; + INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */ + + /* Compute true coordinates of update box's upper corner and center. + * Actually we compute the coordinates of the center of the upper-corner + * histogram cell, which are the upper bounds of the volume we care about. + * Note that since ">>" rounds down, the "center" values may be closer to + * min than to max; hence comparisons to them must be "<=", not "<". + */ + maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT)); + centerc0 = (minc0 + maxc0) >> 1; + maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT)); + centerc1 = (minc1 + maxc1) >> 1; + maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT)); + centerc2 = (minc2 + maxc2) >> 1; + + /* For each color in colormap, find: + * 1. its minimum squared-distance to any point in the update box + * (zero if color is within update box); + * 2. its maximum squared-distance to any point in the update box. + * Both of these can be found by considering only the corners of the box. + * We save the minimum distance for each color in mindist[]; + * only the smallest maximum distance is of interest. + */ + minmaxdist = 0x7FFFFFFFL; + + for (i = 0; i < numcolors; i++) { + /* We compute the squared-c0-distance term, then add in the other two. */ + x = GETJSAMPLE(cinfo->colormap[0][i]); + if (x < minc0) { + tdist = (x - minc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else if (x > maxc0) { + tdist = (x - maxc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + min_dist = 0; + if (x <= centerc0) { + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[1][i]); + if (x < minc1) { + tdist = (x - minc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc1) { + tdist = (x - maxc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc1) { + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[2][i]); + if (x < minc2) { + tdist = (x - minc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc2) { + tdist = (x - maxc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc2) { + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } + } + + mindist[i] = min_dist; /* save away the results */ + if (max_dist < minmaxdist) + minmaxdist = max_dist; + } + + /* Now we know that no cell in the update box is more than minmaxdist + * away from some colormap entry. Therefore, only colors that are + * within minmaxdist of some part of the box need be considered. + */ + ncolors = 0; + for (i = 0; i < numcolors; i++) { + if (mindist[i] <= minmaxdist) + colorlist[ncolors++] = (JSAMPLE) i; + } + return ncolors; +} + + +LOCAL(void) +find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[]) +/* Find the closest colormap entry for each cell in the update box, + * given the list of candidate colors prepared by find_nearby_colors. + * Return the indexes of the closest entries in the bestcolor[] array. + * This routine uses Thomas' incremental distance calculation method to + * find the distance from a colormap entry to successive cells in the box. + */ +{ + int ic0, ic1, ic2; + int i, icolor; + register INT32 * bptr; /* pointer into bestdist[] array */ + JSAMPLE * cptr; /* pointer into bestcolor[] array */ + INT32 dist0, dist1; /* initial distance values */ + register INT32 dist2; /* current distance in inner loop */ + INT32 xx0, xx1; /* distance increments */ + register INT32 xx2; + INT32 inc0, inc1, inc2; /* initial values for increments */ + /* This array holds the distance to the nearest-so-far color for each cell */ + INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Initialize best-distance for each cell of the update box */ + bptr = bestdist; + for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--) + *bptr++ = 0x7FFFFFFFL; + + /* For each color selected by find_nearby_colors, + * compute its distance to the center of each cell in the box. + * If that's less than best-so-far, update best distance and color number. + */ + + /* Nominal steps between cell centers ("x" in Thomas article) */ +#define STEP_C0 ((1 << C0_SHIFT) * C0_SCALE) +#define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE) +#define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE) + + for (i = 0; i < numcolors; i++) { + icolor = GETJSAMPLE(colorlist[i]); + /* Compute (square of) distance from minc0/c1/c2 to this color */ + inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE; + dist0 = inc0*inc0; + inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE; + dist0 += inc1*inc1; + inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE; + dist0 += inc2*inc2; + /* Form the initial difference increments */ + inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; + inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; + inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; + /* Now loop over all cells in box, updating distance per Thomas method */ + bptr = bestdist; + cptr = bestcolor; + xx0 = inc0; + for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) { + dist1 = dist0; + xx1 = inc1; + for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) { + dist2 = dist1; + xx2 = inc2; + for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) { + if (dist2 < *bptr) { + *bptr = dist2; + *cptr = (JSAMPLE) icolor; + } + dist2 += xx2; + xx2 += 2 * STEP_C2 * STEP_C2; + bptr++; + cptr++; + } + dist1 += xx1; + xx1 += 2 * STEP_C1 * STEP_C1; + } + dist0 += xx0; + xx0 += 2 * STEP_C0 * STEP_C0; + } + } +} + + +LOCAL(void) +fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2) +/* Fill the inverse-colormap entries in the update box that contains */ +/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ +/* we can fill as many others as we wish.) */ +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int minc0, minc1, minc2; /* lower left corner of update box */ + int ic0, ic1, ic2; + register JSAMPLE * cptr; /* pointer into bestcolor[] array */ + register histptr cachep; /* pointer into main cache array */ + /* This array lists the candidate colormap indexes. */ + JSAMPLE colorlist[MAXNUMCOLORS]; + int numcolors; /* number of candidate colors */ + /* This array holds the actually closest colormap index for each cell. */ + JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Convert cell coordinates to update box ID */ + c0 >>= BOX_C0_LOG; + c1 >>= BOX_C1_LOG; + c2 >>= BOX_C2_LOG; + + /* Compute true coordinates of update box's origin corner. + * Actually we compute the coordinates of the center of the corner + * histogram cell, which are the lower bounds of the volume we care about. + */ + minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1); + minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1); + minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1); + + /* Determine which colormap entries are close enough to be candidates + * for the nearest entry to some cell in the update box. + */ + numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist); + + /* Determine the actually nearest colors. */ + find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist, + bestcolor); + + /* Save the best color numbers (plus 1) in the main cache array */ + c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ + c1 <<= BOX_C1_LOG; + c2 <<= BOX_C2_LOG; + cptr = bestcolor; + for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { + for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { + cachep = & histogram[c0+ic0][c1+ic1][c2]; + for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { + *cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1); + } + } + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF(void) +pass2_no_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs no dithering */ +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register JSAMPROW inptr, outptr; + register histptr cachep; + register int c0, c1, c2; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the cache */ + c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT; + c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT; + c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT; + cachep = & histogram[c0][c1][c2]; + /* If we have not seen this color before, find nearest colormap entry */ + /* and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, c0,c1,c2); + /* Now emit the colormap index for this cell */ + *outptr++ = (JSAMPLE) (*cachep - 1); + } + } +} + + +METHODDEF(void) +pass2_fs_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs Floyd-Steinberg dithering */ +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */ + LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */ + LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */ + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW inptr; /* => current input pixel */ + JSAMPROW outptr; /* => current output pixel */ + histptr cachep; + int dir; /* +1 or -1 depending on direction */ + int dir3; /* 3*dir, for advancing inptr & errorptr */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + int *error_limit = cquantize->error_limiter; + JSAMPROW colormap0 = cinfo->colormap[0]; + JSAMPROW colormap1 = cinfo->colormap[1]; + JSAMPROW colormap2 = cinfo->colormap[2]; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + inptr += (width-1) * 3; /* so point to rightmost pixel */ + outptr += width-1; + dir = -1; + dir3 = -3; + errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */ + cquantize->on_odd_row = FALSE; /* flip for next time */ + } else { + /* work left to right in this row */ + dir = 1; + dir3 = 3; + errorptr = cquantize->fserrors; /* => entry before first real column */ + cquantize->on_odd_row = TRUE; /* flip for next time */ + } + /* Preset error values: no error propagated to first pixel from left */ + cur0 = cur1 = cur2 = 0; + /* and no error propagated to row below yet */ + belowerr0 = belowerr1 = belowerr2 = 0; + bpreverr0 = bpreverr1 = bpreverr2 = 0; + + for (col = width; col > 0; col--) { + /* curN holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur0 = RIGHT_SHIFT(cur0 + errorptr[dir3+0] + 8, 4); + cur1 = RIGHT_SHIFT(cur1 + errorptr[dir3+1] + 8, 4); + cur2 = RIGHT_SHIFT(cur2 + errorptr[dir3+2] + 8, 4); + /* Limit the error using transfer function set by init_error_limit. + * See comments with init_error_limit for rationale. + */ + cur0 = error_limit[cur0]; + cur1 = error_limit[cur1]; + cur2 = error_limit[cur2]; + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE (or less with error limiting); + * this sets the required size of the range_limit array. + */ + cur0 += GETJSAMPLE(inptr[0]); + cur1 += GETJSAMPLE(inptr[1]); + cur2 += GETJSAMPLE(inptr[2]); + cur0 = GETJSAMPLE(range_limit[cur0]); + cur1 = GETJSAMPLE(range_limit[cur1]); + cur2 = GETJSAMPLE(range_limit[cur2]); + /* Index into the cache with adjusted pixel value */ + cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT]; + /* If we have not seen this color before, find nearest colormap */ + /* entry and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT); + /* Now emit the colormap index for this cell */ + { register int pixcode = *cachep - 1; + *outptr = (JSAMPLE) pixcode; + /* Compute representation error for this pixel */ + cur0 -= GETJSAMPLE(colormap0[pixcode]); + cur1 -= GETJSAMPLE(colormap1[pixcode]); + cur2 -= GETJSAMPLE(colormap2[pixcode]); + } + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + { register LOCFSERROR bnexterr, delta; + + bnexterr = cur0; /* Process component 0 */ + delta = cur0 * 2; + cur0 += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr0 + cur0); + cur0 += delta; /* form error * 5 */ + bpreverr0 = belowerr0 + cur0; + belowerr0 = bnexterr; + cur0 += delta; /* form error * 7 */ + bnexterr = cur1; /* Process component 1 */ + delta = cur1 * 2; + cur1 += delta; /* form error * 3 */ + errorptr[1] = (FSERROR) (bpreverr1 + cur1); + cur1 += delta; /* form error * 5 */ + bpreverr1 = belowerr1 + cur1; + belowerr1 = bnexterr; + cur1 += delta; /* form error * 7 */ + bnexterr = cur2; /* Process component 2 */ + delta = cur2 * 2; + cur2 += delta; /* form error * 3 */ + errorptr[2] = (FSERROR) (bpreverr2 + cur2); + cur2 += delta; /* form error * 5 */ + bpreverr2 = belowerr2 + cur2; + belowerr2 = bnexterr; + cur2 += delta; /* form error * 7 */ + } + /* At this point curN contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + inptr += dir3; /* Advance pixel pointers to next column */ + outptr += dir; + errorptr += dir3; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error values into the + * final fserrors[] entry. Note we need not unload belowerrN because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */ + errorptr[1] = (FSERROR) bpreverr1; + errorptr[2] = (FSERROR) bpreverr2; + } +} + + +/* + * Initialize the error-limiting transfer function (lookup table). + * The raw F-S error computation can potentially compute error values of up to + * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be + * much less, otherwise obviously wrong pixels will be created. (Typical + * effects include weird fringes at color-area boundaries, isolated bright + * pixels in a dark area, etc.) The standard advice for avoiding this problem + * is to ensure that the "corners" of the color cube are allocated as output + * colors; then repeated errors in the same direction cannot cause cascading + * error buildup. However, that only prevents the error from getting + * completely out of hand; Aaron Giles reports that error limiting improves + * the results even with corner colors allocated. + * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty + * well, but the smoother transfer function used below is even better. Thanks + * to Aaron Giles for this idea. + */ + +LOCAL(void) +init_error_limit (j_decompress_ptr cinfo) +/* Allocate and fill in the error_limiter table */ +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + int * table; + int in, out; + + table = (int *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, (MAXJSAMPLE*2+1) * SIZEOF(int)); + table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ + cquantize->error_limiter = table; + +#define STEPSIZE ((MAXJSAMPLE+1)/16) + /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ + out = 0; + for (in = 0; in < STEPSIZE; in++, out++) { + table[in] = out; table[-in] = -out; + } + /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ + for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) { + table[in] = out; table[-in] = -out; + } + /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ + for (; in <= MAXJSAMPLE; in++) { + table[in] = out; table[-in] = -out; + } +#undef STEPSIZE +} + + +/* + * Finish up at the end of each pass. + */ + +METHODDEF(void) +finish_pass1 (j_decompress_ptr cinfo) +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + + /* Select the representative colors and fill in cinfo->colormap */ + cinfo->colormap = cquantize->sv_colormap; + select_colors(cinfo, cquantize->desired); + /* Force next pass to zero the color index table */ + cquantize->needs_zeroed = TRUE; +} + + +METHODDEF(void) +finish_pass2 (j_decompress_ptr) +{ + /* no work */ +} + + +/* + * Initialize for each processing pass. + */ + +METHODDEF(void) +start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int i; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + if (is_pre_scan) { + /* Set up method pointers */ + cquantize->pub.color_quantize = prescan_quantize; + cquantize->pub.finish_pass = finish_pass1; + cquantize->needs_zeroed = TRUE; /* Always zero histogram */ + } else { + /* Set up method pointers */ + if (cinfo->dither_mode == JDITHER_FS) + cquantize->pub.color_quantize = pass2_fs_dither; + else + cquantize->pub.color_quantize = pass2_no_dither; + cquantize->pub.finish_pass = finish_pass2; + + /* Make sure color count is acceptable */ + i = cinfo->actual_number_of_colors; + if (i < 1) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1); + if (i > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + + if (cinfo->dither_mode == JDITHER_FS) { + size_t arraysize = (size_t) ((cinfo->output_width + 2) * + (3 * SIZEOF(FSERROR))); + /* Allocate Floyd-Steinberg workspace if we didn't already. */ + if (cquantize->fserrors == NULL) + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + /* Initialize the propagated errors to zero. */ + jzero_far((void FAR *) cquantize->fserrors, arraysize); + /* Make the error-limit table if we didn't already. */ + if (cquantize->error_limiter == NULL) + init_error_limit(cinfo); + cquantize->on_odd_row = FALSE; + } + + } + /* Zero the histogram or inverse color map, if necessary */ + if (cquantize->needs_zeroed) { + for (i = 0; i < HIST_C0_ELEMS; i++) { + jzero_far((void FAR *) histogram[i], + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = FALSE; + } +} + + +/* + * Switch to a new external colormap between output passes. + */ + +METHODDEF(void) +new_color_map_2_quant (j_decompress_ptr cinfo) +{ + my_cquantize_ptr2 cquantize = (my_cquantize_ptr2) cinfo->cquantize; + + /* Reset the inverse color map */ + cquantize->needs_zeroed = TRUE; +} + + +/* + * Module initialization routine for 2-pass color quantization. + */ + +GLOBAL(void) +jinit_2pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr2 cquantize; + int i; + + cquantize = (my_cquantize_ptr2) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer2)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_2_quant; + cquantize->pub.new_color_map = new_color_map_2_quant; + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Make sure jdmaster didn't give me a case I can't handle */ + if (cinfo->out_color_components != 3) + ERREXIT(cinfo, JERR_NOTIMPL); + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF(hist2d)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + cquantize->histogram[i] = (hist2d) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = TRUE; /* histogram is garbage now */ + + /* Allocate storage for the completed colormap, if required. + * We do this now since it is FAR storage and may affect + * the memory manager's space calculations. + */ + if (cinfo->enable_2pass_quant) { + /* Make sure color count is acceptable */ + int desired = cinfo->desired_number_of_colors; + /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ + if (desired < 8) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 8); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (desired > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + cquantize->sv_colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo,JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3); + cquantize->desired = desired; + } else + cquantize->sv_colormap = NULL; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + /* Allocate Floyd-Steinberg workspace if necessary. + * This isn't really needed until pass 2, but again it is FAR storage. + * Although we will cope with a later change in dither_mode, + * we do not promise to honor max_memory_to_use if dither_mode changes. + */ + if (cinfo->dither_mode == JDITHER_FS) { + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ((cinfo->output_width + 2) * (3 * SIZEOF(FSERROR)))); + /* Might as well create the error-limiting table too. */ + init_error_limit(cinfo); + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jutils.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jutils.c new file mode 100644 index 0000000..286cda2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jutils.c @@ -0,0 +1,179 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1996, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +#if 0 /* This table is not actually needed in v6a */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +#endif + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL(long) +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL(long) +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL(void) +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL(void) +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL(void) +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jversion.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jversion.h new file mode 100644 index 0000000..dadd453 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1998, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6b 27-Mar-1998" + +#define JCOPYRIGHT "Copyright (C) 1998, Thomas G. Lane" diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.c b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.c new file mode 100644 index 0000000..35e293e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.c @@ -0,0 +1,928 @@ +/* + * transupp.c + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains image transformation routines and other utility code + * used by the jpegtran sample application. These are NOT part of the core + * JPEG library. But we keep these routines separate from jpegtran.c to + * ease the task of maintaining jpegtran-like programs that have other user + * interfaces. + */ + +/* Although this file really shouldn't have access to the library internals, + * it's helpful to let it call jround_up() and jcopy_block_row(). + */ +#define JPEG_INTERNALS + +#include "jinclude.h" +#include "jpeglib.h" +#include "transupp.h" /* My own external interface */ + + +#if TRANSFORMS_SUPPORTED + +/* + * Lossless image transformation routines. These routines work on DCT + * coefficient arrays and thus do not require any lossy decompression + * or recompression of the image. + * Thanks to Guido Vollbeding for the initial design and code of this feature. + * + * Horizontal flipping is done in-place, using a single top-to-bottom + * pass through the virtual source array. It will thus be much the + * fastest option for images larger than main memory. + * + * The other routines require a set of destination virtual arrays, so they + * need twice as much memory as jpegtran normally does. The destination + * arrays are always written in normal scan order (top to bottom) because + * the virtual array manager expects this. The source arrays will be scanned + * in the corresponding order, which means multiple passes through the source + * arrays for most of the transforms. That could result in much thrashing + * if the image is larger than main memory. + * + * Some notes about the operating environment of the individual transform + * routines: + * 1. Both the source and destination virtual arrays are allocated from the + * source JPEG object, and therefore should be manipulated by calling the + * source's memory manager. + * 2. The destination's component count should be used. It may be smaller + * than the source's when forcing to grayscale. + * 3. Likewise the destination's sampling factors should be used. When + * forcing to grayscale the destination's sampling factors will be all 1, + * and we may as well take that as the effective iMCU size. + * 4. When "trim" is in effect, the destination's dimensions will be the + * trimmed values but the source's will be untrimmed. + * 5. All the routines assume that the source and destination buffers are + * padded out to a full iMCU boundary. This is true, although for the + * source buffer it is an undocumented property of jdcoefct.c. + * Notes 2,3,4 boil down to this: generally we should use the destination's + * dimensions and ignore the source's. + */ + + +LOCAL(void) +do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays) +/* Horizontal flip; done in-place, so no separate dest array is required */ +{ + JDIMENSION MCU_cols, comp_width, blk_x, blk_y; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JCOEFPTR ptr1, ptr2; + JCOEF temp1, temp2; + jpeg_component_info *compptr; + + /* Horizontal mirroring of DCT blocks is accomplished by swapping + * pairs of blocks in-place. Within a DCT block, we perform horizontal + * mirroring by changing the signs of odd-numbered columns. + * Partial iMCUs at the right edge are left untouched. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) { + ptr1 = buffer[offset_y][blk_x]; + ptr2 = buffer[offset_y][comp_width - blk_x - 1]; + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + temp1 = *ptr1; /* swap even column */ + temp2 = *ptr2; + *ptr1++ = temp2; + *ptr2++ = temp1; + temp1 = *ptr1; /* swap odd column with sign change */ + temp2 = *ptr2; + *ptr1++ = -temp2; + *ptr2++ = -temp1; + } + } + } + } + } +} + + +LOCAL(void) +do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Vertical flip */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* We output into a separate array because we can't touch different + * rows of the source virtual array simultaneously. Otherwise, this + * is a pretty straightforward analog of horizontal flip. + * Within a DCT block, vertical mirroring is done by changing the signs + * of odd-numbered rows. + * Partial iMCUs at the bottom edge are copied verbatim. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge blocks will be copied verbatim. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + /* copy even row */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + /* copy odd row with sign change */ + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Just copy row verbatim. */ + jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y], + compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transpose source into destination */ +{ + JDIMENSION dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Transposing pixels within a block just requires transposing the + * DCT coefficients. + * Partial iMCUs at the edges require no special treatment; we simply + * process all the available DCT blocks for every component. + */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } +} + + +LOCAL(void) +do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 90 degree rotation is equivalent to + * 1. Transposing the image; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) right edge properly. They just get transposed and + * not mirrored. + */ + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* Edge blocks are transposed but not mirrored. */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 270 degree rotation is equivalent to + * 1. Horizontal mirroring; + * 2. Transposing the image. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + /* Because of the horizontal mirror step, we can't process partial iMCUs + * at the (output) bottom edge properly. They just get transposed and + * not mirrored. + */ + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + if (dst_blk_y < comp_height) { + /* Block is within the mirrorable area. */ + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Edge blocks are transposed but not mirrored. */ + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } +} + + +LOCAL(void) +do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* 180 degree rotation is equivalent to + * 1. Vertical mirroring; + * 2. Horizontal mirroring. + * These two steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + if (dst_blk_y < comp_height) { + /* Row is within the vertically mirrorable area. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], + comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } else { + /* Bottom-edge rows are only mirrored horizontally. */ + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dst_blk_y < comp_height) { + /* Row is within the mirrorable area. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1]; + /* Process the blocks that can be mirrored both ways. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE; i += 2) { + /* For even row, negate every odd column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + /* For odd row, negate every even column. */ + for (j = 0; j < DCTSIZE; j += 2) { + *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = *src_ptr++; + } + } + } + /* Any remaining right-edge blocks are only mirrored vertically. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE; i += 2) { + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = *src_ptr++; + for (j = 0; j < DCTSIZE; j++) + *dst_ptr++ = - *src_ptr++; + } + } + } else { + /* Remaining rows are just mirrored horizontally. */ + dst_row_ptr = dst_buffer[offset_y]; + src_row_ptr = src_buffer[offset_y]; + /* Process the blocks that can be mirrored. */ + for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[comp_width - dst_blk_x - 1]; + for (i = 0; i < DCTSIZE2; i += 2) { + *dst_ptr++ = *src_ptr++; + *dst_ptr++ = - *src_ptr++; + } + } + /* Any remaining right-edge blocks are only copied. */ + for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_ptr = dst_row_ptr[dst_blk_x]; + src_ptr = src_row_ptr[dst_blk_x]; + for (i = 0; i < DCTSIZE2; i++) + *dst_ptr++ = *src_ptr++; + } + } + } + } + } +} + + +LOCAL(void) +do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Transverse transpose is equivalent to + * 1. 180 degree rotation; + * 2. Transposition; + * or + * 1. Horizontal mirroring; + * 2. Transposition; + * 3. Horizontal mirroring. + * These steps are merged into a single processing routine. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y; + int ci, i, j, offset_x, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE); + MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION) compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; + dst_blk_x += compptr->h_samp_factor) { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x, + (JDIMENSION) compptr->h_samp_factor, FALSE); + for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) { + if (dst_blk_y < comp_height) { + src_ptr = src_buffer[offset_x] + [comp_height - dst_blk_y - offset_y - 1]; + if (dst_blk_x < comp_width) { + /* Block is within the mirrorable area. */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + i++; + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } else { + /* Right-edge blocks are mirrored in y only */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) { + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + j++; + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } + } + } else { + src_ptr = src_buffer[offset_x][dst_blk_y + offset_y]; + if (dst_blk_x < comp_width) { + /* Bottom-edge blocks are mirrored in x only */ + dst_ptr = dst_buffer[offset_y] + [comp_width - dst_blk_x - offset_x - 1]; + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + i++; + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j]; + } + } else { + /* At lower right corner, just transpose, no mirroring */ + dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x]; + for (i = 0; i < DCTSIZE; i++) + for (j = 0; j < DCTSIZE; j++) + dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j]; + } + } + } + } + } + } + } +} + + +/* Request any required workspace. + * + * We allocate the workspace virtual arrays from the source decompression + * object, so that all the arrays (both the original data and the workspace) + * will be taken into account while making memory management decisions. + * Hence, this routine must be called after jpeg_read_header (which reads + * the image dimensions) and before jpeg_read_coefficients (which realizes + * the source's virtual arrays). + */ + +GLOBAL(void) +jtransform_request_workspace (j_decompress_ptr srcinfo, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *coef_arrays = NULL; + jpeg_component_info *compptr; + int ci; + + if (info->force_grayscale && + srcinfo->jpeg_color_space == JCS_YCbCr && + srcinfo->num_components == 3) { + /* We'll only process the first component */ + info->num_components = 1; + } else { + /* Process all the components */ + info->num_components = srcinfo->num_components; + } + + switch (info->transform) { + case JXFORM_NONE: + case JXFORM_FLIP_H: + /* Don't need a workspace array */ + break; + case JXFORM_FLIP_V: + case JXFORM_ROT_180: + /* Need workspace arrays having same dimensions as source image. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } + break; + case JXFORM_TRANSPOSE: + case JXFORM_TRANSVERSE: + case JXFORM_ROT_90: + case JXFORM_ROT_270: + /* Need workspace arrays having transposed dimensions. + * Note that we allocate arrays padded out to the next iMCU boundary, + * so that transform routines need not worry about missing edge blocks. + */ + coef_arrays = (jvirt_barray_ptr *) + (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE, + SIZEOF(jvirt_barray_ptr) * info->num_components); + for (ci = 0; ci < info->num_components; ci++) { + compptr = srcinfo->comp_info + ci; + coef_arrays[ci] = (*srcinfo->mem->request_virt_barray) + ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) compptr->h_samp_factor); + } + break; + } + info->workspace_coef_arrays = coef_arrays; +} + + +/* Transpose destination image parameters */ + +LOCAL(void) +transpose_critical_parameters (j_compress_ptr dstinfo) +{ + int tblno, i, j, ci, itemp; + jpeg_component_info *compptr; + JQUANT_TBL *qtblptr; + JDIMENSION dtemp; + UINT16 qtemp; + + /* Transpose basic image dimensions */ + dtemp = dstinfo->image_width; + dstinfo->image_width = dstinfo->image_height; + dstinfo->image_height = dtemp; + + /* Transpose sampling factors */ + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + itemp = compptr->h_samp_factor; + compptr->h_samp_factor = compptr->v_samp_factor; + compptr->v_samp_factor = itemp; + } + + /* Transpose quantization tables */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + qtblptr = dstinfo->quant_tbl_ptrs[tblno]; + if (qtblptr != NULL) { + for (i = 0; i < DCTSIZE; i++) { + for (j = 0; j < i; j++) { + qtemp = qtblptr->quantval[i*DCTSIZE+j]; + qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i]; + qtblptr->quantval[j*DCTSIZE+i] = qtemp; + } + } + } + } +} + + +/* Trim off any partial iMCUs on the indicated destination edge */ + +LOCAL(void) +trim_right_edge (j_compress_ptr dstinfo) +{ + int ci, max_h_samp_factor; + JDIMENSION MCU_cols; + + /* We have to compute max_h_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_h_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor; + max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor); + } + MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE); + if (MCU_cols > 0) /* can't trim to 0 pixels */ + dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE); +} + +LOCAL(void) +trim_bottom_edge (j_compress_ptr dstinfo) +{ + int ci, max_v_samp_factor; + JDIMENSION MCU_rows; + + /* We have to compute max_v_samp_factor ourselves, + * because it hasn't been set yet in the destination + * (and we don't want to use the source's value). + */ + max_v_samp_factor = 1; + for (ci = 0; ci < dstinfo->num_components; ci++) { + int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor; + max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor); + } + MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE); + if (MCU_rows > 0) /* can't trim to 0 pixels */ + dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE); +} + + +/* Adjust output image parameters as needed. + * + * This must be called after jpeg_copy_critical_parameters() + * and before jpeg_write_coefficients(). + * + * The return value is the set of virtual coefficient arrays to be written + * (either the ones allocated by jtransform_request_workspace, or the + * original source data arrays). The caller will need to pass this value + * to jpeg_write_coefficients(). + */ + +GLOBAL(jvirt_barray_ptr *) +jtransform_adjust_parameters (j_decompress_ptr, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + /* If force-to-grayscale is requested, adjust destination parameters */ + if (info->force_grayscale) { + /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed + * properly. Among other things, the target h_samp_factor & v_samp_factor + * will get set to 1, which typically won't match the source. + * In fact we do this even if the source is already grayscale; that + * provides an easy way of coercing a grayscale JPEG with funny sampling + * factors to the customary 1,1. (Some decoders fail on other factors.) + */ + if ((dstinfo->jpeg_color_space == JCS_YCbCr && + dstinfo->num_components == 3) || + (dstinfo->jpeg_color_space == JCS_GRAYSCALE && + dstinfo->num_components == 1)) { + /* We have to preserve the source's quantization table number. */ + int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no; + jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE); + dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no; + } else { + /* Sorry, can't do it */ + ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL); + } + } + + /* Correct the destination's image dimensions etc if necessary */ + switch (info->transform) { + case JXFORM_NONE: + /* Nothing to do */ + break; + case JXFORM_FLIP_H: + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_FLIP_V: + if (info->trim) + trim_bottom_edge(dstinfo); + break; + case JXFORM_TRANSPOSE: + transpose_critical_parameters(dstinfo); + /* transpose does NOT have to trim anything */ + break; + case JXFORM_TRANSVERSE: + transpose_critical_parameters(dstinfo); + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_90: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_right_edge(dstinfo); + break; + case JXFORM_ROT_180: + if (info->trim) { + trim_right_edge(dstinfo); + trim_bottom_edge(dstinfo); + } + break; + case JXFORM_ROT_270: + transpose_critical_parameters(dstinfo); + if (info->trim) + trim_bottom_edge(dstinfo); + break; + } + + /* Return the appropriate output data set */ + if (info->workspace_coef_arrays != NULL) + return info->workspace_coef_arrays; + return src_coef_arrays; +} + + +/* Execute the actual transformation, if any. + * + * This must be called *after* jpeg_write_coefficients, because it depends + * on jpeg_write_coefficients to have computed subsidiary values such as + * the per-component width and height fields in the destination object. + * + * Note that some transformations will modify the source data arrays! + */ + +GLOBAL(void) +jtransform_execute_transformation (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info) +{ + jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays; + + switch (info->transform) { + case JXFORM_NONE: + break; + case JXFORM_FLIP_H: + do_flip_h(srcinfo, dstinfo, src_coef_arrays); + break; + case JXFORM_FLIP_V: + do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSPOSE: + do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_TRANSVERSE: + do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_90: + do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_180: + do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + case JXFORM_ROT_270: + do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays); + break; + } +} + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* Setup decompression object to save desired markers in memory. + * This must be called before jpeg_read_header() to have the desired effect. + */ + +GLOBAL(void) +jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option) +{ +#ifdef SAVE_MARKERS_SUPPORTED + int m; + + /* Save comments except under NONE option */ + if (option != JCOPYOPT_NONE) { + jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF); + } + /* Save all types of APPn markers iff ALL option */ + if (option == JCOPYOPT_ALL) { + for (m = 0; m < 16; m++) + jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF); + } +#endif /* SAVE_MARKERS_SUPPORTED */ +} + +/* Copy markers saved in the given source object to the destination object. + * This should be called just after jpeg_start_compress() or + * jpeg_write_coefficients(). + * Note that those routines will have written the SOI, and also the + * JFIF APP0 or Adobe APP14 markers if selected. + */ + +GLOBAL(void) +jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION) +{ + jpeg_saved_marker_ptr marker; + + /* In the current implementation, we don't actually need to examine the + * option flag here; we just copy everything that got saved. + * But to avoid confusion, we do not output JFIF and Adobe APP14 markers + * if the encoder library already wrote one. + */ + for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) { + if (dstinfo->write_JFIF_header && + marker->marker == JPEG_APP0 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x4A && + GETJOCTET(marker->data[1]) == 0x46 && + GETJOCTET(marker->data[2]) == 0x49 && + GETJOCTET(marker->data[3]) == 0x46 && + GETJOCTET(marker->data[4]) == 0) + continue; /* reject duplicate JFIF */ + if (dstinfo->write_Adobe_marker && + marker->marker == JPEG_APP0+14 && + marker->data_length >= 5 && + GETJOCTET(marker->data[0]) == 0x41 && + GETJOCTET(marker->data[1]) == 0x64 && + GETJOCTET(marker->data[2]) == 0x6F && + GETJOCTET(marker->data[3]) == 0x62 && + GETJOCTET(marker->data[4]) == 0x65) + continue; /* reject duplicate Adobe */ +#ifdef NEED_FAR_POINTERS + /* We could use jpeg_write_marker if the data weren't FAR... */ + { + unsigned int i; + jpeg_write_m_header(dstinfo, marker->marker, marker->data_length); + for (i = 0; i < marker->data_length; i++) + jpeg_write_m_byte(dstinfo, marker->data[i]); + } +#else + jpeg_write_marker(dstinfo, marker->marker, + marker->data, marker->data_length); +#endif + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.h b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.h new file mode 100644 index 0000000..eb0b055 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/jpglib/transupp.h @@ -0,0 +1,135 @@ +/* + * transupp.h + * + * Copyright (C) 1997, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for image transformation routines and + * other utility code used by the jpegtran sample application. These are + * NOT part of the core JPEG library. But we keep these routines separate + * from jpegtran.c to ease the task of maintaining jpegtran-like programs + * that have other user interfaces. + * + * NOTE: all the routines declared here have very specific requirements + * about when they are to be executed during the reading and writing of the + * source and destination files. See the comments in transupp.c, or see + * jpegtran.c for an example of correct usage. + */ + +/* If you happen not to want the image transform support, disable it here */ +#ifndef TRANSFORMS_SUPPORTED +#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */ +#endif + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jtransform_request_workspace jTrRequest +#define jtransform_adjust_parameters jTrAdjust +#define jtransform_execute_transformation jTrExec +#define jcopy_markers_setup jCMrkSetup +#define jcopy_markers_execute jCMrkExec +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * Codes for supported types of image transformations. + */ + +typedef enum { + JXFORM_NONE, /* no transformation */ + JXFORM_FLIP_H, /* horizontal flip */ + JXFORM_FLIP_V, /* vertical flip */ + JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */ + JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ + JXFORM_ROT_90, /* 90-degree clockwise rotation */ + JXFORM_ROT_180, /* 180-degree rotation */ + JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ +} JXFORM_CODE; + +/* + * Although rotating and flipping data expressed as DCT coefficients is not + * hard, there is an asymmetry in the JPEG format specification for images + * whose dimensions aren't multiples of the iMCU size. The right and bottom + * image edges are padded out to the next iMCU boundary with junk data; but + * no padding is possible at the top and left edges. If we were to flip + * the whole image including the pad data, then pad garbage would become + * visible at the top and/or left, and real pixels would disappear into the + * pad margins --- perhaps permanently, since encoders & decoders may not + * bother to preserve DCT blocks that appear to be completely outside the + * nominal image area. So, we have to exclude any partial iMCUs from the + * basic transformation. + * + * Transpose is the only transformation that can handle partial iMCUs at the + * right and bottom edges completely cleanly. flip_h can flip partial iMCUs + * at the bottom, but leaves any partial iMCUs at the right edge untouched. + * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched. + * The other transforms are defined as combinations of these basic transforms + * and process edge blocks in a way that preserves the equivalence. + * + * The "trim" option causes untransformable partial iMCUs to be dropped; + * this is not strictly lossless, but it usually gives the best-looking + * result for odd-size images. Note that when this option is active, + * the expected mathematical equivalences between the transforms may not hold. + * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim + * followed by -rot 180 -trim trims both edges.) + * + * We also offer a "force to grayscale" option, which simply discards the + * chrominance channels of a YCbCr image. This is lossless in the sense that + * the luminance channel is preserved exactly. It's not the same kind of + * thing as the rotate/flip transformations, but it's convenient to handle it + * as part of this package, mainly because the transformation routines have to + * be aware of the option to know how many components to work on. + */ + +typedef struct { + /* Options: set by caller */ + JXFORM_CODE transform; /* image transform operator */ + boolean trim; /* if TRUE, trim partial MCUs as needed */ + boolean force_grayscale; /* if TRUE, convert color image to grayscale */ + + /* Internal workspace: caller should not touch these */ + int num_components; /* # of components in workspace */ + jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */ +} jpeg_transform_info; + + +#if TRANSFORMS_SUPPORTED + +/* Request any required workspace */ +EXTERN(void) jtransform_request_workspace + JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info)); +/* Adjust output image parameters */ +EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); +/* Execute the actual transformation, if any */ +EXTERN(void) jtransform_execute_transformation + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + jvirt_barray_ptr *src_coef_arrays, + jpeg_transform_info *info)); + +#endif /* TRANSFORMS_SUPPORTED */ + + +/* + * Support for copying optional markers from source to destination file. + */ + +typedef enum { + JCOPYOPT_NONE, /* copy no optional markers */ + JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */ + JCOPYOPT_ALL /* copy all optional markers */ +} JCOPY_OPTION; + +#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */ + +/* Setup decompression object to save desired markers in memory */ +EXTERN(void) jcopy_markers_setup + JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option)); +/* Copy markers saved in the given source object to the destination object */ +EXTERN(void) jcopy_markers_execute + JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JCOPY_OPTION option)); diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp new file mode 100644 index 0000000..2c9066f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp @@ -0,0 +1,444 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if (JUCE_MAC || JUCE_IOS) && USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER + Image juce_loadWithCoreImage (InputStream& input); +#else + +//============================================================================== +class GIFLoader +{ +public: + GIFLoader (InputStream& in) + : input (in), + dataBlockIsZero (false), fresh (false), finished (false), + currentBit (0), lastBit (0), lastByteIndex (0), + codeSize (0), setCodeSize (0), maxCode (0), maxCodeSize (0), + firstcode (0), oldcode (0), clearCode (0), endCode (0) + { + int imageWidth, imageHeight; + if (! getSizeFromHeader (imageWidth, imageHeight)) + return; + + uint8 buf [16]; + if (in.read (buf, 3) != 3) + return; + + int numColours = 2 << (buf[0] & 7); + int transparent = -1; + + if ((buf[0] & 0x80) != 0) + readPalette (numColours); + + for (;;) + { + if (input.read (buf, 1) != 1 || buf[0] == ';') + break; + + if (buf[0] == '!') + { + if (readExtension (transparent)) + continue; + + break; + } + + if (buf[0] != ',') + continue; + + if (input.read (buf, 9) == 9) + { + imageWidth = (int) ByteOrder::littleEndianShort (buf + 4); + imageHeight = (int) ByteOrder::littleEndianShort (buf + 6); + + numColours = 2 << (buf[8] & 7); + + if ((buf[8] & 0x80) != 0) + if (! readPalette (numColours)) + break; + + image = Image (transparent >= 0 ? Image::ARGB : Image::RGB, + imageWidth, imageHeight, transparent >= 0); + + image.getProperties()->set ("originalImageHadAlpha", transparent >= 0); + + readImage ((buf[8] & 0x40) != 0, transparent); + } + + break; + } + } + + Image image; + +private: + InputStream& input; + uint8 buffer [260]; + PixelARGB palette [256]; + bool dataBlockIsZero, fresh, finished; + int currentBit, lastBit, lastByteIndex; + int codeSize, setCodeSize; + int maxCode, maxCodeSize; + int firstcode, oldcode; + int clearCode, endCode; + enum { maxGifCode = 1 << 12 }; + int table [2] [maxGifCode]; + int stack [2 * maxGifCode]; + int* sp; + + bool getSizeFromHeader (int& w, int& h) + { + char b[6]; + + if (input.read (b, 6) == 6 + && (strncmp ("GIF87a", b, 6) == 0 + || strncmp ("GIF89a", b, 6) == 0)) + { + if (input.read (b, 4) == 4) + { + w = (int) ByteOrder::littleEndianShort (b); + h = (int) ByteOrder::littleEndianShort (b + 2); + return w > 0 && h > 0; + } + } + + return false; + } + + bool readPalette (const int numCols) + { + for (int i = 0; i < numCols; ++i) + { + uint8 rgb[4]; + input.read (rgb, 3); + + palette[i].setARGB (0xff, rgb[0], rgb[1], rgb[2]); + palette[i].premultiply(); + } + + return true; + } + + int readDataBlock (uint8* const dest) + { + uint8 n; + if (input.read (&n, 1) == 1) + { + dataBlockIsZero = (n == 0); + + if (dataBlockIsZero || (input.read (dest, n) == n)) + return n; + } + + return -1; + } + + int readExtension (int& transparent) + { + uint8 type; + if (input.read (&type, 1) != 1) + return false; + + uint8 b [260]; + int n = 0; + + if (type == 0xf9) + { + n = readDataBlock (b); + if (n < 0) + return 1; + + if ((b[0] & 1) != 0) + transparent = b[3]; + } + + do + { + n = readDataBlock (b); + } + while (n > 0); + + return n >= 0; + } + + void clearTable() + { + int i; + for (i = 0; i < clearCode; ++i) + { + table[0][i] = 0; + table[1][i] = i; + } + + for (; i < maxGifCode; ++i) + { + table[0][i] = 0; + table[1][i] = 0; + } + } + + void initialise (const int inputCodeSize) + { + setCodeSize = inputCodeSize; + codeSize = setCodeSize + 1; + clearCode = 1 << setCodeSize; + endCode = clearCode + 1; + maxCodeSize = 2 * clearCode; + maxCode = clearCode + 2; + + getCode (0, true); + + fresh = true; + clearTable(); + sp = stack; + } + + int readLZWByte() + { + if (fresh) + { + fresh = false; + + for (;;) + { + firstcode = oldcode = getCode (codeSize, false); + + if (firstcode != clearCode) + return firstcode; + } + } + + if (sp > stack) + return *--sp; + + int code; + + while ((code = getCode (codeSize, false)) >= 0) + { + if (code == clearCode) + { + clearTable(); + codeSize = setCodeSize + 1; + maxCodeSize = 2 * clearCode; + maxCode = clearCode + 2; + sp = stack; + firstcode = oldcode = getCode (codeSize, false); + return firstcode; + } + else if (code == endCode) + { + if (dataBlockIsZero) + return -2; + + uint8 buf [260]; + int n; + + while ((n = readDataBlock (buf)) > 0) + {} + + if (n != 0) + return -2; + } + + const int incode = code; + + if (code >= maxCode) + { + *sp++ = firstcode; + code = oldcode; + } + + while (code >= clearCode) + { + *sp++ = table[1][code]; + if (code == table[0][code]) + return -2; + + code = table[0][code]; + } + + *sp++ = firstcode = table[1][code]; + + if ((code = maxCode) < maxGifCode) + { + table[0][code] = oldcode; + table[1][code] = firstcode; + ++maxCode; + + if (maxCode >= maxCodeSize && maxCodeSize < maxGifCode) + { + maxCodeSize <<= 1; + ++codeSize; + } + } + + oldcode = incode; + + if (sp > stack) + return *--sp; + } + + return code; + } + + int getCode (const int codeSize_, const bool shouldInitialise) + { + if (shouldInitialise) + { + currentBit = 0; + lastBit = 0; + finished = false; + return 0; + } + + if ((currentBit + codeSize_) >= lastBit) + { + if (finished) + return -1; + + buffer[0] = buffer [lastByteIndex - 2]; + buffer[1] = buffer [lastByteIndex - 1]; + + const int n = readDataBlock (buffer + 2); + + if (n == 0) + finished = true; + + lastByteIndex = 2 + n; + currentBit = (currentBit - lastBit) + 16; + lastBit = (2 + n) * 8 ; + } + + int result = 0; + int i = currentBit; + + for (int j = 0; j < codeSize_; ++j) + { + result |= ((buffer[i >> 3] & (1 << (i & 7))) != 0) << j; + ++i; + } + + currentBit += codeSize_; + return result; + } + + bool readImage (const int interlace, const int transparent) + { + uint8 c; + if (input.read (&c, 1) != 1) + return false; + + initialise (c); + + if (transparent >= 0) + palette [transparent].setARGB (0, 0, 0, 0); + + int xpos = 0, ypos = 0, yStep = 8, pass = 0; + + const Image::BitmapData destData (image, Image::BitmapData::writeOnly); + uint8* p = destData.getPixelPointer (0, 0); + const bool hasAlpha = image.hasAlphaChannel(); + + for (;;) + { + const int index = readLZWByte(); + if (index < 0) + break; + + if (hasAlpha) + ((PixelARGB*) p)->set (palette [index]); + else + ((PixelRGB*) p)->set (palette [index]); + + p += destData.pixelStride; + + if (++xpos == destData.width) + { + xpos = 0; + + if (interlace) + { + ypos += yStep; + + while (ypos >= destData.height) + { + switch (++pass) + { + case 1: ypos = 4; yStep = 8; break; + case 2: ypos = 2; yStep = 4; break; + case 3: ypos = 1; yStep = 2; break; + default: return true; + } + } + } + else + { + if (++ypos >= destData.height) + break; + } + + p = destData.getPixelPointer (xpos, ypos); + } + } + + return true; + } + + JUCE_DECLARE_NON_COPYABLE (GIFLoader) +}; + +#endif + +//============================================================================== +GIFImageFormat::GIFImageFormat() {} +GIFImageFormat::~GIFImageFormat() {} + +String GIFImageFormat::getFormatName() { return "GIF"; } +bool GIFImageFormat::usesFileExtension (const File& f) { return f.hasFileExtension ("gif"); } + +bool GIFImageFormat::canUnderstand (InputStream& in) +{ + char header [4]; + + return (in.read (header, sizeof (header)) == sizeof (header)) + && header[0] == 'G' + && header[1] == 'I' + && header[2] == 'F'; +} + +Image GIFImageFormat::decodeImage (InputStream& in) +{ + #if (JUCE_MAC || JUCE_IOS) && USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER + return juce_loadWithCoreImage (in); + #else + const ScopedPointer loader (new GIFLoader (in)); + return loader->image; + #endif +} + +bool GIFImageFormat::writeImageToStream (const Image& /*sourceImage*/, OutputStream& /*destStream*/) +{ + jassertfalse; // writing isn't implemented for GIFs! + return false; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp new file mode 100644 index 0000000..0f56e32 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp @@ -0,0 +1,430 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4365) +#endif + +namespace jpeglibNamespace +{ +#if JUCE_INCLUDE_JPEGLIB_CODE || ! defined (JUCE_INCLUDE_JPEGLIB_CODE) + #if JUCE_MINGW + typedef unsigned char boolean; + #endif + + #if JUCE_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wconversion" + #pragma clang diagnostic ignored "-Wdeprecated-register" + #endif + + #define JPEG_INTERNALS + #undef FAR + #include "jpglib/jpeglib.h" + + #include "jpglib/jcapimin.c" + #include "jpglib/jcapistd.c" + #include "jpglib/jccoefct.c" + #include "jpglib/jccolor.c" + #undef FIX + #include "jpglib/jcdctmgr.c" + #undef CONST_BITS + #include "jpglib/jchuff.c" + #undef emit_byte + #include "jpglib/jcinit.c" + #include "jpglib/jcmainct.c" + #include "jpglib/jcmarker.c" + #include "jpglib/jcmaster.c" + #include "jpglib/jcomapi.c" + #include "jpglib/jcparam.c" + #include "jpglib/jcphuff.c" + #include "jpglib/jcprepct.c" + #include "jpglib/jcsample.c" + #include "jpglib/jctrans.c" + #include "jpglib/jdapistd.c" + #include "jpglib/jdapimin.c" + #include "jpglib/jdatasrc.c" + #include "jpglib/jdcoefct.c" + #undef FIX + #include "jpglib/jdcolor.c" + #undef FIX + #include "jpglib/jddctmgr.c" + #undef CONST_BITS + #undef ASSIGN_STATE + #include "jpglib/jdhuff.c" + #include "jpglib/jdinput.c" + #include "jpglib/jdmainct.c" + #include "jpglib/jdmarker.c" + #include "jpglib/jdmaster.c" + #undef FIX + #include "jpglib/jdmerge.c" + #undef ASSIGN_STATE + #include "jpglib/jdphuff.c" + #include "jpglib/jdpostct.c" + #undef FIX + #include "jpglib/jdsample.c" + #include "jpglib/jdtrans.c" + #include "jpglib/jfdctflt.c" + #include "jpglib/jfdctint.c" + #undef CONST_BITS + #undef MULTIPLY + #undef FIX_0_541196100 + #include "jpglib/jfdctfst.c" + #undef FIX_0_541196100 + #include "jpglib/jidctflt.c" + #undef CONST_BITS + #undef FIX_1_847759065 + #undef MULTIPLY + #undef DEQUANTIZE + #undef DESCALE + #include "jpglib/jidctfst.c" + #undef CONST_BITS + #undef FIX_1_847759065 + #undef MULTIPLY + #undef DEQUANTIZE + #include "jpglib/jidctint.c" + #include "jpglib/jidctred.c" + #include "jpglib/jmemmgr.c" + #include "jpglib/jmemnobs.c" + #include "jpglib/jquant1.c" + #include "jpglib/jquant2.c" + #include "jpglib/jutils.c" + #include "jpglib/transupp.c" + + #if JUCE_CLANG + #pragma clang diagnostic pop + #endif +#else + #define JPEG_INTERNALS + #undef FAR + #include +#endif +} + +#undef max +#undef min + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +namespace JPEGHelpers +{ + using namespace jpeglibNamespace; + + #if ! JUCE_MSVC + using jpeglibNamespace::boolean; + #endif + + struct JPEGDecodingFailure {}; + + static void fatalErrorHandler (j_common_ptr) { throw JPEGDecodingFailure(); } + static void silentErrorCallback1 (j_common_ptr) {} + static void silentErrorCallback2 (j_common_ptr, int) {} + static void silentErrorCallback3 (j_common_ptr, char*) {} + + static void setupSilentErrorHandler (struct jpeg_error_mgr& err) + { + zerostruct (err); + + err.error_exit = fatalErrorHandler; + err.emit_message = silentErrorCallback2; + err.output_message = silentErrorCallback1; + err.format_message = silentErrorCallback3; + err.reset_error_mgr = silentErrorCallback1; + } + + //============================================================================== + #if ! JUCE_USING_COREIMAGE_LOADER + static void dummyCallback1 (j_decompress_ptr) {} + + static void jpegSkip (j_decompress_ptr decompStruct, long num) + { + decompStruct->src->next_input_byte += num; + + num = jmin (num, (long) decompStruct->src->bytes_in_buffer); + decompStruct->src->bytes_in_buffer -= (size_t) num; + } + + static boolean jpegFill (j_decompress_ptr) + { + return 0; + } + #endif + + //============================================================================== + const int jpegBufferSize = 512; + + struct JuceJpegDest : public jpeg_destination_mgr + { + OutputStream* output; + char* buffer; + }; + + static void jpegWriteInit (j_compress_ptr) {} + + static void jpegWriteTerminate (j_compress_ptr cinfo) + { + JuceJpegDest* const dest = static_cast (cinfo->dest); + + const size_t numToWrite = jpegBufferSize - dest->free_in_buffer; + dest->output->write (dest->buffer, numToWrite); + } + + static boolean jpegWriteFlush (j_compress_ptr cinfo) + { + JuceJpegDest* const dest = static_cast (cinfo->dest); + + const int numToWrite = jpegBufferSize; + + dest->next_output_byte = reinterpret_cast (dest->buffer); + dest->free_in_buffer = jpegBufferSize; + + return (boolean) dest->output->write (dest->buffer, (size_t) numToWrite); + } +} + +//============================================================================== +JPEGImageFormat::JPEGImageFormat() + : quality (-1.0f) +{ +} + +JPEGImageFormat::~JPEGImageFormat() {} + +void JPEGImageFormat::setQuality (const float newQuality) +{ + quality = newQuality; +} + +String JPEGImageFormat::getFormatName() { return "JPEG"; } +bool JPEGImageFormat::usesFileExtension (const File& f) { return f.hasFileExtension ("jpeg;jpg"); } + +bool JPEGImageFormat::canUnderstand (InputStream& in) +{ + const int bytesNeeded = 10; + uint8 header [bytesNeeded]; + + return in.read (header, bytesNeeded) == bytesNeeded + && header[0] == 0xff + && header[1] == 0xd8 + && header[2] == 0xff; +} + +#if JUCE_USING_COREIMAGE_LOADER + Image juce_loadWithCoreImage (InputStream& input); +#endif + +Image JPEGImageFormat::decodeImage (InputStream& in) +{ +#if JUCE_USING_COREIMAGE_LOADER + return juce_loadWithCoreImage (in); +#else + using namespace jpeglibNamespace; + using namespace JPEGHelpers; + + MemoryOutputStream mb; + mb << in; + + Image image; + + if (mb.getDataSize() > 16) + { + struct jpeg_decompress_struct jpegDecompStruct; + + struct jpeg_error_mgr jerr; + setupSilentErrorHandler (jerr); + jpegDecompStruct.err = &jerr; + + jpeg_create_decompress (&jpegDecompStruct); + + jpegDecompStruct.src = (jpeg_source_mgr*)(jpegDecompStruct.mem->alloc_small) + ((j_common_ptr)(&jpegDecompStruct), JPOOL_PERMANENT, sizeof (jpeg_source_mgr)); + + jpegDecompStruct.src->init_source = dummyCallback1; + jpegDecompStruct.src->fill_input_buffer = jpegFill; + jpegDecompStruct.src->skip_input_data = jpegSkip; + jpegDecompStruct.src->resync_to_restart = jpeg_resync_to_restart; + jpegDecompStruct.src->term_source = dummyCallback1; + + jpegDecompStruct.src->next_input_byte = static_cast (mb.getData()); + jpegDecompStruct.src->bytes_in_buffer = mb.getDataSize(); + + try + { + jpeg_read_header (&jpegDecompStruct, TRUE); + + jpeg_calc_output_dimensions (&jpegDecompStruct); + + const int width = (int) jpegDecompStruct.output_width; + const int height = (int) jpegDecompStruct.output_height; + + jpegDecompStruct.out_color_space = JCS_RGB; + + JSAMPARRAY buffer + = (*jpegDecompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegDecompStruct, + JPOOL_IMAGE, + (JDIMENSION) width * 3, 1); + + if (jpeg_start_decompress (&jpegDecompStruct)) + { + image = Image (Image::RGB, width, height, false); + image.getProperties()->set ("originalImageHadAlpha", false); + const bool hasAlphaChan = image.hasAlphaChannel(); // (the native image creator may not give back what we expect) + + const Image::BitmapData destData (image, Image::BitmapData::writeOnly); + + for (int y = 0; y < height; ++y) + { + jpeg_read_scanlines (&jpegDecompStruct, buffer, 1); + + const uint8* src = *buffer; + uint8* dest = destData.getLinePointer (y); + + if (hasAlphaChan) + { + for (int i = width; --i >= 0;) + { + ((PixelARGB*) dest)->setARGB (0xff, src[0], src[1], src[2]); + ((PixelARGB*) dest)->premultiply(); + dest += destData.pixelStride; + src += 3; + } + } + else + { + for (int i = width; --i >= 0;) + { + ((PixelRGB*) dest)->setARGB (0xff, src[0], src[1], src[2]); + dest += destData.pixelStride; + src += 3; + } + } + } + + jpeg_finish_decompress (&jpegDecompStruct); + + in.setPosition (((char*) jpegDecompStruct.src->next_input_byte) - (char*) mb.getData()); + } + + jpeg_destroy_decompress (&jpegDecompStruct); + } + catch (...) + {} + } + + return image; +#endif +} + +bool JPEGImageFormat::writeImageToStream (const Image& image, OutputStream& out) +{ + using namespace jpeglibNamespace; + using namespace JPEGHelpers; + + jpeg_compress_struct jpegCompStruct; + zerostruct (jpegCompStruct); + jpeg_create_compress (&jpegCompStruct); + + struct jpeg_error_mgr jerr; + setupSilentErrorHandler (jerr); + jpegCompStruct.err = &jerr; + + JuceJpegDest dest; + jpegCompStruct.dest = &dest; + + dest.output = &out; + HeapBlock tempBuffer (jpegBufferSize); + dest.buffer = tempBuffer; + dest.next_output_byte = (JOCTET*) dest.buffer; + dest.free_in_buffer = jpegBufferSize; + dest.init_destination = jpegWriteInit; + dest.empty_output_buffer = jpegWriteFlush; + dest.term_destination = jpegWriteTerminate; + + jpegCompStruct.image_width = (JDIMENSION) image.getWidth(); + jpegCompStruct.image_height = (JDIMENSION) image.getHeight(); + jpegCompStruct.input_components = 3; + jpegCompStruct.in_color_space = JCS_RGB; + jpegCompStruct.write_JFIF_header = 1; + + jpegCompStruct.X_density = 72; + jpegCompStruct.Y_density = 72; + + jpeg_set_defaults (&jpegCompStruct); + + jpegCompStruct.dct_method = JDCT_FLOAT; + jpegCompStruct.optimize_coding = 1; + + if (quality < 0.0f) + quality = 0.85f; + + jpeg_set_quality (&jpegCompStruct, jlimit (0, 100, roundToInt (quality * 100.0f)), TRUE); + + jpeg_start_compress (&jpegCompStruct, TRUE); + + const int strideBytes = (int) (jpegCompStruct.image_width * (unsigned int) jpegCompStruct.input_components); + + JSAMPARRAY buffer = (*jpegCompStruct.mem->alloc_sarray) ((j_common_ptr) &jpegCompStruct, + JPOOL_IMAGE, (JDIMENSION) strideBytes, 1); + + const Image::BitmapData srcData (image, Image::BitmapData::readOnly); + + while (jpegCompStruct.next_scanline < jpegCompStruct.image_height) + { + uint8* dst = *buffer; + + if (srcData.pixelFormat == Image::RGB) + { + const uint8* src = srcData.getLinePointer ((int) jpegCompStruct.next_scanline); + + for (int i = srcData.width; --i >= 0;) + { + *dst++ = ((const PixelRGB*) src)->getRed(); + *dst++ = ((const PixelRGB*) src)->getGreen(); + *dst++ = ((const PixelRGB*) src)->getBlue(); + src += srcData.pixelStride; + } + } + else + { + for (int x = 0; x < srcData.width; ++x) + { + const Colour pixel (srcData.getPixelColour (x, (int) jpegCompStruct.next_scanline)); + *dst++ = pixel.getRed(); + *dst++ = pixel.getGreen(); + *dst++ = pixel.getBlue(); + } + } + + jpeg_write_scanlines (&jpegCompStruct, buffer, 1); + } + + jpeg_finish_compress (&jpegCompStruct); + jpeg_destroy_compress (&jpegCompStruct); + + return true; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp new file mode 100644 index 0000000..636b293 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp @@ -0,0 +1,546 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4390 4611 4365 4267) + #ifdef __INTEL_COMPILER + #pragma warning (disable: 2544 2545) + #endif +#endif + +namespace zlibNamespace +{ +#if JUCE_INCLUDE_ZLIB_CODE + #undef OS_CODE + #undef fdopen + #include "../../juce_core/zip/zlib/zlib.h" + #undef OS_CODE +#else + #include JUCE_ZLIB_INCLUDE_PATH +#endif +} + +namespace pnglibNamespace +{ + using namespace zlibNamespace; + +#if JUCE_INCLUDE_PNGLIB_CODE || ! defined (JUCE_INCLUDE_PNGLIB_CODE) + + #if _MSC_VER != 1310 + using std::calloc; // (causes conflict in VS.NET 2003) + using std::malloc; + using std::free; + #endif + + #if JUCE_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wsign-conversion" + #endif + + using std::abs; + #define NO_DUMMY_DECL + #define PNGLCONF_H 1 + + #if JUCE_ANDROID + #define PNG_ARM_NEON_SUPPORTED + #endif + + #define PNG_16BIT_SUPPORTED + #define PNG_ALIGNED_MEMORY_SUPPORTED + #define PNG_BENIGN_ERRORS_SUPPORTED + #define PNG_BENIGN_READ_ERRORS_SUPPORTED + #define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED + #define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + #define PNG_COLORSPACE_SUPPORTED + #define PNG_CONSOLE_IO_SUPPORTED + #define PNG_EASY_ACCESS_SUPPORTED + #define PNG_FIXED_POINT_SUPPORTED + #define PNG_FLOATING_ARITHMETIC_SUPPORTED + #define PNG_FLOATING_POINT_SUPPORTED + #define PNG_FORMAT_AFIRST_SUPPORTED + #define PNG_FORMAT_BGR_SUPPORTED + #define PNG_GAMMA_SUPPORTED + #define PNG_GET_PALETTE_MAX_SUPPORTED + #define PNG_HANDLE_AS_UNKNOWN_SUPPORTED + #define PNG_INCH_CONVERSIONS_SUPPORTED + #define PNG_INFO_IMAGE_SUPPORTED + #define PNG_IO_STATE_SUPPORTED + #define PNG_MNG_FEATURES_SUPPORTED + #define PNG_POINTER_INDEXING_SUPPORTED + #define PNG_PROGRESSIVE_READ_SUPPORTED + #define PNG_READ_16BIT_SUPPORTED + #define PNG_READ_ALPHA_MODE_SUPPORTED + #define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED + #define PNG_READ_BACKGROUND_SUPPORTED + #define PNG_READ_BGR_SUPPORTED + #define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + #define PNG_READ_COMPOSITE_NODIV_SUPPORTED + #define PNG_READ_COMPRESSED_TEXT_SUPPORTED + #define PNG_READ_EXPAND_16_SUPPORTED + #define PNG_READ_EXPAND_SUPPORTED + #define PNG_READ_FILLER_SUPPORTED + #define PNG_READ_GAMMA_SUPPORTED + #define PNG_READ_GET_PALETTE_MAX_SUPPORTED + #define PNG_READ_GRAY_TO_RGB_SUPPORTED + #define PNG_READ_INTERLACING_SUPPORTED + #define PNG_READ_INT_FUNCTIONS_SUPPORTED + #define PNG_READ_INVERT_ALPHA_SUPPORTED + #define PNG_READ_INVERT_SUPPORTED + #define PNG_READ_OPT_PLTE_SUPPORTED + #define PNG_READ_PACKSWAP_SUPPORTED + #define PNG_READ_PACK_SUPPORTED + #define PNG_READ_QUANTIZE_SUPPORTED + #define PNG_READ_RGB_TO_GRAY_SUPPORTED + #define PNG_READ_SCALE_16_TO_8_SUPPORTED + #define PNG_READ_SHIFT_SUPPORTED + #define PNG_READ_STRIP_16_TO_8_SUPPORTED + #define PNG_READ_STRIP_ALPHA_SUPPORTED + #define PNG_READ_SUPPORTED + #define PNG_READ_SWAP_ALPHA_SUPPORTED + #define PNG_READ_SWAP_SUPPORTED + #define PNG_READ_TEXT_SUPPORTED + #define PNG_READ_TRANSFORMS_SUPPORTED + #define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_READ_USER_CHUNKS_SUPPORTED + #define PNG_READ_USER_TRANSFORM_SUPPORTED + #define PNG_READ_bKGD_SUPPORTED + #define PNG_READ_cHRM_SUPPORTED + #define PNG_READ_gAMA_SUPPORTED + #define PNG_READ_hIST_SUPPORTED + #define PNG_READ_iCCP_SUPPORTED + #define PNG_READ_iTXt_SUPPORTED + #define PNG_READ_oFFs_SUPPORTED + #define PNG_READ_pCAL_SUPPORTED + #define PNG_READ_pHYs_SUPPORTED + #define PNG_READ_sBIT_SUPPORTED + #define PNG_READ_sCAL_SUPPORTED + #define PNG_READ_sPLT_SUPPORTED + #define PNG_READ_sRGB_SUPPORTED + #define PNG_READ_tEXt_SUPPORTED + #define PNG_READ_tIME_SUPPORTED + #define PNG_READ_tRNS_SUPPORTED + #define PNG_READ_zTXt_SUPPORTED + #define PNG_SAVE_INT_32_SUPPORTED + #define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_SEQUENTIAL_READ_SUPPORTED + #define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED + #define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED + #define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_SET_USER_LIMITS_SUPPORTED + #define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED + #define PNG_SIMPLIFIED_READ_BGR_SUPPORTED + #define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED + #define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED + #define PNG_STDIO_SUPPORTED + #define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_TEXT_SUPPORTED + #define PNG_TIME_RFC1123_SUPPORTED + #define PNG_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_USER_CHUNKS_SUPPORTED + #define PNG_USER_LIMITS_SUPPORTED + #define PNG_USER_TRANSFORM_INFO_SUPPORTED + #define PNG_USER_TRANSFORM_PTR_SUPPORTED + #define PNG_WARNINGS_SUPPORTED + #define PNG_WRITE_16BIT_SUPPORTED + #define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED + #define PNG_WRITE_BGR_SUPPORTED + #define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED + #define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED + #define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + #define PNG_WRITE_FILLER_SUPPORTED + #define PNG_WRITE_FILTER_SUPPORTED + #define PNG_WRITE_FLUSH_SUPPORTED + #define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED + #define PNG_WRITE_INTERLACING_SUPPORTED + #define PNG_WRITE_INT_FUNCTIONS_SUPPORTED + #define PNG_WRITE_INVERT_ALPHA_SUPPORTED + #define PNG_WRITE_INVERT_SUPPORTED + #define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + #define PNG_WRITE_PACKSWAP_SUPPORTED + #define PNG_WRITE_PACK_SUPPORTED + #define PNG_WRITE_SHIFT_SUPPORTED + #define PNG_WRITE_SUPPORTED + #define PNG_WRITE_SWAP_ALPHA_SUPPORTED + #define PNG_WRITE_SWAP_SUPPORTED + #define PNG_WRITE_TEXT_SUPPORTED + #define PNG_WRITE_TRANSFORMS_SUPPORTED + #define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + #define PNG_WRITE_USER_TRANSFORM_SUPPORTED + #define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + #define PNG_WRITE_bKGD_SUPPORTED + #define PNG_WRITE_cHRM_SUPPORTED + #define PNG_WRITE_gAMA_SUPPORTED + #define PNG_WRITE_hIST_SUPPORTED + #define PNG_WRITE_iCCP_SUPPORTED + #define PNG_WRITE_iTXt_SUPPORTED + #define PNG_WRITE_oFFs_SUPPORTED + #define PNG_WRITE_pCAL_SUPPORTED + #define PNG_WRITE_pHYs_SUPPORTED + #define PNG_WRITE_sBIT_SUPPORTED + #define PNG_WRITE_sCAL_SUPPORTED + #define PNG_WRITE_sPLT_SUPPORTED + #define PNG_WRITE_sRGB_SUPPORTED + #define PNG_WRITE_tEXt_SUPPORTED + #define PNG_WRITE_tIME_SUPPORTED + #define PNG_WRITE_tRNS_SUPPORTED + #define PNG_WRITE_zTXt_SUPPORTED + #define PNG_bKGD_SUPPORTED + #define PNG_cHRM_SUPPORTED + #define PNG_gAMA_SUPPORTED + #define PNG_hIST_SUPPORTED + #define PNG_iCCP_SUPPORTED + #define PNG_iTXt_SUPPORTED + #define PNG_oFFs_SUPPORTED + #define PNG_pCAL_SUPPORTED + #define PNG_pHYs_SUPPORTED + #define PNG_sBIT_SUPPORTED + #define PNG_sCAL_SUPPORTED + #define PNG_sPLT_SUPPORTED + #define PNG_sRGB_SUPPORTED + #define PNG_tEXt_SUPPORTED + #define PNG_tIME_SUPPORTED + #define PNG_tRNS_SUPPORTED + #define PNG_zTXt_SUPPORTED + + #define PNG_STRING_COPYRIGHT ""; + #define PNG_STRING_NEWLINE "\n" + #define PNG_LITERAL_SHARP 0x23 + #define PNG_LITERAL_LEFT_SQUARE_BRACKET 0x5b + #define PNG_LITERAL_RIGHT_SQUARE_BRACKET 0x5d + + #define PNG_API_RULE 0 + #define PNG_CALLOC_SUPPORTED + #define PNG_COST_SHIFT 3 + #define PNG_DEFAULT_READ_MACROS 1 + #define PNG_GAMMA_THRESHOLD_FIXED 5000 + #define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE + #define PNG_INFLATE_BUF_SIZE 1024 + #define PNG_MAX_GAMMA_8 11 + #define PNG_QUANTIZE_BLUE_BITS 5 + #define PNG_QUANTIZE_GREEN_BITS 5 + #define PNG_QUANTIZE_RED_BITS 5 + #define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) + #define PNG_TEXT_Z_DEFAULT_STRATEGY 0 + #define PNG_WEIGHT_SHIFT 8 + #define PNG_ZBUF_SIZE 8192 + #define PNG_Z_DEFAULT_COMPRESSION (-1) + #define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 + #define PNG_Z_DEFAULT_STRATEGY 1 + #define PNG_sCAL_PRECISION 5 + #define PNG_sRGB_PROFILE_CHECKS 2 + + #define png_debug(a, b) + #define png_debug1(a, b, c) + #define png_debug2(a, b, c, d) + + #include "pnglib/png.h" + #include "pnglib/pngconf.h" + + #define PNG_NO_EXTERN + #include "pnglib/png.c" + #include "pnglib/pngerror.c" + #include "pnglib/pngget.c" + #include "pnglib/pngmem.c" + #include "pnglib/pngread.c" + #include "pnglib/pngpread.c" + #include "pnglib/pngrio.c" + #include "pnglib/pngrtran.c" + #include "pnglib/pngrutil.c" + #include "pnglib/pngset.c" + #include "pnglib/pngtrans.c" + #include "pnglib/pngwio.c" + #include "pnglib/pngwrite.c" + #include "pnglib/pngwtran.c" + #include "pnglib/pngwutil.c" + + #if JUCE_CLANG + #pragma clang diagnostic pop + #endif +#else + extern "C" + { + #include + #include + } +#endif +} + +#undef max +#undef min +#undef fdopen + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +//============================================================================== +namespace PNGHelpers +{ + using namespace pnglibNamespace; + + static void JUCE_CDECL writeDataCallback (png_structp png, png_bytep data, png_size_t length) + { + static_cast (png_get_io_ptr (png))->write (data, length); + } + + #if ! JUCE_USING_COREIMAGE_LOADER + static void JUCE_CDECL readCallback (png_structp png, png_bytep data, png_size_t length) + { + static_cast (png_get_io_ptr (png))->read (data, (int) length); + } + + struct PNGErrorStruct {}; + + static void JUCE_CDECL errorCallback (png_structp, png_const_charp) + { + throw PNGErrorStruct(); + } + + static void JUCE_CDECL warningCallback (png_structp, png_const_charp) {} + #endif +} + +//============================================================================== +PNGImageFormat::PNGImageFormat() {} +PNGImageFormat::~PNGImageFormat() {} + +String PNGImageFormat::getFormatName() { return "PNG"; } +bool PNGImageFormat::usesFileExtension (const File& f) { return f.hasFileExtension ("png"); } + +bool PNGImageFormat::canUnderstand (InputStream& in) +{ + const int bytesNeeded = 4; + char header [bytesNeeded]; + + return in.read (header, bytesNeeded) == bytesNeeded + && header[1] == 'P' + && header[2] == 'N' + && header[3] == 'G'; +} + +#if JUCE_USING_COREIMAGE_LOADER + Image juce_loadWithCoreImage (InputStream& input); +#endif + +Image PNGImageFormat::decodeImage (InputStream& in) +{ +#if JUCE_USING_COREIMAGE_LOADER + return juce_loadWithCoreImage (in); +#else + using namespace pnglibNamespace; + Image image; + + if (png_structp pngReadStruct = png_create_read_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0)) + { + try + { + png_infop pngInfoStruct = png_create_info_struct (pngReadStruct); + + if (pngInfoStruct == nullptr) + { + png_destroy_read_struct (&pngReadStruct, 0, 0); + return Image::null; + } + + png_set_error_fn (pngReadStruct, 0, PNGHelpers::errorCallback, PNGHelpers::warningCallback); + + // read the header.. + png_set_read_fn (pngReadStruct, &in, PNGHelpers::readCallback); + + png_uint_32 width = 0, height = 0; + int bitDepth = 0, colorType = 0, interlaceType; + + png_read_info (pngReadStruct, pngInfoStruct); + + png_get_IHDR (pngReadStruct, pngInfoStruct, + &width, &height, + &bitDepth, &colorType, + &interlaceType, 0, 0); + + if (bitDepth == 16) + png_set_strip_16 (pngReadStruct); + + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_expand (pngReadStruct); + + if (bitDepth < 8) + png_set_expand (pngReadStruct); + + if (png_get_valid (pngReadStruct, pngInfoStruct, PNG_INFO_tRNS)) + png_set_expand (pngReadStruct); + + if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb (pngReadStruct); + + png_set_add_alpha (pngReadStruct, 0xff, PNG_FILLER_AFTER); + + bool hasAlphaChan = (colorType & PNG_COLOR_MASK_ALPHA) != 0 + || pngInfoStruct->num_trans > 0; + + // Load the image into a temp buffer in the pnglib format.. + const size_t lineStride = width * 4; + HeapBlock tempBuffer (height * lineStride); + + HeapBlock rows (height); + for (size_t y = 0; y < height; ++y) + rows[y] = (png_bytep) (tempBuffer + lineStride * y); + + try + { + png_read_image (pngReadStruct, rows); + png_read_end (pngReadStruct, pngInfoStruct); + } + catch (PNGHelpers::PNGErrorStruct&) + {} + + png_destroy_read_struct (&pngReadStruct, &pngInfoStruct, 0); + + // now convert the data to a juce image format.. + image = Image (hasAlphaChan ? Image::ARGB : Image::RGB, + (int) width, (int) height, hasAlphaChan); + + image.getProperties()->set ("originalImageHadAlpha", image.hasAlphaChannel()); + hasAlphaChan = image.hasAlphaChannel(); // (the native image creator may not give back what we expect) + + const Image::BitmapData destData (image, Image::BitmapData::writeOnly); + + for (int y = 0; y < (int) height; ++y) + { + const uint8* src = rows[y]; + uint8* dest = destData.getLinePointer (y); + + if (hasAlphaChan) + { + for (int i = (int) width; --i >= 0;) + { + ((PixelARGB*) dest)->setARGB (src[3], src[0], src[1], src[2]); + ((PixelARGB*) dest)->premultiply(); + dest += destData.pixelStride; + src += 4; + } + } + else + { + for (int i = (int) width; --i >= 0;) + { + ((PixelRGB*) dest)->setARGB (0, src[0], src[1], src[2]); + dest += destData.pixelStride; + src += 4; + } + } + } + } + catch (PNGHelpers::PNGErrorStruct&) + {} + } + + return image; +#endif +} + +bool PNGImageFormat::writeImageToStream (const Image& image, OutputStream& out) +{ + using namespace pnglibNamespace; + const int width = image.getWidth(); + const int height = image.getHeight(); + + png_structp pngWriteStruct = png_create_write_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0); + + if (pngWriteStruct == nullptr) + return false; + + png_infop pngInfoStruct = png_create_info_struct (pngWriteStruct); + + if (pngInfoStruct == nullptr) + { + png_destroy_write_struct (&pngWriteStruct, (png_infopp) nullptr); + return false; + } + + png_set_write_fn (pngWriteStruct, &out, PNGHelpers::writeDataCallback, 0); + + png_set_IHDR (pngWriteStruct, pngInfoStruct, (png_uint_32) width, (png_uint_32) height, 8, + image.hasAlphaChannel() ? PNG_COLOR_TYPE_RGB_ALPHA + : PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + HeapBlock rowData ((size_t) width * 4); + + png_color_8 sig_bit; + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + sig_bit.gray = 0; + sig_bit.alpha = 8; + png_set_sBIT (pngWriteStruct, pngInfoStruct, &sig_bit); + + png_write_info (pngWriteStruct, pngInfoStruct); + + png_set_shift (pngWriteStruct, &sig_bit); + png_set_packing (pngWriteStruct); + + const Image::BitmapData srcData (image, Image::BitmapData::readOnly); + + for (int y = 0; y < height; ++y) + { + uint8* dst = rowData; + const uint8* src = srcData.getLinePointer (y); + + if (image.hasAlphaChannel()) + { + for (int i = width; --i >= 0;) + { + PixelARGB p (*(const PixelARGB*) src); + p.unpremultiply(); + + *dst++ = p.getRed(); + *dst++ = p.getGreen(); + *dst++ = p.getBlue(); + *dst++ = p.getAlpha(); + src += srcData.pixelStride; + } + } + else + { + for (int i = width; --i >= 0;) + { + *dst++ = ((const PixelRGB*) src)->getRed(); + *dst++ = ((const PixelRGB*) src)->getGreen(); + *dst++ = ((const PixelRGB*) src)->getBlue(); + src += srcData.pixelStride; + } + } + + png_bytep rowPtr = rowData; + png_write_rows (pngWriteStruct, &rowPtr, 1); + } + + png_write_end (pngWriteStruct, pngInfoStruct); + png_destroy_write_struct (&pngWriteStruct, &pngInfoStruct); + + return true; +} diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/LICENSE b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/LICENSE new file mode 100644 index 0000000..462c70d --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/LICENSE @@ -0,0 +1,109 @@ + +This copy of the libpng notices is provided for your convenience. In case of +any discrepancy between this copy and the notices in the file png.h that is +included in the libpng distribution, the latter shall prevail. + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + +If you modify libpng you may insert additional notices immediately following +this sentence. + +libpng versions 1.2.6, August 15, 2004, through 1.2.21, October 4, 2007, are +Copyright (c) 2004, 2006-2007 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-1.2.5 +with the following individual added to the list of Contributing Authors + + Cosmin Truta + +libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are +Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-1.0.6 +with the following individuals added to the list of Contributing Authors + + Simon-Pierre Cadieux + Eric S. Raymond + Gilles Vollant + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of the + library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is with + the user. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998, 1999 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-0.96, +with the following individuals added to the list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996, 1997 Andreas Dilger +Distributed according to the same disclaimer and license as libpng-0.88, +with the following individuals added to the list of Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing Authors +and Group 42, Inc. disclaim all warranties, expressed or implied, +including, without limitation, the warranties of merchantability and of +fitness for any purpose. The Contributing Authors and Group 42, Inc. +assume no liability for direct, indirect, incidental, special, exemplary, +or consequential damages, which may result from the use of the PNG +Reference Library, even if advised of the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + +1. The origin of this source code must not be misrepresented. + +2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + +3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, without +fee, and encourage the use of this source code as a component to +supporting the PNG file format in commercial products. If you use this +source code in a product, acknowledgment is not required but would be +appreciated. + + +A "png_get_copyright" function is available, for convenient use in "about" +boxes and the like: + + printf("%s",png_get_copyright(NULL)); + +Also, the PNG logo (in PNG format, of course) is supplied in the +files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). + +Libpng is OSI Certified Open Source Software. OSI Certified Open Source is a +certification mark of the Open Source Initiative. + +Glenn Randers-Pehrson +glennrp at users.sourceforge.net +October 4, 2007 diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/libpng_readme.txt b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/libpng_readme.txt new file mode 100644 index 0000000..75a4595 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/libpng_readme.txt @@ -0,0 +1,2 @@ + +These files are from the libpng library - http://www.libpng.org/ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.c new file mode 100644 index 0000000..67e7e2e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.c @@ -0,0 +1,4295 @@ + +/* png.c - location for general purpose libpng functions + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +/* Generate a compiler error if there is an old png.h in the search path. */ +typedef png_libpng_version_1_6_1 Your_png_h_is_not_version_1_6_1; + +/* Tells libpng that we have already handled the first "num_bytes" bytes + * of the PNG file signature. If the PNG data is embedded into another + * stream we can set num_bytes = 8 so that libpng will not attempt to read + * or write any of the magic bytes before it starts on the IHDR. + */ + +#ifdef PNG_READ_SUPPORTED +void PNGAPI +png_set_sig_bytes(png_structrp png_ptr, int num_bytes) +{ + png_debug(1, "in png_set_sig_bytes"); + + if (png_ptr == NULL) + return; + + if (num_bytes > 8) + png_error(png_ptr, "Too many bytes for PNG signature"); + + png_ptr->sig_bytes = (png_byte)(num_bytes < 0 ? 0 : num_bytes); +} + +/* Checks whether the supplied bytes match the PNG signature. We allow + * checking less than the full 8-byte signature so that those apps that + * already read the first few bytes of a file to determine the file type + * can simply check the remaining bytes for extra assurance. Returns + * an integer less than, equal to, or greater than zero if sig is found, + * respectively, to be less than, to match, or be greater than the correct + * PNG signature (this is the same behavior as strcmp, memcmp, etc). + */ +int PNGAPI +png_sig_cmp(png_const_bytep sig, png_size_t start, png_size_t num_to_check) +{ + png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + + if (num_to_check > 8) + num_to_check = 8; + + else if (num_to_check < 1) + return (-1); + + if (start > 7) + return (-1); + + if (start + num_to_check > 8) + num_to_check = 8 - start; + + return ((int)(memcmp(&sig[start], &png_signature[start], num_to_check))); +} + +#endif /* PNG_READ_SUPPORTED */ + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Function to allocate memory for zlib */ +PNG_FUNCTION(voidpf /* PRIVATE */, +png_zalloc,(voidpf png_ptr, uInt items, uInt size),PNG_ALLOCATED) +{ + png_alloc_size_t num_bytes = size; + + if (png_ptr == NULL) + return NULL; + + if (items >= (~(png_alloc_size_t)0)/size) + { + png_warning (png_voidcast(png_structrp, png_ptr), + "Potential overflow in png_zalloc()"); + return NULL; + } + + num_bytes *= items; + return png_malloc_warn(png_voidcast(png_structrp, png_ptr), num_bytes); +} + +/* Function to free memory for zlib */ +void /* PRIVATE */ +png_zfree(voidpf png_ptr, voidpf ptr) +{ + png_free(png_voidcast(png_const_structrp,png_ptr), ptr); +} + +/* Reset the CRC variable to 32 bits of 1's. Care must be taken + * in case CRC is > 32 bits to leave the top bits 0. + */ +void /* PRIVATE */ +png_reset_crc(png_structrp png_ptr) +{ + /* The cast is safe because the crc is a 32 bit value. */ + png_ptr->crc = (png_uint_32)crc32(0, Z_NULL, 0); +} + +/* Calculate the CRC over a section of data. We can only pass as + * much data to this routine as the largest single buffer size. We + * also check that this data will actually be used before going to the + * trouble of calculating it. + */ +void /* PRIVATE */ +png_calculate_crc(png_structrp png_ptr, png_const_bytep ptr, png_size_t length) +{ + int need_crc = 1; + + if (PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name)) + { + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) == + (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN)) + need_crc = 0; + } + + else /* critical */ + { + if (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE) + need_crc = 0; + } + + /* 'uLong' is defined in zlib.h as unsigned long; this means that on some + * systems it is a 64 bit value. crc32, however, returns 32 bits so the + * following cast is safe. 'uInt' may be no more than 16 bits, so it is + * necessary to perform a loop here. + */ + if (need_crc && length > 0) + { + uLong crc = png_ptr->crc; /* Should never issue a warning */ + + do + { + uInt safe_length = (uInt)length; + if (safe_length == 0) + safe_length = (uInt)-1; /* evil, but safe */ + + crc = crc32(crc, ptr, safe_length); + + /* The following should never issue compiler warnings; if they do the + * target system has characteristics that will probably violate other + * assumptions within the libpng code. + */ + ptr += safe_length; + length -= safe_length; + } + while (length > 0); + + /* And the following is always safe because the crc is only 32 bits. */ + png_ptr->crc = (png_uint_32)crc; + } +} + +/* Check a user supplied version number, called from both read and write + * functions that create a png_struct. + */ +int +png_user_version_check(png_structrp png_ptr, png_const_charp user_png_ver) +{ + if (user_png_ver) + { + int i = 0; + + do + { + if (user_png_ver[i] != png_libpng_ver[i]) + png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; + } while (png_libpng_ver[i++]); + } + + else + png_ptr->flags |= PNG_FLAG_LIBRARY_MISMATCH; + + if (png_ptr->flags & PNG_FLAG_LIBRARY_MISMATCH) + { + /* Libpng 0.90 and later are binary incompatible with libpng 0.89, so + * we must recompile any applications that use any older library version. + * For versions after libpng 1.0, we will be compatible, so we need + * only check the first and third digits (note that when we reach version + * 1.10 we will need to check the fourth symbol, namely user_png_ver[3]). + */ + if (user_png_ver == NULL || user_png_ver[0] != png_libpng_ver[0] || + (user_png_ver[0] == '1' && (user_png_ver[2] != png_libpng_ver[2] || + user_png_ver[3] != png_libpng_ver[3])) || + (user_png_ver[0] == '0' && user_png_ver[2] < '9')) + { +#ifdef PNG_WARNINGS_SUPPORTED + size_t pos = 0; + char m[128]; + + pos = png_safecat(m, (sizeof m), pos, + "Application built with libpng-"); + pos = png_safecat(m, (sizeof m), pos, user_png_ver); + pos = png_safecat(m, (sizeof m), pos, " but running with "); + png_safecat(m, (sizeof m), pos, png_libpng_ver); + + png_warning(png_ptr, m); +#endif + +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + png_ptr->flags = 0; +#endif + + return 0; + } + } + + /* Success return. */ + return 1; +} + +/* Generic function to create a png_struct for either read or write - this + * contains the common initialization. + */ +PNG_FUNCTION(png_structp /* PRIVATE */, +png_create_png_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp, + png_malloc_ptr, png_free_ptr),PNG_ALLOCATED) +{ + png_struct create_struct; +# ifdef PNG_SETJMP_SUPPORTED + jmp_buf create_jmp_buf; +# endif + + /* This temporary stack-allocated structure is used to provide a place to + * build enough context to allow the user provided memory allocator (if any) + * to be called. + */ + memset(&create_struct, 0, (sizeof create_struct)); + + /* Added at libpng-1.2.6 */ +# ifdef PNG_USER_LIMITS_SUPPORTED + create_struct.user_width_max = PNG_USER_WIDTH_MAX; + create_struct.user_height_max = PNG_USER_HEIGHT_MAX; + +# ifdef PNG_USER_CHUNK_CACHE_MAX + /* Added at libpng-1.2.43 and 1.4.0 */ + create_struct.user_chunk_cache_max = PNG_USER_CHUNK_CACHE_MAX; +# endif + +# ifdef PNG_USER_CHUNK_MALLOC_MAX + /* Added at libpng-1.2.43 and 1.4.1, required only for read but exists + * in png_struct regardless. + */ + create_struct.user_chunk_malloc_max = PNG_USER_CHUNK_MALLOC_MAX; +# endif +# endif + + /* The following two API calls simply set fields in png_struct, so it is safe + * to do them now even though error handling is not yet set up. + */ +# ifdef PNG_USER_MEM_SUPPORTED + png_set_mem_fn(&create_struct, mem_ptr, malloc_fn, free_fn); +# endif + + /* (*error_fn) can return control to the caller after the error_ptr is set, + * this will result in a memory leak unless the error_fn does something + * extremely sophisticated. The design lacks merit but is implicit in the + * API. + */ + png_set_error_fn(&create_struct, error_ptr, error_fn, warn_fn); + +# ifdef PNG_SETJMP_SUPPORTED + if (!setjmp(create_jmp_buf)) + { + /* Temporarily fake out the longjmp information until we have + * successfully completed this function. This only works if we have + * setjmp() support compiled in, but it is safe - this stuff should + * never happen. + */ + create_struct.jmp_buf_ptr = &create_jmp_buf; + create_struct.jmp_buf_size = 0; /*stack allocation*/ + create_struct.longjmp_fn = longjmp; +# else + { +# endif + /* Call the general version checker (shared with read and write code): + */ + if (png_user_version_check(&create_struct, user_png_ver)) + { + png_structrp png_ptr = png_voidcast(png_structrp, + png_malloc_warn(&create_struct, (sizeof *png_ptr))); + + if (png_ptr != NULL) + { + /* png_ptr->zstream holds a back-pointer to the png_struct, so + * this can only be done now: + */ + create_struct.zstream.zalloc = png_zalloc; + create_struct.zstream.zfree = png_zfree; + create_struct.zstream.opaque = png_ptr; + +# ifdef PNG_SETJMP_SUPPORTED + /* Eliminate the local error handling: */ + create_struct.jmp_buf_ptr = NULL; + create_struct.jmp_buf_size = 0; + create_struct.longjmp_fn = 0; +# endif + + *png_ptr = create_struct; + + /* This is the successful return point */ + return png_ptr; + } + } + } + + /* A longjmp because of a bug in the application storage allocator or a + * simple failure to allocate the png_struct. + */ + return NULL; +} + +/* Allocate the memory for an info_struct for the application. */ +PNG_FUNCTION(png_infop,PNGAPI +png_create_info_struct,(png_const_structrp png_ptr),PNG_ALLOCATED) +{ + png_inforp info_ptr; + + png_debug(1, "in png_create_info_struct"); + + if (png_ptr == NULL) + return NULL; + + /* Use the internal API that does not (or at least should not) error out, so + * that this call always returns ok. The application typically sets up the + * error handling *after* creating the info_struct because this is the way it + * has always been done in 'example.c'. + */ + info_ptr = png_voidcast(png_inforp, png_malloc_base(png_ptr, + (sizeof *info_ptr))); + + if (info_ptr != NULL) + memset(info_ptr, 0, (sizeof *info_ptr)); + + return info_ptr; +} + +/* This function frees the memory associated with a single info struct. + * Normally, one would use either png_destroy_read_struct() or + * png_destroy_write_struct() to free an info struct, but this may be + * useful for some applications. From libpng 1.6.0 this function is also used + * internally to implement the png_info release part of the 'struct' destroy + * APIs. This ensures that all possible approaches free the same data (all of + * it). + */ +void PNGAPI +png_destroy_info_struct(png_const_structrp png_ptr, png_infopp info_ptr_ptr) +{ + png_inforp info_ptr = NULL; + + png_debug(1, "in png_destroy_info_struct"); + + if (png_ptr == NULL) + return; + + if (info_ptr_ptr != NULL) + info_ptr = *info_ptr_ptr; + + if (info_ptr != NULL) + { + /* Do this first in case of an error below; if the app implements its own + * memory management this can lead to png_free calling png_error, which + * will abort this routine and return control to the app error handler. + * An infinite loop may result if it then tries to free the same info + * ptr. + */ + *info_ptr_ptr = NULL; + + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + memset(info_ptr, 0, (sizeof *info_ptr)); + png_free(png_ptr, info_ptr); + } +} + +/* Initialize the info structure. This is now an internal function (0.89) + * and applications using it are urged to use png_create_info_struct() + * instead. Use deprecated in 1.6.0, internal use removed (used internally it + * is just a memset). + * + * NOTE: it is almost inconceivable that this API is used because it bypasses + * the user-memory mechanism and the user error handling/warning mechanisms in + * those cases where it does anything other than a memset. + */ +PNG_FUNCTION(void,PNGAPI +png_info_init_3,(png_infopp ptr_ptr, png_size_t png_info_struct_size), + PNG_DEPRECATED) +{ + png_inforp info_ptr = *ptr_ptr; + + png_debug(1, "in png_info_init_3"); + + if (info_ptr == NULL) + return; + + if ((sizeof (png_info)) > png_info_struct_size) + { + *ptr_ptr = NULL; + /* The following line is why this API should not be used: */ + free(info_ptr); + info_ptr = png_voidcast(png_inforp, png_malloc_base(NULL, + (sizeof *info_ptr))); + *ptr_ptr = info_ptr; + } + + /* Set everything to 0 */ + memset(info_ptr, 0, (sizeof *info_ptr)); +} + +/* The following API is not called internally */ +void PNGAPI +png_data_freer(png_const_structrp png_ptr, png_inforp info_ptr, + int freer, png_uint_32 mask) +{ + png_debug(1, "in png_data_freer"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (freer == PNG_DESTROY_WILL_FREE_DATA) + info_ptr->free_me |= mask; + + else if (freer == PNG_USER_WILL_FREE_DATA) + info_ptr->free_me &= ~mask; + + else + png_error(png_ptr, "Unknown freer parameter in png_data_freer"); +} + +void PNGAPI +png_free_data(png_const_structrp png_ptr, png_inforp info_ptr, png_uint_32 mask, + int num) +{ + png_debug(1, "in png_free_data"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + +#ifdef PNG_TEXT_SUPPORTED + /* Free text item num or (if num == -1) all text items */ + if ((mask & PNG_FREE_TEXT) & info_ptr->free_me) + { + if (num != -1) + { + if (info_ptr->text && info_ptr->text[num].key) + { + png_free(png_ptr, info_ptr->text[num].key); + info_ptr->text[num].key = NULL; + } + } + + else + { + int i; + for (i = 0; i < info_ptr->num_text; i++) + png_free_data(png_ptr, info_ptr, PNG_FREE_TEXT, i); + png_free(png_ptr, info_ptr->text); + info_ptr->text = NULL; + info_ptr->num_text=0; + } + } +#endif + +#ifdef PNG_tRNS_SUPPORTED + /* Free any tRNS entry */ + if ((mask & PNG_FREE_TRNS) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->trans_alpha); + info_ptr->trans_alpha = NULL; + info_ptr->valid &= ~PNG_INFO_tRNS; + } +#endif + +#ifdef PNG_sCAL_SUPPORTED + /* Free any sCAL entry */ + if ((mask & PNG_FREE_SCAL) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->scal_s_width); + png_free(png_ptr, info_ptr->scal_s_height); + info_ptr->scal_s_width = NULL; + info_ptr->scal_s_height = NULL; + info_ptr->valid &= ~PNG_INFO_sCAL; + } +#endif + +#ifdef PNG_pCAL_SUPPORTED + /* Free any pCAL entry */ + if ((mask & PNG_FREE_PCAL) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->pcal_purpose); + png_free(png_ptr, info_ptr->pcal_units); + info_ptr->pcal_purpose = NULL; + info_ptr->pcal_units = NULL; + if (info_ptr->pcal_params != NULL) + { + unsigned int i; + for (i = 0; i < info_ptr->pcal_nparams; i++) + { + png_free(png_ptr, info_ptr->pcal_params[i]); + info_ptr->pcal_params[i] = NULL; + } + png_free(png_ptr, info_ptr->pcal_params); + info_ptr->pcal_params = NULL; + } + info_ptr->valid &= ~PNG_INFO_pCAL; + } +#endif + +#ifdef PNG_iCCP_SUPPORTED + /* Free any profile entry */ + if ((mask & PNG_FREE_ICCP) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->iccp_name); + png_free(png_ptr, info_ptr->iccp_profile); + info_ptr->iccp_name = NULL; + info_ptr->iccp_profile = NULL; + info_ptr->valid &= ~PNG_INFO_iCCP; + } +#endif + +#ifdef PNG_sPLT_SUPPORTED + /* Free a given sPLT entry, or (if num == -1) all sPLT entries */ + if ((mask & PNG_FREE_SPLT) & info_ptr->free_me) + { + if (num != -1) + { + if (info_ptr->splt_palettes) + { + png_free(png_ptr, info_ptr->splt_palettes[num].name); + png_free(png_ptr, info_ptr->splt_palettes[num].entries); + info_ptr->splt_palettes[num].name = NULL; + info_ptr->splt_palettes[num].entries = NULL; + } + } + + else + { + if (info_ptr->splt_palettes_num) + { + int i; + for (i = 0; i < info_ptr->splt_palettes_num; i++) + png_free_data(png_ptr, info_ptr, PNG_FREE_SPLT, (int)i); + + png_free(png_ptr, info_ptr->splt_palettes); + info_ptr->splt_palettes = NULL; + info_ptr->splt_palettes_num = 0; + } + info_ptr->valid &= ~PNG_INFO_sPLT; + } + } +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + if ((mask & PNG_FREE_UNKN) & info_ptr->free_me) + { + if (num != -1) + { + if (info_ptr->unknown_chunks) + { + png_free(png_ptr, info_ptr->unknown_chunks[num].data); + info_ptr->unknown_chunks[num].data = NULL; + } + } + + else + { + int i; + + if (info_ptr->unknown_chunks_num) + { + for (i = 0; i < info_ptr->unknown_chunks_num; i++) + png_free_data(png_ptr, info_ptr, PNG_FREE_UNKN, (int)i); + + png_free(png_ptr, info_ptr->unknown_chunks); + info_ptr->unknown_chunks = NULL; + info_ptr->unknown_chunks_num = 0; + } + } + } +#endif + +#ifdef PNG_hIST_SUPPORTED + /* Free any hIST entry */ + if ((mask & PNG_FREE_HIST) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->hist); + info_ptr->hist = NULL; + info_ptr->valid &= ~PNG_INFO_hIST; + } +#endif + + /* Free any PLTE entry that was internally allocated */ + if ((mask & PNG_FREE_PLTE) & info_ptr->free_me) + { + png_free(png_ptr, info_ptr->palette); + info_ptr->palette = NULL; + info_ptr->valid &= ~PNG_INFO_PLTE; + info_ptr->num_palette = 0; + } + +#ifdef PNG_INFO_IMAGE_SUPPORTED + /* Free any image bits attached to the info structure */ + if ((mask & PNG_FREE_ROWS) & info_ptr->free_me) + { + if (info_ptr->row_pointers) + { + png_uint_32 row; + for (row = 0; row < info_ptr->height; row++) + { + png_free(png_ptr, info_ptr->row_pointers[row]); + info_ptr->row_pointers[row] = NULL; + } + png_free(png_ptr, info_ptr->row_pointers); + info_ptr->row_pointers = NULL; + } + info_ptr->valid &= ~PNG_INFO_IDAT; + } +#endif + + if (num != -1) + mask &= ~PNG_FREE_MUL; + + info_ptr->free_me &= ~mask; +} +#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */ + +/* This function returns a pointer to the io_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy() or png_read_destroy() are called. + */ +png_voidp PNGAPI +png_get_io_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return (png_ptr->io_ptr); +} + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +# ifdef PNG_STDIO_SUPPORTED +/* Initialize the default input/output functions for the PNG file. If you + * use your own read or write routines, you can call either png_set_read_fn() + * or png_set_write_fn() instead of png_init_io(). If you have defined + * PNG_NO_STDIO or otherwise disabled PNG_STDIO_SUPPORTED, you must use a + * function of your own because "FILE *" isn't necessarily available. + */ +void PNGAPI +png_init_io(png_structrp png_ptr, png_FILE_p fp) +{ + png_debug(1, "in png_init_io"); + + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = (png_voidp)fp; +} +# endif + +#ifdef PNG_SAVE_INT_32_SUPPORTED +/* The png_save_int_32 function assumes integers are stored in two's + * complement format. If this isn't the case, then this routine needs to + * be modified to write data in two's complement format. Note that, + * the following works correctly even if png_int_32 has more than 32 bits + * (compare the more complex code required on read for sign extension.) + */ +void PNGAPI +png_save_int_32(png_bytep buf, png_int_32 i) +{ + buf[0] = (png_byte)((i >> 24) & 0xff); + buf[1] = (png_byte)((i >> 16) & 0xff); + buf[2] = (png_byte)((i >> 8) & 0xff); + buf[3] = (png_byte)(i & 0xff); +} +#endif + +# ifdef PNG_TIME_RFC1123_SUPPORTED +/* Convert the supplied time into an RFC 1123 string suitable for use in + * a "Creation Time" or other text-based time string. + */ +int PNGAPI +png_convert_to_rfc1123_buffer(char out[29], png_const_timep ptime) +{ + static PNG_CONST char short_months[12][4] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + if (out == NULL) + return 0; + + if (ptime->year > 9999 /* RFC1123 limitation */ || + ptime->month == 0 || ptime->month > 12 || + ptime->day == 0 || ptime->day > 31 || + ptime->hour > 23 || ptime->minute > 59 || + ptime->second > 60) + return 0; + + { + size_t pos = 0; + char number_buf[5]; /* enough for a four-digit year */ + +# define APPEND_STRING(string) pos = png_safecat(out, 29, pos, (string)) +# define APPEND_NUMBER(format, value)\ + APPEND_STRING(PNG_FORMAT_NUMBER(number_buf, format, (value))) +# define APPEND(ch) if (pos < 28) out[pos++] = (ch) + + APPEND_NUMBER(PNG_NUMBER_FORMAT_u, (unsigned)ptime->day); + APPEND(' '); + APPEND_STRING(short_months[(ptime->month - 1)]); + APPEND(' '); + APPEND_NUMBER(PNG_NUMBER_FORMAT_u, ptime->year); + APPEND(' '); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->hour); + APPEND(':'); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->minute); + APPEND(':'); + APPEND_NUMBER(PNG_NUMBER_FORMAT_02u, (unsigned)ptime->second); + APPEND_STRING(" +0000"); /* This reliably terminates the buffer */ + +# undef APPEND +# undef APPEND_NUMBER +# undef APPEND_STRING + } + + return 1; +} + +# if PNG_LIBPNG_VER < 10700 +/* To do: remove the following from libpng-1.7 */ +/* Original API that uses a private buffer in png_struct. + * Deprecated because it causes png_struct to carry a spurious temporary + * buffer (png_struct::time_buffer), better to have the caller pass this in. + */ +png_const_charp PNGAPI +png_convert_to_rfc1123(png_structrp png_ptr, png_const_timep ptime) +{ + if (png_ptr != NULL) + { + /* The only failure above if png_ptr != NULL is from an invalid ptime */ + if (!png_convert_to_rfc1123_buffer(png_ptr->time_buffer, ptime)) + png_warning(png_ptr, "Ignoring invalid time value"); + + else + return png_ptr->time_buffer; + } + + return NULL; +} +# endif +# endif /* PNG_TIME_RFC1123_SUPPORTED */ + +#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */ + +png_const_charp PNGAPI +png_get_copyright(png_const_structrp png_ptr) +{ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ +#ifdef PNG_STRING_COPYRIGHT + return PNG_STRING_COPYRIGHT +#else +# ifdef __STDC__ + return PNG_STRING_NEWLINE \ + "libpng version 1.6.1 - March 28, 2013" PNG_STRING_NEWLINE \ + "Copyright (c) 1998-2013 Glenn Randers-Pehrson" PNG_STRING_NEWLINE \ + "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \ + "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \ + PNG_STRING_NEWLINE; +# else + return "libpng version 1.6.1 - March 28, 2013\ + Copyright (c) 1998-2013 Glenn Randers-Pehrson\ + Copyright (c) 1996-1997 Andreas Dilger\ + Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc."; +# endif +#endif +} + +/* The following return the library version as a short string in the + * format 1.0.0 through 99.99.99zz. To get the version of *.h files + * used with your application, print out PNG_LIBPNG_VER_STRING, which + * is defined in png.h. + * Note: now there is no difference between png_get_libpng_ver() and + * png_get_header_ver(). Due to the version_nn_nn_nn typedef guard, + * it is guaranteed that png.c uses the correct version of png.h. + */ +png_const_charp PNGAPI +png_get_libpng_ver(png_const_structrp png_ptr) +{ + /* Version of *.c files used when building libpng */ + return png_get_header_ver(png_ptr); +} + +png_const_charp PNGAPI +png_get_header_ver(png_const_structrp png_ptr) +{ + /* Version of *.h files used when building libpng */ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ + return PNG_LIBPNG_VER_STRING; +} + +png_const_charp PNGAPI +png_get_header_version(png_const_structrp png_ptr) +{ + /* Returns longer string containing both version and date */ + PNG_UNUSED(png_ptr) /* Silence compiler warning about unused png_ptr */ +#ifdef __STDC__ + return PNG_HEADER_VERSION_STRING +# ifndef PNG_READ_SUPPORTED + " (NO READ SUPPORT)" +# endif + PNG_STRING_NEWLINE; +#else + return PNG_HEADER_VERSION_STRING; +#endif +} + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +int PNGAPI +png_handle_as_unknown(png_const_structrp png_ptr, png_const_bytep chunk_name) +{ + /* Check chunk_name and return "keep" value if it's on the list, else 0 */ + png_const_bytep p, p_end; + + if (png_ptr == NULL || chunk_name == NULL || png_ptr->num_chunk_list == 0) + return PNG_HANDLE_CHUNK_AS_DEFAULT; + + p_end = png_ptr->chunk_list; + p = p_end + png_ptr->num_chunk_list*5; /* beyond end */ + + /* The code is the fifth byte after each four byte string. Historically this + * code was always searched from the end of the list, this is no longer + * necessary because the 'set' routine handles duplicate entries correcty. + */ + do /* num_chunk_list > 0, so at least one */ + { + p -= 5; + + if (!memcmp(chunk_name, p, 4)) + return p[4]; + } + while (p > p_end); + + /* This means that known chunks should be processed and unknown chunks should + * be handled according to the value of png_ptr->unknown_default; this can be + * confusing because, as a result, there are two levels of defaulting for + * unknown chunks. + */ + return PNG_HANDLE_CHUNK_AS_DEFAULT; +} + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +int /* PRIVATE */ +png_chunk_unknown_handling(png_const_structrp png_ptr, png_uint_32 chunk_name) +{ + png_byte chunk_string[5]; + + PNG_CSTRING_FROM_CHUNK(chunk_string, chunk_name); + return png_handle_as_unknown(png_ptr, chunk_string); +} +#endif /* READ_UNKNOWN_CHUNKS */ +#endif /* SET_UNKNOWN_CHUNKS */ + +#ifdef PNG_READ_SUPPORTED +/* This function, added to libpng-1.0.6g, is untested. */ +int PNGAPI +png_reset_zstream(png_structrp png_ptr) +{ + if (png_ptr == NULL) + return Z_STREAM_ERROR; + + /* WARNING: this resets the window bits to the maximum! */ + return (inflateReset(&png_ptr->zstream)); +} +#endif /* PNG_READ_SUPPORTED */ + +/* This function was added to libpng-1.0.7 */ +png_uint_32 PNGAPI +png_access_version_number(void) +{ + /* Version of *.c files used when building libpng */ + return((png_uint_32)PNG_LIBPNG_VER); +} + + + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Ensure that png_ptr->zstream.msg holds some appropriate error message string. + * If it doesn't 'ret' is used to set it to something appropriate, even in cases + * like Z_OK or Z_STREAM_END where the error code is apparently a success code. + */ +void /* PRIVATE */ +png_zstream_error(png_structrp png_ptr, int ret) +{ + /* Translate 'ret' into an appropriate error string, priority is given to the + * one in zstream if set. This always returns a string, even in cases like + * Z_OK or Z_STREAM_END where the error code is a success code. + */ + if (png_ptr->zstream.msg == NULL) switch (ret) + { + default: + case Z_OK: + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return code"); + break; + + case Z_STREAM_END: + /* Normal exit */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected end of LZ stream"); + break; + + case Z_NEED_DICT: + /* This means the deflate stream did not have a dictionary; this + * indicates a bogus PNG. + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("missing LZ dictionary"); + break; + + case Z_ERRNO: + /* gz APIs only: should not happen */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("zlib IO error"); + break; + + case Z_STREAM_ERROR: + /* internal libpng error */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("bad parameters to zlib"); + break; + + case Z_DATA_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("damaged LZ stream"); + break; + + case Z_MEM_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("insufficient memory"); + break; + + case Z_BUF_ERROR: + /* End of input or output; not a problem if the caller is doing + * incremental read or write. + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("truncated"); + break; + + case Z_VERSION_ERROR: + png_ptr->zstream.msg = PNGZ_MSG_CAST("unsupported zlib version"); + break; + + case PNG_UNEXPECTED_ZLIB_RETURN: + /* Compile errors here mean that zlib now uses the value co-opted in + * pngpriv.h for PNG_UNEXPECTED_ZLIB_RETURN; update the switch above + * and change pngpriv.h. Note that this message is "... return", + * whereas the default/Z_OK one is "... return code". + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return"); + break; + } +} + +/* png_convert_size: a PNGAPI but no longer in png.h, so deleted + * at libpng 1.5.5! + */ + +/* Added at libpng version 1.2.34 and 1.4.0 (moved from pngset.c) */ +#ifdef PNG_GAMMA_SUPPORTED /* always set if COLORSPACE */ +static int +png_colorspace_check_gamma(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA, int from) + /* This is called to check a new gamma value against an existing one. The + * routine returns false if the new gamma value should not be written. + * + * 'from' says where the new gamma value comes from: + * + * 0: the new gamma value is the libpng estimate for an ICC profile + * 1: the new gamma value comes from a gAMA chunk + * 2: the new gamma value comes from an sRGB chunk + */ +{ + png_fixed_point gtest; + + if ((colorspace->flags & PNG_COLORSPACE_HAVE_GAMMA) != 0 && + (!png_muldiv(>est, colorspace->gamma, PNG_FP_1, gAMA) || + png_gamma_significant(gtest))) + { + /* Either this is an sRGB image, in which case the calculated gamma + * approximation should match, or this is an image with a profile and the + * value libpng calculates for the gamma of the profile does not match the + * value recorded in the file. The former, sRGB, case is an error, the + * latter is just a warning. + */ + if ((colorspace->flags & PNG_COLORSPACE_FROM_sRGB) != 0 || from == 2) + { + png_chunk_report(png_ptr, "gamma value does not match sRGB", + PNG_CHUNK_ERROR); + /* Do not overwrite an sRGB value */ + return from == 2; + } + + else /* sRGB tag not involved */ + { + png_chunk_report(png_ptr, "gamma value does not match libpng estimate", + PNG_CHUNK_WARNING); + return from == 1; + } + } + + return 1; +} + +void /* PRIVATE */ +png_colorspace_set_gamma(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA) +{ + /* Changed in libpng-1.5.4 to limit the values to ensure overflow can't + * occur. Since the fixed point representation is assymetrical it is + * possible for 1/gamma to overflow the limit of 21474 and this means the + * gamma value must be at least 5/100000 and hence at most 20000.0. For + * safety the limits here are a little narrower. The values are 0.00016 to + * 6250.0, which are truly ridiculous gamma values (and will produce + * displays that are all black or all white.) + * + * In 1.6.0 this test replaces the ones in pngrutil.c, in the gAMA chunk + * handling code, which only required the value to be >0. + */ + png_const_charp errmsg; + + if (gAMA < 16 || gAMA > 625000000) + errmsg = "gamma value out of range"; + +# ifdef PNG_READ_gAMA_SUPPORTED + /* Allow the application to set the gamma value more than once */ + else if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + (colorspace->flags & PNG_COLORSPACE_FROM_gAMA) != 0) + errmsg = "duplicate"; +# endif + + /* Do nothing if the colorspace is already invalid */ + else if (colorspace->flags & PNG_COLORSPACE_INVALID) + return; + + else + { + if (png_colorspace_check_gamma(png_ptr, colorspace, gAMA, 1/*from gAMA*/)) + { + /* Store this gamma value. */ + colorspace->gamma = gAMA; + colorspace->flags |= + (PNG_COLORSPACE_HAVE_GAMMA | PNG_COLORSPACE_FROM_gAMA); + } + + /* At present if the check_gamma test fails the gamma of the colorspace is + * not updated however the colorspace is not invalidated. This + * corresponds to the case where the existing gamma comes from an sRGB + * chunk or profile. An error message has already been output. + */ + return; + } + + /* Error exit - errmsg has been set. */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_chunk_report(png_ptr, errmsg, PNG_CHUNK_WRITE_ERROR); +} + +void /* PRIVATE */ +png_colorspace_sync_info(png_const_structrp png_ptr, png_inforp info_ptr) +{ + if (info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) + { + /* Everything is invalid */ + info_ptr->valid &= ~(PNG_INFO_gAMA|PNG_INFO_cHRM|PNG_INFO_sRGB| + PNG_INFO_iCCP); + +# ifdef PNG_COLORSPACE_SUPPORTED + /* Clean up the iCCP profile now if it won't be used. */ + png_free_data(png_ptr, info_ptr, PNG_FREE_ICCP, -1/*not used*/); +# else + PNG_UNUSED(png_ptr) +# endif + } + + else + { +# ifdef PNG_COLORSPACE_SUPPORTED + /* Leave the INFO_iCCP flag set if the pngset.c code has already set + * it; this allows a PNG to contain a profile which matches sRGB and + * yet still have that profile retrievable by the application. + */ + if (info_ptr->colorspace.flags & PNG_COLORSPACE_MATCHES_sRGB) + info_ptr->valid |= PNG_INFO_sRGB; + + else + info_ptr->valid &= ~PNG_INFO_sRGB; + + if (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) + info_ptr->valid |= PNG_INFO_cHRM; + + else + info_ptr->valid &= ~PNG_INFO_cHRM; +# endif + + if (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) + info_ptr->valid |= PNG_INFO_gAMA; + + else + info_ptr->valid &= ~PNG_INFO_gAMA; + } +} + +#ifdef PNG_READ_SUPPORTED +void /* PRIVATE */ +png_colorspace_sync(png_const_structrp png_ptr, png_inforp info_ptr) +{ + if (info_ptr == NULL) /* reduce code size; check here not in the caller */ + return; + + info_ptr->colorspace = png_ptr->colorspace; + png_colorspace_sync_info(png_ptr, info_ptr); +} +#endif +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED +/* Added at libpng-1.5.5 to support read and write of true CIEXYZ values for + * cHRM, as opposed to using chromaticities. These internal APIs return + * non-zero on a parameter error. The X, Y and Z values are required to be + * positive and less than 1.0. + */ +static int +png_xy_from_XYZ(png_xy *xy, const png_XYZ *XYZ) +{ + png_int_32 d, dwhite, whiteX, whiteY; + + d = XYZ->red_X + XYZ->red_Y + XYZ->red_Z; + if (!png_muldiv(&xy->redx, XYZ->red_X, PNG_FP_1, d)) return 1; + if (!png_muldiv(&xy->redy, XYZ->red_Y, PNG_FP_1, d)) return 1; + dwhite = d; + whiteX = XYZ->red_X; + whiteY = XYZ->red_Y; + + d = XYZ->green_X + XYZ->green_Y + XYZ->green_Z; + if (!png_muldiv(&xy->greenx, XYZ->green_X, PNG_FP_1, d)) return 1; + if (!png_muldiv(&xy->greeny, XYZ->green_Y, PNG_FP_1, d)) return 1; + dwhite += d; + whiteX += XYZ->green_X; + whiteY += XYZ->green_Y; + + d = XYZ->blue_X + XYZ->blue_Y + XYZ->blue_Z; + if (!png_muldiv(&xy->bluex, XYZ->blue_X, PNG_FP_1, d)) return 1; + if (!png_muldiv(&xy->bluey, XYZ->blue_Y, PNG_FP_1, d)) return 1; + dwhite += d; + whiteX += XYZ->blue_X; + whiteY += XYZ->blue_Y; + + /* The reference white is simply the sum of the end-point (X,Y,Z) vectors, + * thus: + */ + if (!png_muldiv(&xy->whitex, whiteX, PNG_FP_1, dwhite)) return 1; + if (!png_muldiv(&xy->whitey, whiteY, PNG_FP_1, dwhite)) return 1; + + return 0; +} + +static int +png_XYZ_from_xy(png_XYZ *XYZ, const png_xy *xy) +{ + png_fixed_point red_inverse, green_inverse, blue_scale; + png_fixed_point left, right, denominator; + + /* Check xy and, implicitly, z. Note that wide gamut color spaces typically + * have end points with 0 tristimulus values (these are impossible end + * points, but they are used to cover the possible colors.) + */ + if (xy->redx < 0 || xy->redx > PNG_FP_1) return 1; + if (xy->redy < 0 || xy->redy > PNG_FP_1-xy->redx) return 1; + if (xy->greenx < 0 || xy->greenx > PNG_FP_1) return 1; + if (xy->greeny < 0 || xy->greeny > PNG_FP_1-xy->greenx) return 1; + if (xy->bluex < 0 || xy->bluex > PNG_FP_1) return 1; + if (xy->bluey < 0 || xy->bluey > PNG_FP_1-xy->bluex) return 1; + if (xy->whitex < 0 || xy->whitex > PNG_FP_1) return 1; + if (xy->whitey < 0 || xy->whitey > PNG_FP_1-xy->whitex) return 1; + + /* The reverse calculation is more difficult because the original tristimulus + * value had 9 independent values (red,green,blue)x(X,Y,Z) however only 8 + * derived values were recorded in the cHRM chunk; + * (red,green,blue,white)x(x,y). This loses one degree of freedom and + * therefore an arbitrary ninth value has to be introduced to undo the + * original transformations. + * + * Think of the original end-points as points in (X,Y,Z) space. The + * chromaticity values (c) have the property: + * + * C + * c = --------- + * X + Y + Z + * + * For each c (x,y,z) from the corresponding original C (X,Y,Z). Thus the + * three chromaticity values (x,y,z) for each end-point obey the + * relationship: + * + * x + y + z = 1 + * + * This describes the plane in (X,Y,Z) space that intersects each axis at the + * value 1.0; call this the chromaticity plane. Thus the chromaticity + * calculation has scaled each end-point so that it is on the x+y+z=1 plane + * and chromaticity is the intersection of the vector from the origin to the + * (X,Y,Z) value with the chromaticity plane. + * + * To fully invert the chromaticity calculation we would need the three + * end-point scale factors, (red-scale, green-scale, blue-scale), but these + * were not recorded. Instead we calculated the reference white (X,Y,Z) and + * recorded the chromaticity of this. The reference white (X,Y,Z) would have + * given all three of the scale factors since: + * + * color-C = color-c * color-scale + * white-C = red-C + green-C + blue-C + * = red-c*red-scale + green-c*green-scale + blue-c*blue-scale + * + * But cHRM records only white-x and white-y, so we have lost the white scale + * factor: + * + * white-C = white-c*white-scale + * + * To handle this the inverse transformation makes an arbitrary assumption + * about white-scale: + * + * Assume: white-Y = 1.0 + * Hence: white-scale = 1/white-y + * Or: red-Y + green-Y + blue-Y = 1.0 + * + * Notice the last statement of the assumption gives an equation in three of + * the nine values we want to calculate. 8 more equations come from the + * above routine as summarised at the top above (the chromaticity + * calculation): + * + * Given: color-x = color-X / (color-X + color-Y + color-Z) + * Hence: (color-x - 1)*color-X + color.x*color-Y + color.x*color-Z = 0 + * + * This is 9 simultaneous equations in the 9 variables "color-C" and can be + * solved by Cramer's rule. Cramer's rule requires calculating 10 9x9 matrix + * determinants, however this is not as bad as it seems because only 28 of + * the total of 90 terms in the various matrices are non-zero. Nevertheless + * Cramer's rule is notoriously numerically unstable because the determinant + * calculation involves the difference of large, but similar, numbers. It is + * difficult to be sure that the calculation is stable for real world values + * and it is certain that it becomes unstable where the end points are close + * together. + * + * So this code uses the perhaps slightly less optimal but more + * understandable and totally obvious approach of calculating color-scale. + * + * This algorithm depends on the precision in white-scale and that is + * (1/white-y), so we can immediately see that as white-y approaches 0 the + * accuracy inherent in the cHRM chunk drops off substantially. + * + * libpng arithmetic: a simple invertion of the above equations + * ------------------------------------------------------------ + * + * white_scale = 1/white-y + * white-X = white-x * white-scale + * white-Y = 1.0 + * white-Z = (1 - white-x - white-y) * white_scale + * + * white-C = red-C + green-C + blue-C + * = red-c*red-scale + green-c*green-scale + blue-c*blue-scale + * + * This gives us three equations in (red-scale,green-scale,blue-scale) where + * all the coefficients are now known: + * + * red-x*red-scale + green-x*green-scale + blue-x*blue-scale + * = white-x/white-y + * red-y*red-scale + green-y*green-scale + blue-y*blue-scale = 1 + * red-z*red-scale + green-z*green-scale + blue-z*blue-scale + * = (1 - white-x - white-y)/white-y + * + * In the last equation color-z is (1 - color-x - color-y) so we can add all + * three equations together to get an alternative third: + * + * red-scale + green-scale + blue-scale = 1/white-y = white-scale + * + * So now we have a Cramer's rule solution where the determinants are just + * 3x3 - far more tractible. Unfortunately 3x3 determinants still involve + * multiplication of three coefficients so we can't guarantee to avoid + * overflow in the libpng fixed point representation. Using Cramer's rule in + * floating point is probably a good choice here, but it's not an option for + * fixed point. Instead proceed to simplify the first two equations by + * eliminating what is likely to be the largest value, blue-scale: + * + * blue-scale = white-scale - red-scale - green-scale + * + * Hence: + * + * (red-x - blue-x)*red-scale + (green-x - blue-x)*green-scale = + * (white-x - blue-x)*white-scale + * + * (red-y - blue-y)*red-scale + (green-y - blue-y)*green-scale = + * 1 - blue-y*white-scale + * + * And now we can trivially solve for (red-scale,green-scale): + * + * green-scale = + * (white-x - blue-x)*white-scale - (red-x - blue-x)*red-scale + * ----------------------------------------------------------- + * green-x - blue-x + * + * red-scale = + * 1 - blue-y*white-scale - (green-y - blue-y) * green-scale + * --------------------------------------------------------- + * red-y - blue-y + * + * Hence: + * + * red-scale = + * ( (green-x - blue-x) * (white-y - blue-y) - + * (green-y - blue-y) * (white-x - blue-x) ) / white-y + * ------------------------------------------------------------------------- + * (green-x - blue-x)*(red-y - blue-y)-(green-y - blue-y)*(red-x - blue-x) + * + * green-scale = + * ( (red-y - blue-y) * (white-x - blue-x) - + * (red-x - blue-x) * (white-y - blue-y) ) / white-y + * ------------------------------------------------------------------------- + * (green-x - blue-x)*(red-y - blue-y)-(green-y - blue-y)*(red-x - blue-x) + * + * Accuracy: + * The input values have 5 decimal digits of accuracy. The values are all in + * the range 0 < value < 1, so simple products are in the same range but may + * need up to 10 decimal digits to preserve the original precision and avoid + * underflow. Because we are using a 32-bit signed representation we cannot + * match this; the best is a little over 9 decimal digits, less than 10. + * + * The approach used here is to preserve the maximum precision within the + * signed representation. Because the red-scale calculation above uses the + * difference between two products of values that must be in the range -1..+1 + * it is sufficient to divide the product by 7; ceil(100,000/32767*2). The + * factor is irrelevant in the calculation because it is applied to both + * numerator and denominator. + * + * Note that the values of the differences of the products of the + * chromaticities in the above equations tend to be small, for example for + * the sRGB chromaticities they are: + * + * red numerator: -0.04751 + * green numerator: -0.08788 + * denominator: -0.2241 (without white-y multiplication) + * + * The resultant Y coefficients from the chromaticities of some widely used + * color space definitions are (to 15 decimal places): + * + * sRGB + * 0.212639005871510 0.715168678767756 0.072192315360734 + * Kodak ProPhoto + * 0.288071128229293 0.711843217810102 0.000085653960605 + * Adobe RGB + * 0.297344975250536 0.627363566255466 0.075291458493998 + * Adobe Wide Gamut RGB + * 0.258728243040113 0.724682314948566 0.016589442011321 + */ + /* By the argument, above overflow should be impossible here. The return + * value of 2 indicates an internal error to the caller. + */ + if (!png_muldiv(&left, xy->greenx-xy->bluex, xy->redy - xy->bluey, 7)) + return 2; + if (!png_muldiv(&right, xy->greeny-xy->bluey, xy->redx - xy->bluex, 7)) + return 2; + denominator = left - right; + + /* Now find the red numerator. */ + if (!png_muldiv(&left, xy->greenx-xy->bluex, xy->whitey-xy->bluey, 7)) + return 2; + if (!png_muldiv(&right, xy->greeny-xy->bluey, xy->whitex-xy->bluex, 7)) + return 2; + + /* Overflow is possible here and it indicates an extreme set of PNG cHRM + * chunk values. This calculation actually returns the reciprocal of the + * scale value because this allows us to delay the multiplication of white-y + * into the denominator, which tends to produce a small number. + */ + if (!png_muldiv(&red_inverse, xy->whitey, denominator, left-right) || + red_inverse <= xy->whitey /* r+g+b scales = white scale */) + return 1; + + /* Similarly for green_inverse: */ + if (!png_muldiv(&left, xy->redy-xy->bluey, xy->whitex-xy->bluex, 7)) + return 2; + if (!png_muldiv(&right, xy->redx-xy->bluex, xy->whitey-xy->bluey, 7)) + return 2; + if (!png_muldiv(&green_inverse, xy->whitey, denominator, left-right) || + green_inverse <= xy->whitey) + return 1; + + /* And the blue scale, the checks above guarantee this can't overflow but it + * can still produce 0 for extreme cHRM values. + */ + blue_scale = png_reciprocal(xy->whitey) - png_reciprocal(red_inverse) - + png_reciprocal(green_inverse); + if (blue_scale <= 0) return 1; + + + /* And fill in the png_XYZ: */ + if (!png_muldiv(&XYZ->red_X, xy->redx, PNG_FP_1, red_inverse)) return 1; + if (!png_muldiv(&XYZ->red_Y, xy->redy, PNG_FP_1, red_inverse)) return 1; + if (!png_muldiv(&XYZ->red_Z, PNG_FP_1 - xy->redx - xy->redy, PNG_FP_1, + red_inverse)) + return 1; + + if (!png_muldiv(&XYZ->green_X, xy->greenx, PNG_FP_1, green_inverse)) + return 1; + if (!png_muldiv(&XYZ->green_Y, xy->greeny, PNG_FP_1, green_inverse)) + return 1; + if (!png_muldiv(&XYZ->green_Z, PNG_FP_1 - xy->greenx - xy->greeny, PNG_FP_1, + green_inverse)) + return 1; + + if (!png_muldiv(&XYZ->blue_X, xy->bluex, blue_scale, PNG_FP_1)) return 1; + if (!png_muldiv(&XYZ->blue_Y, xy->bluey, blue_scale, PNG_FP_1)) return 1; + if (!png_muldiv(&XYZ->blue_Z, PNG_FP_1 - xy->bluex - xy->bluey, blue_scale, + PNG_FP_1)) + return 1; + + return 0; /*success*/ +} + +static int +png_XYZ_normalize(png_XYZ *XYZ) +{ + png_int_32 Y; + + if (XYZ->red_Y < 0 || XYZ->green_Y < 0 || XYZ->blue_Y < 0 || + XYZ->red_X < 0 || XYZ->green_X < 0 || XYZ->blue_X < 0 || + XYZ->red_Z < 0 || XYZ->green_Z < 0 || XYZ->blue_Z < 0) + return 1; + + /* Normalize by scaling so the sum of the end-point Y values is PNG_FP_1. + * IMPLEMENTATION NOTE: ANSI requires signed overflow not to occur, therefore + * relying on addition of two positive values producing a negative one is not + * safe. + */ + Y = XYZ->red_Y; + if (0x7fffffff - Y < XYZ->green_X) return 1; + Y += XYZ->green_Y; + if (0x7fffffff - Y < XYZ->blue_X) return 1; + Y += XYZ->blue_Y; + + if (Y != PNG_FP_1) + { + if (!png_muldiv(&XYZ->red_X, XYZ->red_X, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->red_Y, XYZ->red_Y, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->red_Z, XYZ->red_Z, PNG_FP_1, Y)) return 1; + + if (!png_muldiv(&XYZ->green_X, XYZ->green_X, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->green_Y, XYZ->green_Y, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->green_Z, XYZ->green_Z, PNG_FP_1, Y)) return 1; + + if (!png_muldiv(&XYZ->blue_X, XYZ->blue_X, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->blue_Y, XYZ->blue_Y, PNG_FP_1, Y)) return 1; + if (!png_muldiv(&XYZ->blue_Z, XYZ->blue_Z, PNG_FP_1, Y)) return 1; + } + + return 0; +} + +static int +png_colorspace_endpoints_match(const png_xy *xy1, const png_xy *xy2, int delta) +{ + /* Allow an error of +/-0.01 (absolute value) on each chromaticity */ + return !(PNG_OUT_OF_RANGE(xy1->whitex, xy2->whitex,delta) || + PNG_OUT_OF_RANGE(xy1->whitey, xy2->whitey,delta) || + PNG_OUT_OF_RANGE(xy1->redx, xy2->redx, delta) || + PNG_OUT_OF_RANGE(xy1->redy, xy2->redy, delta) || + PNG_OUT_OF_RANGE(xy1->greenx, xy2->greenx,delta) || + PNG_OUT_OF_RANGE(xy1->greeny, xy2->greeny,delta) || + PNG_OUT_OF_RANGE(xy1->bluex, xy2->bluex, delta) || + PNG_OUT_OF_RANGE(xy1->bluey, xy2->bluey, delta)); +} + +/* Added in libpng-1.6.0, a different check for the validity of a set of cHRM + * chunk chromaticities. Earlier checks used to simply look for the overflow + * condition (where the determinant of the matrix to solve for XYZ ends up zero + * because the chromaticity values are not all distinct.) Despite this it is + * theoretically possible to produce chromaticities that are apparently valid + * but that rapidly degrade to invalid, potentially crashing, sets because of + * arithmetic inaccuracies when calculations are performed on them. The new + * check is to round-trip xy -> XYZ -> xy and then check that the result is + * within a small percentage of the original. + */ +static int +png_colorspace_check_xy(png_XYZ *XYZ, const png_xy *xy) +{ + int result; + png_xy xy_test; + + /* As a side-effect this routine also returns the XYZ endpoints. */ + result = png_XYZ_from_xy(XYZ, xy); + if (result) return result; + + result = png_xy_from_XYZ(&xy_test, XYZ); + if (result) return result; + + if (png_colorspace_endpoints_match(xy, &xy_test, + 5/*actually, the math is pretty accurate*/)) + return 0; + + /* Too much slip */ + return 1; +} + +/* This is the check going the other way. The XYZ is modified to normalize it + * (another side-effect) and the xy chromaticities are returned. + */ +static int +png_colorspace_check_XYZ(png_xy *xy, png_XYZ *XYZ) +{ + int result; + png_XYZ XYZtemp; + + result = png_XYZ_normalize(XYZ); + if (result) return result; + + result = png_xy_from_XYZ(xy, XYZ); + if (result) return result; + + XYZtemp = *XYZ; + return png_colorspace_check_xy(&XYZtemp, xy); +} + +/* Used to check for an endpoint match against sRGB */ +static const png_xy sRGB_xy = /* From ITU-R BT.709-3 */ +{ + /* color x y */ + /* red */ 64000, 33000, + /* green */ 30000, 60000, + /* blue */ 15000, 6000, + /* white */ 31270, 32900 +}; + +static int +png_colorspace_set_xy_and_XYZ(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_xy *xy, const png_XYZ *XYZ, + int preferred) +{ + if (colorspace->flags & PNG_COLORSPACE_INVALID) + return 0; + + /* The consistency check is performed on the chromaticities; this factors out + * variations because of the normalization (or not) of the end point Y + * values. + */ + if (preferred < 2 && (colorspace->flags & PNG_COLORSPACE_HAVE_ENDPOINTS)) + { + /* The end points must be reasonably close to any we already have. The + * following allows an error of up to +/-.001 + */ + if (!png_colorspace_endpoints_match(xy, &colorspace->end_points_xy, 100)) + { + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "inconsistent chromaticities"); + return 0; /* failed */ + } + + /* Only overwrite with preferred values */ + if (!preferred) + return 1; /* ok, but no change */ + } + + colorspace->end_points_xy = *xy; + colorspace->end_points_XYZ = *XYZ; + colorspace->flags |= PNG_COLORSPACE_HAVE_ENDPOINTS; + + /* The end points are normally quoted to two decimal digits, so allow +/-0.01 + * on this test. + */ + if (png_colorspace_endpoints_match(xy, &sRGB_xy, 1000)) + colorspace->flags |= PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB; + + else + colorspace->flags &= PNG_COLORSPACE_CANCEL( + PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB); + + return 2; /* ok and changed */ +} + +int /* PRIVATE */ +png_colorspace_set_chromaticities(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_xy *xy, int preferred) +{ + /* We must check the end points to ensure they are reasonable - in the past + * color management systems have crashed as a result of getting bogus + * colorant values, while this isn't the fault of libpng it is the + * responsibility of libpng because PNG carries the bomb and libpng is in a + * position to protect against it. + */ + png_XYZ XYZ; + + switch (png_colorspace_check_xy(&XYZ, xy)) + { + case 0: /* success */ + return png_colorspace_set_xy_and_XYZ(png_ptr, colorspace, xy, &XYZ, + preferred); + + case 1: + /* We can't invert the chromaticities so we can't produce value XYZ + * values. Likely as not a color management system will fail too. + */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "invalid chromaticities"); + break; + + default: + /* libpng is broken; this should be a warning but if it happens we + * want error reports so for the moment it is an error. + */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_error(png_ptr, "internal error checking chromaticities"); + break; + } + + return 0; /* failed */ +} + +int /* PRIVATE */ +png_colorspace_set_endpoints(png_const_structrp png_ptr, + png_colorspacerp colorspace, const png_XYZ *XYZ_in, int preferred) +{ + png_XYZ XYZ = *XYZ_in; + png_xy xy; + + switch (png_colorspace_check_XYZ(&xy, &XYZ)) + { + case 0: + return png_colorspace_set_xy_and_XYZ(png_ptr, colorspace, &xy, &XYZ, + preferred); + + case 1: + /* End points are invalid. */ + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_benign_error(png_ptr, "invalid end points"); + break; + + default: + colorspace->flags |= PNG_COLORSPACE_INVALID; + png_error(png_ptr, "internal error checking chromaticities"); + break; + } + + return 0; /* failed */ +} + +#if defined(PNG_sRGB_SUPPORTED) || defined(PNG_iCCP_SUPPORTED) +/* Error message generation */ +static char +png_icc_tag_char(png_uint_32 byte) +{ + byte &= 0xff; + if (byte >= 32 && byte <= 126) + return (char)byte; + else + return '?'; +} + +static void +png_icc_tag_name(char *name, png_uint_32 tag) +{ + name[0] = '\''; + name[1] = png_icc_tag_char(tag >> 24); + name[2] = png_icc_tag_char(tag >> 16); + name[3] = png_icc_tag_char(tag >> 8); + name[4] = png_icc_tag_char(tag ); + name[5] = '\''; +} + +static int +is_ICC_signature_char(png_alloc_size_t it) +{ + return it == 32 || (it >= 48 && it <= 57) || (it >= 65 && it <= 90) || + (it >= 97 && it <= 122); +} + +static int is_ICC_signature(png_alloc_size_t it) +{ + return is_ICC_signature_char(it >> 24) /* checks all the top bits */ && + is_ICC_signature_char((it >> 16) & 0xff) && + is_ICC_signature_char((it >> 8) & 0xff) && + is_ICC_signature_char(it & 0xff); +} + +static int +png_icc_profile_error(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_alloc_size_t value, png_const_charp reason) +{ + size_t pos; + char message[196]; /* see below for calculation */ + + if (colorspace != NULL) + colorspace->flags |= PNG_COLORSPACE_INVALID; + + pos = png_safecat(message, (sizeof message), 0, "profile '"); /* 9 chars */ + pos = png_safecat(message, pos+79, pos, name); /* Truncate to 79 chars */ + pos = png_safecat(message, (sizeof message), pos, "': "); /* +2 = 90 */ + if (is_ICC_signature(value)) + { + /* So 'value' is at most 4 bytes and the following cast is safe */ + png_icc_tag_name(message+pos, (png_uint_32)value); + pos += 6; /* total +8; less than the else clause */ + message[pos++] = ':'; + message[pos++] = ' '; + } +# ifdef PNG_WARNINGS_SUPPORTED + else + { + char number[PNG_NUMBER_BUFFER_SIZE]; /* +24 = 114*/ + + pos = png_safecat(message, (sizeof message), pos, + png_format_number(number, number+(sizeof number), + PNG_NUMBER_FORMAT_x, value)); + pos = png_safecat(message, (sizeof message), pos, "h: "); /*+2 = 116*/ + } +# endif + /* The 'reason' is an arbitrary message, allow +79 maximum 195 */ + png_safecat(message, (sizeof message), pos, reason); + + /* This is recoverable, but make it unconditionally an app_error on write to + * avoid writing invalid ICC profiles into PNG files. (I.e. we handle them + * on read, with a warning, but on write unless the app turns off + * application errors the PNG won't be written.) + */ + png_chunk_report(png_ptr, message, + (colorspace != NULL) ? PNG_CHUNK_ERROR : PNG_CHUNK_WRITE_ERROR); + + return 0; +} +#endif /* sRGB || iCCP */ + +#ifdef PNG_sRGB_SUPPORTED +int /* PRIVATE */ +png_colorspace_set_sRGB(png_const_structrp png_ptr, png_colorspacerp colorspace, + int intent) +{ + /* sRGB sets known gamma, end points and (from the chunk) intent. */ + /* IMPORTANT: these are not necessarily the values found in an ICC profile + * because ICC profiles store values adapted to a D50 environment; it is + * expected that the ICC profile mediaWhitePointTag will be D50, see the + * checks and code elsewhere to understand this better. + * + * These XYZ values, which are accurate to 5dp, produce rgb to gray + * coefficients of (6968,23435,2366), which are reduced (because they add up + * to 32769 not 32768) to (6968,23434,2366). These are the values that + * libpng has traditionally used (and are the best values given the 15bit + * algorithm used by the rgb to gray code.) + */ + static const png_XYZ sRGB_XYZ = /* D65 XYZ (*not* the D50 adapted values!) */ + { + /* color X Y Z */ + /* red */ 41239, 21264, 1933, + /* green */ 35758, 71517, 11919, + /* blue */ 18048, 7219, 95053 + }; + + /* Do nothing if the colorspace is already invalidated. */ + if (colorspace->flags & PNG_COLORSPACE_INVALID) + return 0; + + /* Check the intent, then check for existing settings. It is valid for the + * PNG file to have cHRM or gAMA chunks along with sRGB, but the values must + * be consistent with the correct values. If, however, this function is + * called below because an iCCP chunk matches sRGB then it is quite + * conceivable that an older app recorded incorrect gAMA and cHRM because of + * an incorrect calculation based on the values in the profile - this does + * *not* invalidate the profile (though it still produces an error, which can + * be ignored.) + */ + if (intent < 0 || intent >= PNG_sRGB_INTENT_LAST) + return png_icc_profile_error(png_ptr, colorspace, "sRGB", + (unsigned)intent, "invalid sRGB rendering intent"); + + if ((colorspace->flags & PNG_COLORSPACE_HAVE_INTENT) != 0 && + colorspace->rendering_intent != intent) + return png_icc_profile_error(png_ptr, colorspace, "sRGB", + (unsigned)intent, "inconsistent rendering intents"); + + if ((colorspace->flags & PNG_COLORSPACE_FROM_sRGB) != 0) + { + png_benign_error(png_ptr, "duplicate sRGB information ignored"); + return 0; + } + + /* If the standard sRGB cHRM chunk does not match the one from the PNG file + * warn but overwrite the value with the correct one. + */ + if ((colorspace->flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0 && + !png_colorspace_endpoints_match(&sRGB_xy, &colorspace->end_points_xy, + 100)) + png_chunk_report(png_ptr, "cHRM chunk does not match sRGB", + PNG_CHUNK_ERROR); + + /* This check is just done for the error reporting - the routine always + * returns true when the 'from' argument corresponds to sRGB (2). + */ + (void)png_colorspace_check_gamma(png_ptr, colorspace, PNG_GAMMA_sRGB_INVERSE, + 2/*from sRGB*/); + + /* intent: bugs in GCC force 'int' to be used as the parameter type. */ + colorspace->rendering_intent = (png_uint_16)intent; + colorspace->flags |= PNG_COLORSPACE_HAVE_INTENT; + + /* endpoints */ + colorspace->end_points_xy = sRGB_xy; + colorspace->end_points_XYZ = sRGB_XYZ; + colorspace->flags |= + (PNG_COLORSPACE_HAVE_ENDPOINTS|PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB); + + /* gamma */ + colorspace->gamma = PNG_GAMMA_sRGB_INVERSE; + colorspace->flags |= PNG_COLORSPACE_HAVE_GAMMA; + + /* Finally record that we have an sRGB profile */ + colorspace->flags |= + (PNG_COLORSPACE_MATCHES_sRGB|PNG_COLORSPACE_FROM_sRGB); + + return 1; /* set */ +} +#endif /* sRGB */ + +#ifdef PNG_iCCP_SUPPORTED +/* Encoded value of D50 as an ICC XYZNumber. From the ICC 2010 spec the value + * is XYZ(0.9642,1.0,0.8249), which scales to: + * + * (63189.8112, 65536, 54060.6464) + */ +static const png_byte D50_nCIEXYZ[12] = + { 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d }; + +int /* PRIVATE */ +png_icc_check_length(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length) +{ + if (profile_length < 132) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "too short"); + + if (profile_length & 3) + return png_icc_profile_error(png_ptr, colorspace, name, profile_length, + "invalid length"); + + return 1; +} + +int /* PRIVATE */ +png_icc_check_header(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, + png_const_bytep profile/* first 132 bytes only */, int color_type) +{ + png_uint_32 temp; + + /* Length check; this cannot be ignored in this code because profile_length + * is used later to check the tag table, so even if the profile seems over + * long profile_length from the caller must be correct. The caller can fix + * this up on read or write by just passing in the profile header length. + */ + temp = png_get_uint_32(profile); + if (temp != profile_length) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "length does not match profile"); + + temp = png_get_uint_32(profile+128); /* tag count: 12 bytes/tag */ + if (temp > 357913930 || /* (2^32-4-132)/12: maxium possible tag count */ + profile_length < 132+12*temp) /* truncated tag table */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "tag count too large"); + + /* The 'intent' must be valid or we can't store it, ICC limits the intent to + * 16 bits. + */ + temp = png_get_uint_32(profile+64); + if (temp >= 0xffff) /* The ICC limit */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid rendering intent"); + + /* This is just a warning because the profile may be valid in future + * versions. + */ + if (temp >= PNG_sRGB_INTENT_LAST) + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "intent outside defined range"); + + /* At this point the tag table can't be checked because it hasn't necessarily + * been loaded; however, various header fields can be checked. These checks + * are for values permitted by the PNG spec in an ICC profile; the PNG spec + * restricts the profiles that can be passed in an iCCP chunk (they must be + * appropriate to processing PNG data!) + */ + + /* Data checks (could be skipped). These checks must be independent of the + * version number; however, the version number doesn't accomodate changes in + * the header fields (just the known tags and the interpretation of the + * data.) + */ + temp = png_get_uint_32(profile+36); /* signature 'ascp' */ + if (temp != 0x61637370) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid signature"); + + /* Currently the PCS illuminant/adopted white point (the computational + * white point) are required to be D50, + * however the profile contains a record of the illuminant so perhaps ICC + * expects to be able to change this in the future (despite the rationale in + * the introduction for using a fixed PCS adopted white.) Consequently the + * following is just a warning. + */ + if (memcmp(profile+68, D50_nCIEXYZ, 12) != 0) + (void)png_icc_profile_error(png_ptr, NULL, name, 0/*no tag value*/, + "PCS illuminant is not D50"); + + /* The PNG spec requires this: + * "If the iCCP chunk is present, the image samples conform to the colour + * space represented by the embedded ICC profile as defined by the + * International Color Consortium [ICC]. The colour space of the ICC profile + * shall be an RGB colour space for colour images (PNG colour types 2, 3, and + * 6), or a greyscale colour space for greyscale images (PNG colour types 0 + * and 4)." + * + * This checking code ensures the embedded profile (on either read or write) + * conforms to the specification requirements. Notice that an ICC 'gray' + * color-space profile contains the information to transform the monochrome + * data to XYZ or L*a*b (according to which PCS the profile uses) and this + * should be used in preference to the standard libpng K channel replication + * into R, G and B channels. + * + * Previously it was suggested that an RGB profile on grayscale data could be + * handled. However it it is clear that using an RGB profile in this context + * must be an error - there is no specification of what it means. Thus it is + * almost certainly more correct to ignore the profile. + */ + temp = png_get_uint_32(profile+16); /* data colour space field */ + switch (temp) + { + case 0x52474220: /* 'RGB ' */ + if (!(color_type & PNG_COLOR_MASK_COLOR)) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "RGB color space not permitted on grayscale PNG"); + break; + + case 0x47524159: /* 'GRAY' */ + if (color_type & PNG_COLOR_MASK_COLOR) + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "Gray color space not permitted on RGB PNG"); + break; + + default: + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid ICC profile color space"); + } + + /* It is up to the application to check that the profile class matches the + * application requirements; the spec provides no guidance, but it's pretty + * weird if the profile is not scanner ('scnr'), monitor ('mntr'), printer + * ('prtr') or 'spac' (for generic color spaces). Issue a warning in these + * cases. Issue an error for device link or abstract profiles - these don't + * contain the records necessary to transform the color-space to anything + * other than the target device (and not even that for an abstract profile). + * Profiles of these classes may not be embedded in images. + */ + temp = png_get_uint_32(profile+12); /* profile/device class */ + switch (temp) + { + case 0x73636E72: /* 'scnr' */ + case 0x6D6E7472: /* 'mntr' */ + case 0x70727472: /* 'prtr' */ + case 0x73706163: /* 'spac' */ + /* All supported */ + break; + + case 0x61627374: /* 'abst' */ + /* May not be embedded in an image */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "invalid embedded Abstract ICC profile"); + + case 0x6C696E6B: /* 'link' */ + /* DeviceLink profiles cannnot be interpreted in a non-device specific + * fashion, if an app uses the AToB0Tag in the profile the results are + * undefined unless the result is sent to the intended device, + * therefore a DeviceLink profile should not be found embedded in a + * PNG. + */ + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "unexpected DeviceLink ICC profile class"); + + case 0x6E6D636C: /* 'nmcl' */ + /* A NamedColor profile is also device specific, however it doesn't + * contain an AToB0 tag that is open to misintrepretation. Almost + * certainly it will fail the tests below. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "unexpected NamedColor ICC profile class"); + break; + + default: + /* To allow for future enhancements to the profile accept unrecognized + * profile classes with a warning, these then hit the test below on the + * tag content to ensure they are backward compatible with one of the + * understood profiles. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, temp, + "unrecognized ICC profile class"); + break; + } + + /* For any profile other than a device link one the PCS must be encoded + * either in XYZ or Lab. + */ + temp = png_get_uint_32(profile+20); + switch (temp) + { + case 0x58595A20: /* 'XYZ ' */ + case 0x4C616220: /* 'Lab ' */ + break; + + default: + return png_icc_profile_error(png_ptr, colorspace, name, temp, + "unexpected ICC PCS encoding"); + } + + return 1; +} + +int /* PRIVATE */ +png_icc_check_tag_table(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, + png_const_bytep profile /* header plus whole tag table */) +{ + png_uint_32 tag_count = png_get_uint_32(profile+128); + png_uint_32 itag; + png_const_bytep tag = profile+132; /* The first tag */ + + /* First scan all the tags in the table and add bits to the icc_info value + * (temporarily in 'tags'). + */ + for (itag=0; itag < tag_count; ++itag, tag += 12) + { + png_uint_32 tag_id = png_get_uint_32(tag+0); + png_uint_32 tag_start = png_get_uint_32(tag+4); /* must be aligned */ + png_uint_32 tag_length = png_get_uint_32(tag+8);/* not padded */ + + /* The ICC specification does not exclude zero length tags, therefore the + * start might actually be anywhere if there is no data, but this would be + * a clear abuse of the intent of the standard so the start is checked for + * being in range. All defined tag types have an 8 byte header - a 4 byte + * type signature then 0. + */ + if ((tag_start & 3) != 0) + { + /* CNHP730S.icc shipped with Microsoft Windows 64 violates this, it is + * only a warning here because libpng does not care about the + * alignment. + */ + (void)png_icc_profile_error(png_ptr, NULL, name, tag_id, + "ICC profile tag start not a multiple of 4"); + } + + /* This is a hard error; potentially it can cause read outside the + * profile. + */ + if (tag_start > profile_length || tag_length > profile_length - tag_start) + return png_icc_profile_error(png_ptr, colorspace, name, tag_id, + "ICC profile tag outside profile"); + } + + return 1; /* success, maybe with warnings */ +} + +#ifdef PNG_sRGB_SUPPORTED +/* Information about the known ICC sRGB profiles */ +static const struct +{ + png_uint_32 adler, crc, length; + png_uint_32 md5[4]; + png_byte have_md5; + png_byte is_broken; + png_uint_16 intent; + +# define PNG_MD5(a,b,c,d) { a, b, c, d }, (a!=0)||(b!=0)||(c!=0)||(d!=0) +# define PNG_ICC_CHECKSUM(adler, crc, md5, intent, broke, date, length, fname)\ + { adler, crc, length, md5, broke, intent }, + +} png_sRGB_checks[] = +{ + /* This data comes from contrib/tools/checksum-icc run on downloads of + * all four ICC sRGB profiles from www.color.org. + */ + /* adler32, crc32, MD5[4], intent, date, length, file-name */ + PNG_ICC_CHECKSUM(0x0a3fd9f6, 0x3b8772b9, + PNG_MD5(0x29f83dde, 0xaff255ae, 0x7842fae4, 0xca83390d), 0, 0, + "2009/03/27 21:36:31", 3048, "sRGB_IEC61966-2-1_black_scaled.icc") + + /* ICC sRGB v2 perceptual no black-compensation: */ + PNG_ICC_CHECKSUM(0x4909e5e1, 0x427ebb21, + PNG_MD5(0xc95bd637, 0xe95d8a3b, 0x0df38f99, 0xc1320389), 1, 0, + "2009/03/27 21:37:45", 3052, "sRGB_IEC61966-2-1_no_black_scaling.icc") + + PNG_ICC_CHECKSUM(0xfd2144a1, 0x306fd8ae, + PNG_MD5(0xfc663378, 0x37e2886b, 0xfd72e983, 0x8228f1b8), 0, 0, + "2009/08/10 17:28:01", 60988, "sRGB_v4_ICC_preference_displayclass.icc") + + /* ICC sRGB v4 perceptual */ + PNG_ICC_CHECKSUM(0x209c35d2, 0xbbef7812, + PNG_MD5(0x34562abf, 0x994ccd06, 0x6d2c5721, 0xd0d68c5d), 0, 0, + "2007/07/25 00:05:37", 60960, "sRGB_v4_ICC_preference.icc") + + /* The following profiles have no known MD5 checksum. If there is a match + * on the (empty) MD5 the other fields are used to attempt a match and + * a warning is produced. The first two of these profiles have a 'cprt' tag + * which suggests that they were also made by Hewlett Packard. + */ + PNG_ICC_CHECKSUM(0xa054d762, 0x5d5129ce, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 0, + "2004/07/21 18:57:42", 3024, "sRGB_IEC61966-2-1_noBPC.icc") + + /* This is a 'mntr' (display) profile with a mediaWhitePointTag that does not + * match the D50 PCS illuminant in the header (it is in fact the D65 values, + * so the white point is recorded as the un-adapted value.) The profiles + * below only differ in one byte - the intent - and are basically the same as + * the previous profile except for the mediaWhitePointTag error and a missing + * chromaticAdaptationTag. + */ + PNG_ICC_CHECKSUM(0xf784f3fb, 0x182ea552, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 0, 1/*broken*/, + "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 perceptual") + + PNG_ICC_CHECKSUM(0x0398f3fc, 0xf29e526d, + PNG_MD5(0x00000000, 0x00000000, 0x00000000, 0x00000000), 1, 1/*broken*/, + "1998/02/09 06:49:00", 3144, "HP-Microsoft sRGB v2 media-relative") +}; + +static int +png_compare_ICC_profile_with_sRGB(png_const_structrp png_ptr, + png_const_bytep profile, uLong adler) +{ + /* The quick check is to verify just the MD5 signature and trust the + * rest of the data. Because the profile has already been verified for + * correctness this is safe. png_colorspace_set_sRGB will check the 'intent' + * field too, so if the profile has been edited with an intent not defined + * by sRGB (but maybe defined by a later ICC specification) the read of + * the profile will fail at that point. + */ + png_uint_32 length = 0; + png_uint_32 intent = 0x10000; /* invalid */ +#if PNG_sRGB_PROFILE_CHECKS > 1 + uLong crc = 0; /* the value for 0 length data */ +#endif + unsigned int i; + + for (i=0; i < (sizeof png_sRGB_checks) / (sizeof png_sRGB_checks[0]); ++i) + { + if (png_get_uint_32(profile+84) == png_sRGB_checks[i].md5[0] && + png_get_uint_32(profile+88) == png_sRGB_checks[i].md5[1] && + png_get_uint_32(profile+92) == png_sRGB_checks[i].md5[2] && + png_get_uint_32(profile+96) == png_sRGB_checks[i].md5[3]) + { + /* This may be one of the old HP profiles without an MD5, in that + * case we can only use the length and Adler32 (note that these + * are not used by default if there is an MD5!) + */ +# if PNG_sRGB_PROFILE_CHECKS == 0 + if (png_sRGB_checks[i].have_md5) + return 1+png_sRGB_checks[i].is_broken; +# endif + + /* Profile is unsigned or more checks have been configured in. */ + if (length == 0) + { + length = png_get_uint_32(profile); + intent = png_get_uint_32(profile+64); + } + + /* Length *and* intent must match */ + if (length == png_sRGB_checks[i].length && + intent == png_sRGB_checks[i].intent) + { + /* Now calculate the adler32 if not done already. */ + if (adler == 0) + { + adler = adler32(0, NULL, 0); + adler = adler32(adler, profile, length); + } + + if (adler == png_sRGB_checks[i].adler) + { + /* These basic checks suggest that the data has not been + * modified, but if the check level is more than 1 perform + * our own crc32 checksum on the data. + */ +# if PNG_sRGB_PROFILE_CHECKS > 1 + if (crc == 0) + { + crc = crc32(0, NULL, 0); + crc = crc32(crc, profile, length); + } + + /* So this check must pass for the 'return' below to happen. + */ + if (crc == png_sRGB_checks[i].crc) +# endif + { + if (png_sRGB_checks[i].is_broken) + { + /* These profiles are known to have bad data that may cause + * problems if they are used, therefore attempt to + * discourage their use, skip the 'have_md5' warning below, + * which is made irrelevant by this error. + */ + png_chunk_report(png_ptr, "known incorrect sRGB profile", + PNG_CHUNK_ERROR); + } + + /* Warn that this being done; this isn't even an error since + * the profile is perfectly valid, but it would be nice if + * people used the up-to-date ones. + */ + else if (!png_sRGB_checks[i].have_md5) + { + png_chunk_report(png_ptr, + "out-of-date sRGB profile with no signature", + PNG_CHUNK_WARNING); + } + + return 1+png_sRGB_checks[i].is_broken; + } + } + } + +# if PNG_sRGB_PROFILE_CHECKS > 0 + /* The signature matched, but the profile had been changed in some + * way. This is an apparent violation of the ICC terms of use and, + * anyway, probably indicates a data error or uninformed hacking. + */ + if (png_sRGB_checks[i].have_md5) + png_benign_error(png_ptr, + "copyright violation: edited ICC profile ignored"); +# endif + } + } + + return 0; /* no match */ +} +#endif + +#ifdef PNG_sRGB_SUPPORTED +void /* PRIVATE */ +png_icc_set_sRGB(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_bytep profile, uLong adler) +{ + /* Is this profile one of the known ICC sRGB profiles? If it is, just set + * the sRGB information. + */ + if (png_compare_ICC_profile_with_sRGB(png_ptr, profile, adler)) + (void)png_colorspace_set_sRGB(png_ptr, colorspace, + (int)/*already checked*/png_get_uint_32(profile+64)); +} +#endif /* PNG_READ_sRGB_SUPPORTED */ + +int /* PRIVATE */ +png_colorspace_set_ICC(png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_charp name, png_uint_32 profile_length, png_const_bytep profile, + int color_type) +{ + if (colorspace->flags & PNG_COLORSPACE_INVALID) + return 0; + + if (png_icc_check_length(png_ptr, colorspace, name, profile_length) && + png_icc_check_header(png_ptr, colorspace, name, profile_length, profile, + color_type) && + png_icc_check_tag_table(png_ptr, colorspace, name, profile_length, + profile)) + { + png_icc_set_sRGB(png_ptr, colorspace, profile, 0); + return 1; + } + + /* Failure case */ + return 0; +} +#endif /* iCCP */ + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +void /* PRIVATE */ +png_colorspace_set_rgb_coefficients(png_structrp png_ptr) +{ + /* Set the rgb_to_gray coefficients from the colorspace. */ + if (!png_ptr->rgb_to_gray_coefficients_set && + (png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS) != 0) + { + /* png_set_background has not been called, get the coefficients from the Y + * values of the colorspace colorants. + */ + png_fixed_point r = png_ptr->colorspace.end_points_XYZ.red_Y; + png_fixed_point g = png_ptr->colorspace.end_points_XYZ.green_Y; + png_fixed_point b = png_ptr->colorspace.end_points_XYZ.blue_Y; + png_fixed_point total = r+g+b; + + if (total > 0 && + r >= 0 && png_muldiv(&r, r, 32768, total) && r >= 0 && r <= 32768 && + g >= 0 && png_muldiv(&g, g, 32768, total) && g >= 0 && g <= 32768 && + b >= 0 && png_muldiv(&b, b, 32768, total) && b >= 0 && b <= 32768 && + r+g+b <= 32769) + { + /* We allow 0 coefficients here. r+g+b may be 32769 if two or + * all of the coefficients were rounded up. Handle this by + * reducing the *largest* coefficient by 1; this matches the + * approach used for the default coefficients in pngrtran.c + */ + int add = 0; + + if (r+g+b > 32768) + add = -1; + else if (r+g+b < 32768) + add = 1; + + if (add != 0) + { + if (g >= r && g >= b) + g += add; + else if (r >= g && r >= b) + r += add; + else + b += add; + } + + /* Check for an internal error. */ + if (r+g+b != 32768) + png_error(png_ptr, + "internal error handling cHRM coefficients"); + + else + { + png_ptr->rgb_to_gray_red_coeff = (png_uint_16)r; + png_ptr->rgb_to_gray_green_coeff = (png_uint_16)g; + } + } + + /* This is a png_error at present even though it could be ignored - + * it should never happen, but it is important that if it does, the + * bug is fixed. + */ + else + png_error(png_ptr, "internal error handling cHRM->XYZ"); + } +} +#endif + +#endif /* COLORSPACE */ + +void /* PRIVATE */ +png_check_IHDR(png_const_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type) +{ + int error = 0; + + /* Check for width and height valid values */ + if (width == 0) + { + png_warning(png_ptr, "Image width is zero in IHDR"); + error = 1; + } + + if (height == 0) + { + png_warning(png_ptr, "Image height is zero in IHDR"); + error = 1; + } + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (width > png_ptr->user_width_max) + +# else + if (width > PNG_USER_WIDTH_MAX) +# endif + { + png_warning(png_ptr, "Image width exceeds user limit in IHDR"); + error = 1; + } + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + if (height > png_ptr->user_height_max) +# else + if (height > PNG_USER_HEIGHT_MAX) +# endif + { + png_warning(png_ptr, "Image height exceeds user limit in IHDR"); + error = 1; + } + + if (width > PNG_UINT_31_MAX) + { + png_warning(png_ptr, "Invalid image width in IHDR"); + error = 1; + } + + if (height > PNG_UINT_31_MAX) + { + png_warning(png_ptr, "Invalid image height in IHDR"); + error = 1; + } + + if (width > (PNG_UINT_32_MAX + >> 3) /* 8-byte RGBA pixels */ + - 48 /* bigrowbuf hack */ + - 1 /* filter byte */ + - 7*8 /* rounding of width to multiple of 8 pixels */ + - 8) /* extra max_pixel_depth pad */ + png_warning(png_ptr, "Width is too large for libpng to process pixels"); + + /* Check other values */ + if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && + bit_depth != 8 && bit_depth != 16) + { + png_warning(png_ptr, "Invalid bit depth in IHDR"); + error = 1; + } + + if (color_type < 0 || color_type == 1 || + color_type == 5 || color_type > 6) + { + png_warning(png_ptr, "Invalid color type in IHDR"); + error = 1; + } + + if (((color_type == PNG_COLOR_TYPE_PALETTE) && bit_depth > 8) || + ((color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA || + color_type == PNG_COLOR_TYPE_RGB_ALPHA) && bit_depth < 8)) + { + png_warning(png_ptr, "Invalid color type/bit depth combination in IHDR"); + error = 1; + } + + if (interlace_type >= PNG_INTERLACE_LAST) + { + png_warning(png_ptr, "Unknown interlace method in IHDR"); + error = 1; + } + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + { + png_warning(png_ptr, "Unknown compression method in IHDR"); + error = 1; + } + +# ifdef PNG_MNG_FEATURES_SUPPORTED + /* Accept filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not read a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) && + png_ptr->mng_features_permitted) + png_warning(png_ptr, "MNG features are not allowed in a PNG datastream"); + + if (filter_type != PNG_FILTER_TYPE_BASE) + { + if (!((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) && + (filter_type == PNG_INTRAPIXEL_DIFFERENCING) && + ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) == 0) && + (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_RGB_ALPHA))) + { + png_warning(png_ptr, "Unknown filter method in IHDR"); + error = 1; + } + + if (png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) + { + png_warning(png_ptr, "Invalid filter method in IHDR"); + error = 1; + } + } + +# else + if (filter_type != PNG_FILTER_TYPE_BASE) + { + png_warning(png_ptr, "Unknown filter method in IHDR"); + error = 1; + } +# endif + + if (error == 1) + png_error(png_ptr, "Invalid IHDR data"); +} + +#if defined(PNG_sCAL_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) +/* ASCII to fp functions */ +/* Check an ASCII formated floating point value, see the more detailed + * comments in pngpriv.h + */ +/* The following is used internally to preserve the sticky flags */ +#define png_fp_add(state, flags) ((state) |= (flags)) +#define png_fp_set(state, value) ((state) = (value) | ((state) & PNG_FP_STICKY)) + +int /* PRIVATE */ +png_check_fp_number(png_const_charp string, png_size_t size, int *statep, + png_size_tp whereami) +{ + int state = *statep; + png_size_t i = *whereami; + + while (i < size) + { + int type; + /* First find the type of the next character */ + switch (string[i]) + { + case 43: type = PNG_FP_SAW_SIGN; break; + case 45: type = PNG_FP_SAW_SIGN + PNG_FP_NEGATIVE; break; + case 46: type = PNG_FP_SAW_DOT; break; + case 48: type = PNG_FP_SAW_DIGIT; break; + case 49: case 50: case 51: case 52: + case 53: case 54: case 55: case 56: + case 57: type = PNG_FP_SAW_DIGIT + PNG_FP_NONZERO; break; + case 69: + case 101: type = PNG_FP_SAW_E; break; + default: goto PNG_FP_End; + } + + /* Now deal with this type according to the current + * state, the type is arranged to not overlap the + * bits of the PNG_FP_STATE. + */ + switch ((state & PNG_FP_STATE) + (type & PNG_FP_SAW_ANY)) + { + case PNG_FP_INTEGER + PNG_FP_SAW_SIGN: + if (state & PNG_FP_SAW_ANY) + goto PNG_FP_End; /* not a part of the number */ + + png_fp_add(state, type); + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_DOT: + /* Ok as trailer, ok as lead of fraction. */ + if (state & PNG_FP_SAW_DOT) /* two dots */ + goto PNG_FP_End; + + else if (state & PNG_FP_SAW_DIGIT) /* trailing dot? */ + png_fp_add(state, type); + + else + png_fp_set(state, PNG_FP_FRACTION | type); + + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_DIGIT: + if (state & PNG_FP_SAW_DOT) /* delayed fraction */ + png_fp_set(state, PNG_FP_FRACTION | PNG_FP_SAW_DOT); + + png_fp_add(state, type | PNG_FP_WAS_VALID); + + break; + + case PNG_FP_INTEGER + PNG_FP_SAW_E: + if ((state & PNG_FP_SAW_DIGIT) == 0) + goto PNG_FP_End; + + png_fp_set(state, PNG_FP_EXPONENT); + + break; + + /* case PNG_FP_FRACTION + PNG_FP_SAW_SIGN: + goto PNG_FP_End; ** no sign in fraction */ + + /* case PNG_FP_FRACTION + PNG_FP_SAW_DOT: + goto PNG_FP_End; ** Because SAW_DOT is always set */ + + case PNG_FP_FRACTION + PNG_FP_SAW_DIGIT: + png_fp_add(state, type | PNG_FP_WAS_VALID); + break; + + case PNG_FP_FRACTION + PNG_FP_SAW_E: + /* This is correct because the trailing '.' on an + * integer is handled above - so we can only get here + * with the sequence ".E" (with no preceding digits). + */ + if ((state & PNG_FP_SAW_DIGIT) == 0) + goto PNG_FP_End; + + png_fp_set(state, PNG_FP_EXPONENT); + + break; + + case PNG_FP_EXPONENT + PNG_FP_SAW_SIGN: + if (state & PNG_FP_SAW_ANY) + goto PNG_FP_End; /* not a part of the number */ + + png_fp_add(state, PNG_FP_SAW_SIGN); + + break; + + /* case PNG_FP_EXPONENT + PNG_FP_SAW_DOT: + goto PNG_FP_End; */ + + case PNG_FP_EXPONENT + PNG_FP_SAW_DIGIT: + png_fp_add(state, PNG_FP_SAW_DIGIT | PNG_FP_WAS_VALID); + + break; + + /* case PNG_FP_EXPONEXT + PNG_FP_SAW_E: + goto PNG_FP_End; */ + + default: goto PNG_FP_End; /* I.e. break 2 */ + } + + /* The character seems ok, continue. */ + ++i; + } + +PNG_FP_End: + /* Here at the end, update the state and return the correct + * return code. + */ + *statep = state; + *whereami = i; + + return (state & PNG_FP_SAW_DIGIT) != 0; +} + + +/* The same but for a complete string. */ +int +png_check_fp_string(png_const_charp string, png_size_t size) +{ + int state=0; + png_size_t char_index=0; + + if (png_check_fp_number(string, size, &state, &char_index) && + (char_index == size || string[char_index] == 0)) + return state /* must be non-zero - see above */; + + return 0; /* i.e. fail */ +} +#endif /* pCAL or sCAL */ + +#ifdef PNG_sCAL_SUPPORTED +# ifdef PNG_FLOATING_POINT_SUPPORTED +/* Utility used below - a simple accurate power of ten from an integral + * exponent. + */ +static double +png_pow10(int power) +{ + int recip = 0; + double d = 1; + + /* Handle negative exponent with a reciprocal at the end because + * 10 is exact whereas .1 is inexact in base 2 + */ + if (power < 0) + { + if (power < DBL_MIN_10_EXP) return 0; + recip = 1, power = -power; + } + + if (power > 0) + { + /* Decompose power bitwise. */ + double mult = 10; + do + { + if (power & 1) d *= mult; + mult *= mult; + power >>= 1; + } + while (power > 0); + + if (recip) d = 1/d; + } + /* else power is 0 and d is 1 */ + + return d; +} + +/* Function to format a floating point value in ASCII with a given + * precision. + */ +void /* PRIVATE */ +png_ascii_from_fp(png_const_structrp png_ptr, png_charp ascii, png_size_t size, + double fp, unsigned int precision) +{ + /* We use standard functions from math.h, but not printf because + * that would require stdio. The caller must supply a buffer of + * sufficient size or we will png_error. The tests on size and + * the space in ascii[] consumed are indicated below. + */ + if (precision < 1) + precision = DBL_DIG; + + /* Enforce the limit of the implementation precision too. */ + if (precision > DBL_DIG+1) + precision = DBL_DIG+1; + + /* Basic sanity checks */ + if (size >= precision+5) /* See the requirements below. */ + { + if (fp < 0) + { + fp = -fp; + *ascii++ = 45; /* '-' PLUS 1 TOTAL 1 */ + --size; + } + + if (fp >= DBL_MIN && fp <= DBL_MAX) + { + int exp_b10; /* A base 10 exponent */ + double base; /* 10^exp_b10 */ + + /* First extract a base 10 exponent of the number, + * the calculation below rounds down when converting + * from base 2 to base 10 (multiply by log10(2) - + * 0.3010, but 77/256 is 0.3008, so exp_b10 needs to + * be increased. Note that the arithmetic shift + * performs a floor() unlike C arithmetic - using a + * C multiply would break the following for negative + * exponents. + */ + (void)frexp(fp, &exp_b10); /* exponent to base 2 */ + + exp_b10 = (exp_b10 * 77) >> 8; /* <= exponent to base 10 */ + + /* Avoid underflow here. */ + base = png_pow10(exp_b10); /* May underflow */ + + while (base < DBL_MIN || base < fp) + { + /* And this may overflow. */ + double test = png_pow10(exp_b10+1); + + if (test <= DBL_MAX) + ++exp_b10, base = test; + + else + break; + } + + /* Normalize fp and correct exp_b10, after this fp is in the + * range [.1,1) and exp_b10 is both the exponent and the digit + * *before* which the decimal point should be inserted + * (starting with 0 for the first digit). Note that this + * works even if 10^exp_b10 is out of range because of the + * test on DBL_MAX above. + */ + fp /= base; + while (fp >= 1) fp /= 10, ++exp_b10; + + /* Because of the code above fp may, at this point, be + * less than .1, this is ok because the code below can + * handle the leading zeros this generates, so no attempt + * is made to correct that here. + */ + + { + int czero, clead, cdigits; + char exponent[10]; + + /* Allow up to two leading zeros - this will not lengthen + * the number compared to using E-n. + */ + if (exp_b10 < 0 && exp_b10 > -3) /* PLUS 3 TOTAL 4 */ + { + czero = -exp_b10; /* PLUS 2 digits: TOTAL 3 */ + exp_b10 = 0; /* Dot added below before first output. */ + } + else + czero = 0; /* No zeros to add */ + + /* Generate the digit list, stripping trailing zeros and + * inserting a '.' before a digit if the exponent is 0. + */ + clead = czero; /* Count of leading zeros */ + cdigits = 0; /* Count of digits in list. */ + + do + { + double d; + + fp *= 10; + /* Use modf here, not floor and subtract, so that + * the separation is done in one step. At the end + * of the loop don't break the number into parts so + * that the final digit is rounded. + */ + if (cdigits+czero-clead+1 < (int)precision) + fp = modf(fp, &d); + + else + { + d = floor(fp + .5); + + if (d > 9) + { + /* Rounding up to 10, handle that here. */ + if (czero > 0) + { + --czero, d = 1; + if (cdigits == 0) --clead; + } + else + { + while (cdigits > 0 && d > 9) + { + int ch = *--ascii; + + if (exp_b10 != (-1)) + ++exp_b10; + + else if (ch == 46) + { + ch = *--ascii, ++size; + /* Advance exp_b10 to '1', so that the + * decimal point happens after the + * previous digit. + */ + exp_b10 = 1; + } + + --cdigits; + d = ch - 47; /* I.e. 1+(ch-48) */ + } + + /* Did we reach the beginning? If so adjust the + * exponent but take into account the leading + * decimal point. + */ + if (d > 9) /* cdigits == 0 */ + { + if (exp_b10 == (-1)) + { + /* Leading decimal point (plus zeros?), if + * we lose the decimal point here it must + * be reentered below. + */ + int ch = *--ascii; + + if (ch == 46) + ++size, exp_b10 = 1; + + /* Else lost a leading zero, so 'exp_b10' is + * still ok at (-1) + */ + } + else + ++exp_b10; + + /* In all cases we output a '1' */ + d = 1; + } + } + } + fp = 0; /* Guarantees termination below. */ + } + + if (d == 0) + { + ++czero; + if (cdigits == 0) ++clead; + } + else + { + /* Included embedded zeros in the digit count. */ + cdigits += czero - clead; + clead = 0; + + while (czero > 0) + { + /* exp_b10 == (-1) means we just output the decimal + * place - after the DP don't adjust 'exp_b10' any + * more! + */ + if (exp_b10 != (-1)) + { + if (exp_b10 == 0) *ascii++ = 46, --size; + /* PLUS 1: TOTAL 4 */ + --exp_b10; + } + *ascii++ = 48, --czero; + } + + if (exp_b10 != (-1)) + { + if (exp_b10 == 0) *ascii++ = 46, --size; /* counted + above */ + --exp_b10; + } + *ascii++ = (char)(48 + (int)d), ++cdigits; + } + } + while (cdigits+czero-clead < (int)precision && fp > DBL_MIN); + + /* The total output count (max) is now 4+precision */ + + /* Check for an exponent, if we don't need one we are + * done and just need to terminate the string. At + * this point exp_b10==(-1) is effectively if flag - it got + * to '-1' because of the decrement after outputing + * the decimal point above (the exponent required is + * *not* -1!) + */ + if (exp_b10 >= (-1) && exp_b10 <= 2) + { + /* The following only happens if we didn't output the + * leading zeros above for negative exponent, so this + * doest add to the digit requirement. Note that the + * two zeros here can only be output if the two leading + * zeros were *not* output, so this doesn't increase + * the output count. + */ + while (--exp_b10 >= 0) *ascii++ = 48; + + *ascii = 0; + + /* Total buffer requirement (including the '\0') is + * 5+precision - see check at the start. + */ + return; + } + + /* Here if an exponent is required, adjust size for + * the digits we output but did not count. The total + * digit output here so far is at most 1+precision - no + * decimal point and no leading or trailing zeros have + * been output. + */ + size -= cdigits; + + *ascii++ = 69, --size; /* 'E': PLUS 1 TOTAL 2+precision */ + + /* The following use of an unsigned temporary avoids ambiguities in + * the signed arithmetic on exp_b10 and permits GCC at least to do + * better optimization. + */ + { + unsigned int uexp_b10; + + if (exp_b10 < 0) + { + *ascii++ = 45, --size; /* '-': PLUS 1 TOTAL 3+precision */ + uexp_b10 = -exp_b10; + } + + else + uexp_b10 = exp_b10; + + cdigits = 0; + + while (uexp_b10 > 0) + { + exponent[cdigits++] = (char)(48 + uexp_b10 % 10); + uexp_b10 /= 10; + } + } + + /* Need another size check here for the exponent digits, so + * this need not be considered above. + */ + if ((int)size > cdigits) + { + while (cdigits > 0) *ascii++ = exponent[--cdigits]; + + *ascii = 0; + + return; + } + } + } + else if (!(fp >= DBL_MIN)) + { + *ascii++ = 48; /* '0' */ + *ascii = 0; + return; + } + else + { + *ascii++ = 105; /* 'i' */ + *ascii++ = 110; /* 'n' */ + *ascii++ = 102; /* 'f' */ + *ascii = 0; + return; + } + } + + /* Here on buffer too small. */ + png_error(png_ptr, "ASCII conversion buffer too small"); +} + +# endif /* FLOATING_POINT */ + +# ifdef PNG_FIXED_POINT_SUPPORTED +/* Function to format a fixed point value in ASCII. + */ +void /* PRIVATE */ +png_ascii_from_fixed(png_const_structrp png_ptr, png_charp ascii, + png_size_t size, png_fixed_point fp) +{ + /* Require space for 10 decimal digits, a decimal point, a minus sign and a + * trailing \0, 13 characters: + */ + if (size > 12) + { + png_uint_32 num; + + /* Avoid overflow here on the minimum integer. */ + if (fp < 0) + *ascii++ = 45, --size, num = -fp; + else + num = fp; + + if (num <= 0x80000000) /* else overflowed */ + { + unsigned int ndigits = 0, first = 16 /* flag value */; + char digits[10]; + + while (num) + { + /* Split the low digit off num: */ + unsigned int tmp = num/10; + num -= tmp*10; + digits[ndigits++] = (char)(48 + num); + /* Record the first non-zero digit, note that this is a number + * starting at 1, it's not actually the array index. + */ + if (first == 16 && num > 0) + first = ndigits; + num = tmp; + } + + if (ndigits > 0) + { + while (ndigits > 5) *ascii++ = digits[--ndigits]; + /* The remaining digits are fractional digits, ndigits is '5' or + * smaller at this point. It is certainly not zero. Check for a + * non-zero fractional digit: + */ + if (first <= 5) + { + unsigned int i; + *ascii++ = 46; /* decimal point */ + /* ndigits may be <5 for small numbers, output leading zeros + * then ndigits digits to first: + */ + i = 5; + while (ndigits < i) *ascii++ = 48, --i; + while (ndigits >= first) *ascii++ = digits[--ndigits]; + /* Don't output the trailing zeros! */ + } + } + else + *ascii++ = 48; + + /* And null terminate the string: */ + *ascii = 0; + return; + } + } + + /* Here on buffer too small. */ + png_error(png_ptr, "ASCII conversion buffer too small"); +} +# endif /* FIXED_POINT */ +#endif /* READ_SCAL */ + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_gAMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) || \ + defined(PNG_sCAL_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)) || \ + (defined(PNG_sCAL_SUPPORTED) && \ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED)) +png_fixed_point +png_fixed(png_const_structrp png_ptr, double fp, png_const_charp) +{ + double r = floor(100000 * fp + .5); + + if (r > 2147483647. || r < -2147483648.) + png_fixed_error(png_ptr, text); + + return (png_fixed_point)r; +} +#endif + +#if defined(PNG_READ_GAMMA_SUPPORTED) || \ + defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) +/* muldiv functions */ +/* This API takes signed arguments and rounds the result to the nearest + * integer (or, for a fixed point number - the standard argument - to + * the nearest .00001). Overflow and divide by zero are signalled in + * the result, a boolean - true on success, false on overflow. + */ +int +png_muldiv(png_fixed_point_p res, png_fixed_point a, png_int_32 times, + png_int_32 divisor) +{ + /* Return a * times / divisor, rounded. */ + if (divisor != 0) + { + if (a == 0 || times == 0) + { + *res = 0; + return 1; + } + else + { +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = a; + r *= times; + r /= divisor; + r = floor(r+.5); + + /* A png_fixed_point is a 32-bit integer. */ + if (r <= 2147483647. && r >= -2147483648.) + { + *res = (png_fixed_point)r; + return 1; + } +#else + int negative = 0; + png_uint_32 A, T, D; + png_uint_32 s16, s32, s00; + + if (a < 0) + negative = 1, A = -a; + else + A = a; + + if (times < 0) + negative = !negative, T = -times; + else + T = times; + + if (divisor < 0) + negative = !negative, D = -divisor; + else + D = divisor; + + /* Following can't overflow because the arguments only + * have 31 bits each, however the result may be 32 bits. + */ + s16 = (A >> 16) * (T & 0xffff) + + (A & 0xffff) * (T >> 16); + /* Can't overflow because the a*times bit is only 30 + * bits at most. + */ + s32 = (A >> 16) * (T >> 16) + (s16 >> 16); + s00 = (A & 0xffff) * (T & 0xffff); + + s16 = (s16 & 0xffff) << 16; + s00 += s16; + + if (s00 < s16) + ++s32; /* carry */ + + if (s32 < D) /* else overflow */ + { + /* s32.s00 is now the 64-bit product, do a standard + * division, we know that s32 < D, so the maximum + * required shift is 31. + */ + int bitshift = 32; + png_fixed_point result = 0; /* NOTE: signed */ + + while (--bitshift >= 0) + { + png_uint_32 d32, d00; + + if (bitshift > 0) + d32 = D >> (32-bitshift), d00 = D << bitshift; + + else + d32 = 0, d00 = D; + + if (s32 > d32) + { + if (s00 < d00) --s32; /* carry */ + s32 -= d32, s00 -= d00, result += 1<= d00) + s32 = 0, s00 -= d00, result += 1<= (D >> 1)) + ++result; + + if (negative) + result = -result; + + /* Check for overflow. */ + if ((negative && result <= 0) || (!negative && result >= 0)) + { + *res = result; + return 1; + } + } +#endif + } + } + + return 0; +} +#endif /* READ_GAMMA || INCH_CONVERSIONS */ + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_INCH_CONVERSIONS_SUPPORTED) +/* The following is for when the caller doesn't much care about the + * result. + */ +png_fixed_point +png_muldiv_warn(png_const_structrp png_ptr, png_fixed_point a, png_int_32 times, + png_int_32 divisor) +{ + png_fixed_point result; + + if (png_muldiv(&result, a, times, divisor)) + return result; + + png_warning(png_ptr, "fixed point overflow ignored"); + return 0; +} +#endif + +#ifdef PNG_GAMMA_SUPPORTED /* more fixed point functions for gamma */ +/* Calculate a reciprocal, return 0 on div-by-zero or overflow. */ +png_fixed_point +png_reciprocal(png_fixed_point a) +{ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = floor(1E10/a+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; +#else + png_fixed_point res; + + if (png_muldiv(&res, 100000, 100000, a)) + return res; +#endif + + return 0; /* error/overflow */ +} + +/* This is the shared test on whether a gamma value is 'significant' - whether + * it is worth doing gamma correction. + */ +int /* PRIVATE */ +png_gamma_significant(png_fixed_point gamma_val) +{ + return gamma_val < PNG_FP_1 - PNG_GAMMA_THRESHOLD_FIXED || + gamma_val > PNG_FP_1 + PNG_GAMMA_THRESHOLD_FIXED; +} +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* A local convenience routine. */ +static png_fixed_point +png_product2(png_fixed_point a, png_fixed_point b) +{ + /* The required result is 1/a * 1/b; the following preserves accuracy. */ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = a * 1E-5; + r *= b; + r = floor(r+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; +#else + png_fixed_point res; + + if (png_muldiv(&res, a, b, 100000)) + return res; +#endif + + return 0; /* overflow */ +} + +/* The inverse of the above. */ +png_fixed_point +png_reciprocal2(png_fixed_point a, png_fixed_point b) +{ + /* The required result is 1/a * 1/b; the following preserves accuracy. */ +#ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = 1E15/a; + r /= b; + r = floor(r+.5); + + if (r <= 2147483647. && r >= -2147483648.) + return (png_fixed_point)r; +#else + /* This may overflow because the range of png_fixed_point isn't symmetric, + * but this API is only used for the product of file and screen gamma so it + * doesn't matter that the smallest number it can produce is 1/21474, not + * 1/100000 + */ + png_fixed_point res = png_product2(a, b); + + if (res != 0) + return png_reciprocal(res); +#endif + + return 0; /* overflow */ +} +#endif /* READ_GAMMA */ + +#ifdef PNG_READ_GAMMA_SUPPORTED /* gamma table code */ +#ifndef PNG_FLOATING_ARITHMETIC_SUPPORTED +/* Fixed point gamma. + * + * The code to calculate the tables used below can be found in the shell script + * contrib/tools/intgamma.sh + * + * To calculate gamma this code implements fast log() and exp() calls using only + * fixed point arithmetic. This code has sufficient precision for either 8-bit + * or 16-bit sample values. + * + * The tables used here were calculated using simple 'bc' programs, but C double + * precision floating point arithmetic would work fine. + * + * 8-bit log table + * This is a table of -log(value/255)/log(2) for 'value' in the range 128 to + * 255, so it's the base 2 logarithm of a normalized 8-bit floating point + * mantissa. The numbers are 32-bit fractions. + */ +static const png_uint_32 +png_8bit_l2[128] = +{ + 4270715492U, 4222494797U, 4174646467U, 4127164793U, 4080044201U, 4033279239U, + 3986864580U, 3940795015U, 3895065449U, 3849670902U, 3804606499U, 3759867474U, + 3715449162U, 3671346997U, 3627556511U, 3584073329U, 3540893168U, 3498011834U, + 3455425220U, 3413129301U, 3371120137U, 3329393864U, 3287946700U, 3246774933U, + 3205874930U, 3165243125U, 3124876025U, 3084770202U, 3044922296U, 3005329011U, + 2965987113U, 2926893432U, 2888044853U, 2849438323U, 2811070844U, 2772939474U, + 2735041326U, 2697373562U, 2659933400U, 2622718104U, 2585724991U, 2548951424U, + 2512394810U, 2476052606U, 2439922311U, 2404001468U, 2368287663U, 2332778523U, + 2297471715U, 2262364947U, 2227455964U, 2192742551U, 2158222529U, 2123893754U, + 2089754119U, 2055801552U, 2022034013U, 1988449497U, 1955046031U, 1921821672U, + 1888774511U, 1855902668U, 1823204291U, 1790677560U, 1758320682U, 1726131893U, + 1694109454U, 1662251657U, 1630556815U, 1599023271U, 1567649391U, 1536433567U, + 1505374214U, 1474469770U, 1443718700U, 1413119487U, 1382670639U, 1352370686U, + 1322218179U, 1292211689U, 1262349810U, 1232631153U, 1203054352U, 1173618059U, + 1144320946U, 1115161701U, 1086139034U, 1057251672U, 1028498358U, 999877854U, + 971388940U, 943030410U, 914801076U, 886699767U, 858725327U, 830876614U, + 803152505U, 775551890U, 748073672U, 720716771U, 693480120U, 666362667U, + 639363374U, 612481215U, 585715177U, 559064263U, 532527486U, 506103872U, + 479792461U, 453592303U, 427502463U, 401522014U, 375650043U, 349885648U, + 324227938U, 298676034U, 273229066U, 247886176U, 222646516U, 197509248U, + 172473545U, 147538590U, 122703574U, 97967701U, 73330182U, 48790236U, + 24347096U, 0U + +#if 0 + /* The following are the values for 16-bit tables - these work fine for the + * 8-bit conversions but produce very slightly larger errors in the 16-bit + * log (about 1.2 as opposed to 0.7 absolute error in the final value). To + * use these all the shifts below must be adjusted appropriately. + */ + 65166, 64430, 63700, 62976, 62257, 61543, 60835, 60132, 59434, 58741, 58054, + 57371, 56693, 56020, 55352, 54689, 54030, 53375, 52726, 52080, 51439, 50803, + 50170, 49542, 48918, 48298, 47682, 47070, 46462, 45858, 45257, 44661, 44068, + 43479, 42894, 42312, 41733, 41159, 40587, 40020, 39455, 38894, 38336, 37782, + 37230, 36682, 36137, 35595, 35057, 34521, 33988, 33459, 32932, 32408, 31887, + 31369, 30854, 30341, 29832, 29325, 28820, 28319, 27820, 27324, 26830, 26339, + 25850, 25364, 24880, 24399, 23920, 23444, 22970, 22499, 22029, 21562, 21098, + 20636, 20175, 19718, 19262, 18808, 18357, 17908, 17461, 17016, 16573, 16132, + 15694, 15257, 14822, 14390, 13959, 13530, 13103, 12678, 12255, 11834, 11415, + 10997, 10582, 10168, 9756, 9346, 8937, 8531, 8126, 7723, 7321, 6921, 6523, + 6127, 5732, 5339, 4947, 4557, 4169, 3782, 3397, 3014, 2632, 2251, 1872, 1495, + 1119, 744, 372 +#endif +}; + +static png_int_32 +png_log8bit(unsigned int x) +{ + unsigned int lg2 = 0; + /* Each time 'x' is multiplied by 2, 1 must be subtracted off the final log, + * because the log is actually negate that means adding 1. The final + * returned value thus has the range 0 (for 255 input) to 7.994 (for 1 + * input), return -1 for the overflow (log 0) case, - so the result is + * always at most 19 bits. + */ + if ((x &= 0xff) == 0) + return -1; + + if ((x & 0xf0) == 0) + lg2 = 4, x <<= 4; + + if ((x & 0xc0) == 0) + lg2 += 2, x <<= 2; + + if ((x & 0x80) == 0) + lg2 += 1, x <<= 1; + + /* result is at most 19 bits, so this cast is safe: */ + return (png_int_32)((lg2 << 16) + ((png_8bit_l2[x-128]+32768)>>16)); +} + +/* The above gives exact (to 16 binary places) log2 values for 8-bit images, + * for 16-bit images we use the most significant 8 bits of the 16-bit value to + * get an approximation then multiply the approximation by a correction factor + * determined by the remaining up to 8 bits. This requires an additional step + * in the 16-bit case. + * + * We want log2(value/65535), we have log2(v'/255), where: + * + * value = v' * 256 + v'' + * = v' * f + * + * So f is value/v', which is equal to (256+v''/v') since v' is in the range 128 + * to 255 and v'' is in the range 0 to 255 f will be in the range 256 to less + * than 258. The final factor also needs to correct for the fact that our 8-bit + * value is scaled by 255, whereas the 16-bit values must be scaled by 65535. + * + * This gives a final formula using a calculated value 'x' which is value/v' and + * scaling by 65536 to match the above table: + * + * log2(x/257) * 65536 + * + * Since these numbers are so close to '1' we can use simple linear + * interpolation between the two end values 256/257 (result -368.61) and 258/257 + * (result 367.179). The values used below are scaled by a further 64 to give + * 16-bit precision in the interpolation: + * + * Start (256): -23591 + * Zero (257): 0 + * End (258): 23499 + */ +static png_int_32 +png_log16bit(png_uint_32 x) +{ + unsigned int lg2 = 0; + + /* As above, but now the input has 16 bits. */ + if ((x &= 0xffff) == 0) + return -1; + + if ((x & 0xff00) == 0) + lg2 = 8, x <<= 8; + + if ((x & 0xf000) == 0) + lg2 += 4, x <<= 4; + + if ((x & 0xc000) == 0) + lg2 += 2, x <<= 2; + + if ((x & 0x8000) == 0) + lg2 += 1, x <<= 1; + + /* Calculate the base logarithm from the top 8 bits as a 28-bit fractional + * value. + */ + lg2 <<= 28; + lg2 += (png_8bit_l2[(x>>8)-128]+8) >> 4; + + /* Now we need to interpolate the factor, this requires a division by the top + * 8 bits. Do this with maximum precision. + */ + x = ((x << 16) + (x >> 9)) / (x >> 8); + + /* Since we divided by the top 8 bits of 'x' there will be a '1' at 1<<24, + * the value at 1<<16 (ignoring this) will be 0 or 1; this gives us exactly + * 16 bits to interpolate to get the low bits of the result. Round the + * answer. Note that the end point values are scaled by 64 to retain overall + * precision and that 'lg2' is current scaled by an extra 12 bits, so adjust + * the overall scaling by 6-12. Round at every step. + */ + x -= 1U << 24; + + if (x <= 65536U) /* <= '257' */ + lg2 += ((23591U * (65536U-x)) + (1U << (16+6-12-1))) >> (16+6-12); + + else + lg2 -= ((23499U * (x-65536U)) + (1U << (16+6-12-1))) >> (16+6-12); + + /* Safe, because the result can't have more than 20 bits: */ + return (png_int_32)((lg2 + 2048) >> 12); +} + +/* The 'exp()' case must invert the above, taking a 20-bit fixed point + * logarithmic value and returning a 16 or 8-bit number as appropriate. In + * each case only the low 16 bits are relevant - the fraction - since the + * integer bits (the top 4) simply determine a shift. + * + * The worst case is the 16-bit distinction between 65535 and 65534, this + * requires perhaps spurious accuracty in the decoding of the logarithm to + * distinguish log2(65535/65534.5) - 10^-5 or 17 bits. There is little chance + * of getting this accuracy in practice. + * + * To deal with this the following exp() function works out the exponent of the + * frational part of the logarithm by using an accurate 32-bit value from the + * top four fractional bits then multiplying in the remaining bits. + */ +static const png_uint_32 +png_32bit_exp[16] = +{ + /* NOTE: the first entry is deliberately set to the maximum 32-bit value. */ + 4294967295U, 4112874773U, 3938502376U, 3771522796U, 3611622603U, 3458501653U, + 3311872529U, 3171459999U, 3037000500U, 2908241642U, 2784941738U, 2666869345U, + 2553802834U, 2445529972U, 2341847524U, 2242560872U +}; + +/* Adjustment table; provided to explain the numbers in the code below. */ +#if 0 +for (i=11;i>=0;--i){ print i, " ", (1 - e(-(2^i)/65536*l(2))) * 2^(32-i), "\n"} + 11 44937.64284865548751208448 + 10 45180.98734845585101160448 + 9 45303.31936980687359311872 + 8 45364.65110595323018870784 + 7 45395.35850361789624614912 + 6 45410.72259715102037508096 + 5 45418.40724413220722311168 + 4 45422.25021786898173001728 + 3 45424.17186732298419044352 + 2 45425.13273269940811464704 + 1 45425.61317555035558641664 + 0 45425.85339951654943850496 +#endif + +static png_uint_32 +png_exp(png_fixed_point x) +{ + if (x > 0 && x <= 0xfffff) /* Else overflow or zero (underflow) */ + { + /* Obtain a 4-bit approximation */ + png_uint_32 e = png_32bit_exp[(x >> 12) & 0xf]; + + /* Incorporate the low 12 bits - these decrease the returned value by + * multiplying by a number less than 1 if the bit is set. The multiplier + * is determined by the above table and the shift. Notice that the values + * converge on 45426 and this is used to allow linear interpolation of the + * low bits. + */ + if (x & 0x800) + e -= (((e >> 16) * 44938U) + 16U) >> 5; + + if (x & 0x400) + e -= (((e >> 16) * 45181U) + 32U) >> 6; + + if (x & 0x200) + e -= (((e >> 16) * 45303U) + 64U) >> 7; + + if (x & 0x100) + e -= (((e >> 16) * 45365U) + 128U) >> 8; + + if (x & 0x080) + e -= (((e >> 16) * 45395U) + 256U) >> 9; + + if (x & 0x040) + e -= (((e >> 16) * 45410U) + 512U) >> 10; + + /* And handle the low 6 bits in a single block. */ + e -= (((e >> 16) * 355U * (x & 0x3fU)) + 256U) >> 9; + + /* Handle the upper bits of x. */ + e >>= x >> 16; + return e; + } + + /* Check for overflow */ + if (x <= 0) + return png_32bit_exp[0]; + + /* Else underflow */ + return 0; +} + +static png_byte +png_exp8bit(png_fixed_point lg2) +{ + /* Get a 32-bit value: */ + png_uint_32 x = png_exp(lg2); + + /* Convert the 32-bit value to 0..255 by multiplying by 256-1, note that the + * second, rounding, step can't overflow because of the first, subtraction, + * step. + */ + x -= x >> 8; + return (png_byte)((x + 0x7fffffU) >> 24); +} + +static png_uint_16 +png_exp16bit(png_fixed_point lg2) +{ + /* Get a 32-bit value: */ + png_uint_32 x = png_exp(lg2); + + /* Convert the 32-bit value to 0..65535 by multiplying by 65536-1: */ + x -= x >> 16; + return (png_uint_16)((x + 32767U) >> 16); +} +#endif /* FLOATING_ARITHMETIC */ + +png_byte +png_gamma_8bit_correct(unsigned int value, png_fixed_point gamma_val) +{ + if (value > 0 && value < 255) + { +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = floor(255*pow(value/255.,gamma_val*.00001)+.5); + return (png_byte)r; +# else + png_int_32 lg2 = png_log8bit(value); + png_fixed_point res; + + if (png_muldiv(&res, gamma_val, lg2, PNG_FP_1)) + return png_exp8bit(res); + + /* Overflow. */ + value = 0; +# endif + } + + return (png_byte)value; +} + +png_uint_16 +png_gamma_16bit_correct(unsigned int value, png_fixed_point gamma_val) +{ + if (value > 0 && value < 65535) + { +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + double r = floor(65535*pow(value/65535.,gamma_val*.00001)+.5); + return (png_uint_16)r; +# else + png_int_32 lg2 = png_log16bit(value); + png_fixed_point res; + + if (png_muldiv(&res, gamma_val, lg2, PNG_FP_1)) + return png_exp16bit(res); + + /* Overflow. */ + value = 0; +# endif + } + + return (png_uint_16)value; +} + +/* This does the right thing based on the bit_depth field of the + * png_struct, interpreting values as 8-bit or 16-bit. While the result + * is nominally a 16-bit value if bit depth is 8 then the result is + * 8-bit (as are the arguments.) + */ +png_uint_16 /* PRIVATE */ +png_gamma_correct(png_structrp png_ptr, unsigned int value, + png_fixed_point gamma_val) +{ + if (png_ptr->bit_depth == 8) + return png_gamma_8bit_correct(value, gamma_val); + + else + return png_gamma_16bit_correct(value, gamma_val); +} + +/* Internal function to build a single 16-bit table - the table consists of + * 'num' 256 entry subtables, where 'num' is determined by 'shift' - the amount + * to shift the input values right (or 16-number_of_signifiant_bits). + * + * The caller is responsible for ensuring that the table gets cleaned up on + * png_error (i.e. if one of the mallocs below fails) - i.e. the *table argument + * should be somewhere that will be cleaned. + */ +static void +png_build_16bit_table(png_structrp png_ptr, png_uint_16pp *ptable, + PNG_CONST unsigned int shift, PNG_CONST png_fixed_point gamma_val) +{ + /* Various values derived from 'shift': */ + PNG_CONST unsigned int num = 1U << (8U - shift); + PNG_CONST unsigned int max = (1U << (16U - shift))-1U; + PNG_CONST unsigned int max_by_2 = 1U << (15U-shift); + unsigned int i; + + png_uint_16pp table = *ptable = + (png_uint_16pp)png_calloc(png_ptr, num * (sizeof (png_uint_16p))); + + for (i = 0; i < num; i++) + { + png_uint_16p sub_table = table[i] = + (png_uint_16p)png_malloc(png_ptr, 256 * (sizeof (png_uint_16))); + + /* The 'threshold' test is repeated here because it can arise for one of + * the 16-bit tables even if the others don't hit it. + */ + if (png_gamma_significant(gamma_val)) + { + /* The old code would overflow at the end and this would cause the + * 'pow' function to return a result >1, resulting in an + * arithmetic error. This code follows the spec exactly; ig is + * the recovered input sample, it always has 8-16 bits. + * + * We want input * 65535/max, rounded, the arithmetic fits in 32 + * bits (unsigned) so long as max <= 32767. + */ + unsigned int j; + for (j = 0; j < 256; j++) + { + png_uint_32 ig = (j << (8-shift)) + i; +# ifdef PNG_FLOATING_ARITHMETIC_SUPPORTED + /* Inline the 'max' scaling operation: */ + double d = floor(65535*pow(ig/(double)max, gamma_val*.00001)+.5); + sub_table[j] = (png_uint_16)d; +# else + if (shift) + ig = (ig * 65535U + max_by_2)/max; + + sub_table[j] = png_gamma_16bit_correct(ig, gamma_val); +# endif + } + } + else + { + /* We must still build a table, but do it the fast way. */ + unsigned int j; + + for (j = 0; j < 256; j++) + { + png_uint_32 ig = (j << (8-shift)) + i; + + if (shift) + ig = (ig * 65535U + max_by_2)/max; + + sub_table[j] = (png_uint_16)ig; + } + } + } +} + +/* NOTE: this function expects the *inverse* of the overall gamma transformation + * required. + */ +static void +png_build_16to8_table(png_structrp png_ptr, png_uint_16pp *ptable, + PNG_CONST unsigned int shift, PNG_CONST png_fixed_point gamma_val) +{ + PNG_CONST unsigned int num = 1U << (8U - shift); + PNG_CONST unsigned int max = (1U << (16U - shift))-1U; + unsigned int i; + png_uint_32 last; + + png_uint_16pp table = *ptable = + (png_uint_16pp)png_calloc(png_ptr, num * (sizeof (png_uint_16p))); + + /* 'num' is the number of tables and also the number of low bits of low + * bits of the input 16-bit value used to select a table. Each table is + * itself index by the high 8 bits of the value. + */ + for (i = 0; i < num; i++) + table[i] = (png_uint_16p)png_malloc(png_ptr, + 256 * (sizeof (png_uint_16))); + + /* 'gamma_val' is set to the reciprocal of the value calculated above, so + * pow(out,g) is an *input* value. 'last' is the last input value set. + * + * In the loop 'i' is used to find output values. Since the output is + * 8-bit there are only 256 possible values. The tables are set up to + * select the closest possible output value for each input by finding + * the input value at the boundary between each pair of output values + * and filling the table up to that boundary with the lower output + * value. + * + * The boundary values are 0.5,1.5..253.5,254.5. Since these are 9-bit + * values the code below uses a 16-bit value in i; the values start at + * 128.5 (for 0.5) and step by 257, for a total of 254 values (the last + * entries are filled with 255). Start i at 128 and fill all 'last' + * table entries <= 'max' + */ + last = 0; + for (i = 0; i < 255; ++i) /* 8-bit output value */ + { + /* Find the corresponding maximum input value */ + png_uint_16 out = (png_uint_16)(i * 257U); /* 16-bit output value */ + + /* Find the boundary value in 16 bits: */ + png_uint_32 bound = png_gamma_16bit_correct(out+128U, gamma_val); + + /* Adjust (round) to (16-shift) bits: */ + bound = (bound * max + 32768U)/65535U + 1U; + + while (last < bound) + { + table[last & (0xffU >> shift)][last >> (8U - shift)] = out; + last++; + } + } + + /* And fill in the final entries. */ + while (last < (num << 8)) + { + table[last & (0xff >> shift)][last >> (8U - shift)] = 65535U; + last++; + } +} + +/* Build a single 8-bit table: same as the 16-bit case but much simpler (and + * typically much faster). Note that libpng currently does no sBIT processing + * (apparently contrary to the spec) so a 256 entry table is always generated. + */ +static void +png_build_8bit_table(png_structrp png_ptr, png_bytepp ptable, + PNG_CONST png_fixed_point gamma_val) +{ + unsigned int i; + png_bytep table = *ptable = (png_bytep)png_malloc(png_ptr, 256); + + if (png_gamma_significant(gamma_val)) for (i=0; i<256; i++) + table[i] = png_gamma_8bit_correct(i, gamma_val); + + else for (i=0; i<256; ++i) + table[i] = (png_byte)i; +} + +/* Used from png_read_destroy and below to release the memory used by the gamma + * tables. + */ +void /* PRIVATE */ +png_destroy_gamma_table(png_structrp png_ptr) +{ + png_free(png_ptr, png_ptr->gamma_table); + png_ptr->gamma_table = NULL; + + if (png_ptr->gamma_16_table != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_table[i]); + } + png_free(png_ptr, png_ptr->gamma_16_table); + png_ptr->gamma_16_table = NULL; + } + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + png_free(png_ptr, png_ptr->gamma_from_1); + png_ptr->gamma_from_1 = NULL; + png_free(png_ptr, png_ptr->gamma_to_1); + png_ptr->gamma_to_1 = NULL; + + if (png_ptr->gamma_16_from_1 != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_from_1[i]); + } + png_free(png_ptr, png_ptr->gamma_16_from_1); + png_ptr->gamma_16_from_1 = NULL; + } + if (png_ptr->gamma_16_to_1 != NULL) + { + int i; + int istop = (1 << (8 - png_ptr->gamma_shift)); + for (i = 0; i < istop; i++) + { + png_free(png_ptr, png_ptr->gamma_16_to_1[i]); + } + png_free(png_ptr, png_ptr->gamma_16_to_1); + png_ptr->gamma_16_to_1 = NULL; + } +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ +} + +/* We build the 8- or 16-bit gamma tables here. Note that for 16-bit + * tables, we don't make a full table if we are reducing to 8-bit in + * the future. Note also how the gamma_16 tables are segmented so that + * we don't need to allocate > 64K chunks for a full 16-bit table. + */ +void /* PRIVATE */ +png_build_gamma_table(png_structrp png_ptr, int bit_depth) +{ + png_debug(1, "in png_build_gamma_table"); + + /* Remove any existing table; this copes with multiple calls to + * png_read_update_info. The warning is because building the gamma tables + * multiple times is a performance hit - it's harmless but the ability to call + * png_read_update_info() multiple times is new in 1.5.6 so it seems sensible + * to warn if the app introduces such a hit. + */ + if (png_ptr->gamma_table != NULL || png_ptr->gamma_16_table != NULL) + { + png_warning(png_ptr, "gamma table being rebuilt"); + png_destroy_gamma_table(png_ptr); + } + + if (bit_depth <= 8) + { + png_build_8bit_table(png_ptr, &png_ptr->gamma_table, + png_ptr->screen_gamma > 0 ? png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + if (png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) + { + png_build_8bit_table(png_ptr, &png_ptr->gamma_to_1, + png_reciprocal(png_ptr->colorspace.gamma)); + + png_build_8bit_table(png_ptr, &png_ptr->gamma_from_1, + png_ptr->screen_gamma > 0 ? png_reciprocal(png_ptr->screen_gamma) : + png_ptr->colorspace.gamma/* Probably doing rgb_to_gray */); + } +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ + } + else + { + png_byte shift, sig_bit; + + if (png_ptr->color_type & PNG_COLOR_MASK_COLOR) + { + sig_bit = png_ptr->sig_bit.red; + + if (png_ptr->sig_bit.green > sig_bit) + sig_bit = png_ptr->sig_bit.green; + + if (png_ptr->sig_bit.blue > sig_bit) + sig_bit = png_ptr->sig_bit.blue; + } + else + sig_bit = png_ptr->sig_bit.gray; + + /* 16-bit gamma code uses this equation: + * + * ov = table[(iv & 0xff) >> gamma_shift][iv >> 8] + * + * Where 'iv' is the input color value and 'ov' is the output value - + * pow(iv, gamma). + * + * Thus the gamma table consists of up to 256 256 entry tables. The table + * is selected by the (8-gamma_shift) most significant of the low 8 bits of + * the color value then indexed by the upper 8 bits: + * + * table[low bits][high 8 bits] + * + * So the table 'n' corresponds to all those 'iv' of: + * + * ..<(n+1 << gamma_shift)-1> + * + */ + if (sig_bit > 0 && sig_bit < 16U) + shift = (png_byte)(16U - sig_bit); /* shift == insignificant bits */ + + else + shift = 0; /* keep all 16 bits */ + + if (png_ptr->transformations & (PNG_16_TO_8 | PNG_SCALE_16_TO_8)) + { + /* PNG_MAX_GAMMA_8 is the number of bits to keep - effectively + * the significant bits in the *input* when the output will + * eventually be 8 bits. By default it is 11. + */ + if (shift < (16U - PNG_MAX_GAMMA_8)) + shift = (16U - PNG_MAX_GAMMA_8); + } + + if (shift > 8U) + shift = 8U; /* Guarantees at least one table! */ + + png_ptr->gamma_shift = shift; + +#ifdef PNG_16BIT_SUPPORTED + /* NOTE: prior to 1.5.4 this test used to include PNG_BACKGROUND (now + * PNG_COMPOSE). This effectively smashed the background calculation for + * 16-bit output because the 8-bit table assumes the result will be reduced + * to 8 bits. + */ + if (png_ptr->transformations & (PNG_16_TO_8 | PNG_SCALE_16_TO_8)) +#endif + png_build_16to8_table(png_ptr, &png_ptr->gamma_16_table, shift, + png_ptr->screen_gamma > 0 ? png_product2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); + +#ifdef PNG_16BIT_SUPPORTED + else + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_table, shift, + png_ptr->screen_gamma > 0 ? png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma) : PNG_FP_1); +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + if (png_ptr->transformations & (PNG_COMPOSE | PNG_RGB_TO_GRAY)) + { + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_to_1, shift, + png_reciprocal(png_ptr->colorspace.gamma)); + + /* Notice that the '16 from 1' table should be full precision, however + * the lookup on this table still uses gamma_shift, so it can't be. + * TODO: fix this. + */ + png_build_16bit_table(png_ptr, &png_ptr->gamma_16_from_1, shift, + png_ptr->screen_gamma > 0 ? png_reciprocal(png_ptr->screen_gamma) : + png_ptr->colorspace.gamma/* Probably doing rgb_to_gray */); + } +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ + } +} +#endif /* READ_GAMMA */ + +/* HARDWARE OPTION SUPPORT */ +#ifdef PNG_SET_OPTION_SUPPORTED +int PNGAPI +png_set_option(png_structrp png_ptr, int option, int onoff) +{ + if (png_ptr != NULL && option >= 0 && option < PNG_OPTION_NEXT && + (option & 1) == 0) + { + int mask = 3 << option; + int setting = (2 + (onoff != 0)) << option; + int current = png_ptr->options; + + png_ptr->options = (png_byte)((current & ~mask) | setting); + + return (current & mask) >> option; + } + + return PNG_OPTION_INVALID; +} +#endif + +/* sRGB support */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +/* sRGB conversion tables; these are machine generated with the code in + * contrib/tools/makesRGB.c. The actual sRGB transfer curve defined in the + * specification (see the article at http://en.wikipedia.org/wiki/SRGB) + * is used, not the gamma=1/2.2 approximation use elsewhere in libpng. + * The sRGB to linear table is exact (to the nearest 16 bit linear fraction). + * The inverse (linear to sRGB) table has accuracies as follows: + * + * For all possible (255*65535+1) input values: + * + * error: -0.515566 - 0.625971, 79441 (0.475369%) of readings inexact + * + * For the input values corresponding to the 65536 16-bit values: + * + * error: -0.513727 - 0.607759, 308 (0.469978%) of readings inexact + * + * In all cases the inexact readings are off by one. + */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* The convert-to-sRGB table is only currently required for read. */ +const png_uint_16 png_sRGB_table[256] = +{ + 0,20,40,60,80,99,119,139, + 159,179,199,219,241,264,288,313, + 340,367,396,427,458,491,526,562, + 599,637,677,718,761,805,851,898, + 947,997,1048,1101,1156,1212,1270,1330, + 1391,1453,1517,1583,1651,1720,1790,1863, + 1937,2013,2090,2170,2250,2333,2418,2504, + 2592,2681,2773,2866,2961,3058,3157,3258, + 3360,3464,3570,3678,3788,3900,4014,4129, + 4247,4366,4488,4611,4736,4864,4993,5124, + 5257,5392,5530,5669,5810,5953,6099,6246, + 6395,6547,6700,6856,7014,7174,7335,7500, + 7666,7834,8004,8177,8352,8528,8708,8889, + 9072,9258,9445,9635,9828,10022,10219,10417, + 10619,10822,11028,11235,11446,11658,11873,12090, + 12309,12530,12754,12980,13209,13440,13673,13909, + 14146,14387,14629,14874,15122,15371,15623,15878, + 16135,16394,16656,16920,17187,17456,17727,18001, + 18277,18556,18837,19121,19407,19696,19987,20281, + 20577,20876,21177,21481,21787,22096,22407,22721, + 23038,23357,23678,24002,24329,24658,24990,25325, + 25662,26001,26344,26688,27036,27386,27739,28094, + 28452,28813,29176,29542,29911,30282,30656,31033, + 31412,31794,32179,32567,32957,33350,33745,34143, + 34544,34948,35355,35764,36176,36591,37008,37429, + 37852,38278,38706,39138,39572,40009,40449,40891, + 41337,41785,42236,42690,43147,43606,44069,44534, + 45002,45473,45947,46423,46903,47385,47871,48359, + 48850,49344,49841,50341,50844,51349,51858,52369, + 52884,53401,53921,54445,54971,55500,56032,56567, + 57105,57646,58190,58737,59287,59840,60396,60955, + 61517,62082,62650,63221,63795,64372,64952,65535 +}; + +#endif /* simplified read only */ + +/* The base/delta tables are required for both read and write (but currently + * only the simplified versions.) + */ +const png_uint_16 png_sRGB_base[512] = +{ + 128,1782,3383,4644,5675,6564,7357,8074, + 8732,9346,9921,10463,10977,11466,11935,12384, + 12816,13233,13634,14024,14402,14769,15125,15473, + 15812,16142,16466,16781,17090,17393,17690,17981, + 18266,18546,18822,19093,19359,19621,19879,20133, + 20383,20630,20873,21113,21349,21583,21813,22041, + 22265,22487,22707,22923,23138,23350,23559,23767, + 23972,24175,24376,24575,24772,24967,25160,25352, + 25542,25730,25916,26101,26284,26465,26645,26823, + 27000,27176,27350,27523,27695,27865,28034,28201, + 28368,28533,28697,28860,29021,29182,29341,29500, + 29657,29813,29969,30123,30276,30429,30580,30730, + 30880,31028,31176,31323,31469,31614,31758,31902, + 32045,32186,32327,32468,32607,32746,32884,33021, + 33158,33294,33429,33564,33697,33831,33963,34095, + 34226,34357,34486,34616,34744,34873,35000,35127, + 35253,35379,35504,35629,35753,35876,35999,36122, + 36244,36365,36486,36606,36726,36845,36964,37083, + 37201,37318,37435,37551,37668,37783,37898,38013, + 38127,38241,38354,38467,38580,38692,38803,38915, + 39026,39136,39246,39356,39465,39574,39682,39790, + 39898,40005,40112,40219,40325,40431,40537,40642, + 40747,40851,40955,41059,41163,41266,41369,41471, + 41573,41675,41777,41878,41979,42079,42179,42279, + 42379,42478,42577,42676,42775,42873,42971,43068, + 43165,43262,43359,43456,43552,43648,43743,43839, + 43934,44028,44123,44217,44311,44405,44499,44592, + 44685,44778,44870,44962,45054,45146,45238,45329, + 45420,45511,45601,45692,45782,45872,45961,46051, + 46140,46229,46318,46406,46494,46583,46670,46758, + 46846,46933,47020,47107,47193,47280,47366,47452, + 47538,47623,47709,47794,47879,47964,48048,48133, + 48217,48301,48385,48468,48552,48635,48718,48801, + 48884,48966,49048,49131,49213,49294,49376,49458, + 49539,49620,49701,49782,49862,49943,50023,50103, + 50183,50263,50342,50422,50501,50580,50659,50738, + 50816,50895,50973,51051,51129,51207,51285,51362, + 51439,51517,51594,51671,51747,51824,51900,51977, + 52053,52129,52205,52280,52356,52432,52507,52582, + 52657,52732,52807,52881,52956,53030,53104,53178, + 53252,53326,53400,53473,53546,53620,53693,53766, + 53839,53911,53984,54056,54129,54201,54273,54345, + 54417,54489,54560,54632,54703,54774,54845,54916, + 54987,55058,55129,55199,55269,55340,55410,55480, + 55550,55620,55689,55759,55828,55898,55967,56036, + 56105,56174,56243,56311,56380,56448,56517,56585, + 56653,56721,56789,56857,56924,56992,57059,57127, + 57194,57261,57328,57395,57462,57529,57595,57662, + 57728,57795,57861,57927,57993,58059,58125,58191, + 58256,58322,58387,58453,58518,58583,58648,58713, + 58778,58843,58908,58972,59037,59101,59165,59230, + 59294,59358,59422,59486,59549,59613,59677,59740, + 59804,59867,59930,59993,60056,60119,60182,60245, + 60308,60370,60433,60495,60558,60620,60682,60744, + 60806,60868,60930,60992,61054,61115,61177,61238, + 61300,61361,61422,61483,61544,61605,61666,61727, + 61788,61848,61909,61969,62030,62090,62150,62211, + 62271,62331,62391,62450,62510,62570,62630,62689, + 62749,62808,62867,62927,62986,63045,63104,63163, + 63222,63281,63340,63398,63457,63515,63574,63632, + 63691,63749,63807,63865,63923,63981,64039,64097, + 64155,64212,64270,64328,64385,64443,64500,64557, + 64614,64672,64729,64786,64843,64900,64956,65013, + 65070,65126,65183,65239,65296,65352,65409,65465 +}; + +const png_byte png_sRGB_delta[512] = +{ + 207,201,158,129,113,100,90,82,77,72,68,64,61,59,56,54, + 52,50,49,47,46,45,43,42,41,40,39,39,38,37,36,36, + 35,34,34,33,33,32,32,31,31,30,30,30,29,29,28,28, + 28,27,27,27,27,26,26,26,25,25,25,25,24,24,24,24, + 23,23,23,23,23,22,22,22,22,22,22,21,21,21,21,21, + 21,20,20,20,20,20,20,20,20,19,19,19,19,19,19,19, + 19,18,18,18,18,18,18,18,18,18,18,17,17,17,17,17, + 17,17,17,17,17,17,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; +#endif /* SIMPLIFIED READ/WRITE sRGB support */ + +/* SIMPLIFIED READ/WRITE SUPPORT */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +static int +png_image_free_function(png_voidp argument) +{ + png_imagep image = png_voidcast(png_imagep, argument); + png_controlp cp = image->opaque; + png_control c; + + /* Double check that we have a png_ptr - it should be impossible to get here + * without one. + */ + if (cp->png_ptr == NULL) + return 0; + + /* First free any data held in the control structure. */ +# ifdef PNG_STDIO_SUPPORTED + if (cp->owned_file) + { + FILE *fp = png_voidcast(FILE*, cp->png_ptr->io_ptr); + cp->owned_file = 0; + + /* Ignore errors here. */ + if (fp != NULL) + { + cp->png_ptr->io_ptr = NULL; + (void)fclose(fp); + } + } +# endif + + /* Copy the control structure so that the original, allocated, version can be + * safely freed. Notice that a png_error here stops the remainder of the + * cleanup, but this is probably fine because that would indicate bad memory + * problems anyway. + */ + c = *cp; + image->opaque = &c; + png_free(c.png_ptr, cp); + + /* Then the structures, calling the correct API. */ + if (c.for_write) + { +# ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED + png_destroy_write_struct(&c.png_ptr, &c.info_ptr); +# else + png_error(c.png_ptr, "simplified write not supported"); +# endif + } + else + { +# ifdef PNG_SIMPLIFIED_READ_SUPPORTED + png_destroy_read_struct(&c.png_ptr, &c.info_ptr, NULL); +# else + png_error(c.png_ptr, "simplified read not supported"); +# endif + } + + /* Success. */ + return 1; +} + +void PNGAPI +png_image_free(png_imagep image) +{ + /* Safely call the real function, but only if doing so is safe at this point + * (if not inside an error handling context). Otherwise assume + * png_safe_execute will call this API after the return. + */ + if (image != NULL && image->opaque != NULL && + image->opaque->error_buf == NULL) + { + /* Ignore errors here: */ + (void)png_safe_execute(image, png_image_free_function, image); + image->opaque = NULL; + } +} + +int /* PRIVATE */ +png_image_error(png_imagep image, png_const_charp error_message) +{ + /* Utility to log an error. */ + png_safecat(image->message, (sizeof image->message), 0, error_message); + image->warning_or_error |= PNG_IMAGE_ERROR; + png_image_free(image); + return 0; +} + +#endif /* SIMPLIFIED READ/WRITE */ +#endif /* defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.h b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.h new file mode 100644 index 0000000..d447233 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/png.h @@ -0,0 +1,3292 @@ + +/* png.h - header file for PNG reference library + * + * libpng version 1.6.1 - March 28, 2013 + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license (See LICENSE, below) + * + * Authors and maintainers: + * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat + * libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger + * libpng versions 0.97, January 1998, through 1.6.1 - March 28, 2013: Glenn + * See also "Contributing Authors", below. + * + * Note about libpng version numbers: + * + * Due to various miscommunications, unforeseen code incompatibilities + * and occasional factors outside the authors' control, version numbering + * on the library has not always been consistent and straightforward. + * The following table summarizes matters since version 0.89c, which was + * the first widely used release: + * + * source png.h png.h shared-lib + * version string int version + * ------- ------ ----- ---------- + * 0.89c "1.0 beta 3" 0.89 89 1.0.89 + * 0.90 "1.0 beta 4" 0.90 90 0.90 [should have been 2.0.90] + * 0.95 "1.0 beta 5" 0.95 95 0.95 [should have been 2.0.95] + * 0.96 "1.0 beta 6" 0.96 96 0.96 [should have been 2.0.96] + * 0.97b "1.00.97 beta 7" 1.00.97 97 1.0.1 [should have been 2.0.97] + * 0.97c 0.97 97 2.0.97 + * 0.98 0.98 98 2.0.98 + * 0.99 0.99 98 2.0.99 + * 0.99a-m 0.99 99 2.0.99 + * 1.00 1.00 100 2.1.0 [100 should be 10000] + * 1.0.0 (from here on, the 100 2.1.0 [100 should be 10000] + * 1.0.1 png.h string is 10001 2.1.0 + * 1.0.1a-e identical to the 10002 from here on, the shared library + * 1.0.2 source version) 10002 is 2.V where V is the source code + * 1.0.2a-b 10003 version, except as noted. + * 1.0.3 10003 + * 1.0.3a-d 10004 + * 1.0.4 10004 + * 1.0.4a-f 10005 + * 1.0.5 (+ 2 patches) 10005 + * 1.0.5a-d 10006 + * 1.0.5e-r 10100 (not source compatible) + * 1.0.5s-v 10006 (not binary compatible) + * 1.0.6 (+ 3 patches) 10006 (still binary incompatible) + * 1.0.6d-f 10007 (still binary incompatible) + * 1.0.6g 10007 + * 1.0.6h 10007 10.6h (testing xy.z so-numbering) + * 1.0.6i 10007 10.6i + * 1.0.6j 10007 2.1.0.6j (incompatible with 1.0.0) + * 1.0.7beta11-14 DLLNUM 10007 2.1.0.7beta11-14 (binary compatible) + * 1.0.7beta15-18 1 10007 2.1.0.7beta15-18 (binary compatible) + * 1.0.7rc1-2 1 10007 2.1.0.7rc1-2 (binary compatible) + * 1.0.7 1 10007 (still compatible) + * 1.0.8beta1-4 1 10008 2.1.0.8beta1-4 + * 1.0.8rc1 1 10008 2.1.0.8rc1 + * 1.0.8 1 10008 2.1.0.8 + * 1.0.9beta1-6 1 10009 2.1.0.9beta1-6 + * 1.0.9rc1 1 10009 2.1.0.9rc1 + * 1.0.9beta7-10 1 10009 2.1.0.9beta7-10 + * 1.0.9rc2 1 10009 2.1.0.9rc2 + * 1.0.9 1 10009 2.1.0.9 + * 1.0.10beta1 1 10010 2.1.0.10beta1 + * 1.0.10rc1 1 10010 2.1.0.10rc1 + * 1.0.10 1 10010 2.1.0.10 + * 1.0.11beta1-3 1 10011 2.1.0.11beta1-3 + * 1.0.11rc1 1 10011 2.1.0.11rc1 + * 1.0.11 1 10011 2.1.0.11 + * 1.0.12beta1-2 2 10012 2.1.0.12beta1-2 + * 1.0.12rc1 2 10012 2.1.0.12rc1 + * 1.0.12 2 10012 2.1.0.12 + * 1.1.0a-f - 10100 2.1.1.0a-f (branch abandoned) + * 1.2.0beta1-2 2 10200 2.1.2.0beta1-2 + * 1.2.0beta3-5 3 10200 3.1.2.0beta3-5 + * 1.2.0rc1 3 10200 3.1.2.0rc1 + * 1.2.0 3 10200 3.1.2.0 + * 1.2.1beta1-4 3 10201 3.1.2.1beta1-4 + * 1.2.1rc1-2 3 10201 3.1.2.1rc1-2 + * 1.2.1 3 10201 3.1.2.1 + * 1.2.2beta1-6 12 10202 12.so.0.1.2.2beta1-6 + * 1.0.13beta1 10 10013 10.so.0.1.0.13beta1 + * 1.0.13rc1 10 10013 10.so.0.1.0.13rc1 + * 1.2.2rc1 12 10202 12.so.0.1.2.2rc1 + * 1.0.13 10 10013 10.so.0.1.0.13 + * 1.2.2 12 10202 12.so.0.1.2.2 + * 1.2.3rc1-6 12 10203 12.so.0.1.2.3rc1-6 + * 1.2.3 12 10203 12.so.0.1.2.3 + * 1.2.4beta1-3 13 10204 12.so.0.1.2.4beta1-3 + * 1.0.14rc1 13 10014 10.so.0.1.0.14rc1 + * 1.2.4rc1 13 10204 12.so.0.1.2.4rc1 + * 1.0.14 10 10014 10.so.0.1.0.14 + * 1.2.4 13 10204 12.so.0.1.2.4 + * 1.2.5beta1-2 13 10205 12.so.0.1.2.5beta1-2 + * 1.0.15rc1-3 10 10015 10.so.0.1.0.15rc1-3 + * 1.2.5rc1-3 13 10205 12.so.0.1.2.5rc1-3 + * 1.0.15 10 10015 10.so.0.1.0.15 + * 1.2.5 13 10205 12.so.0.1.2.5 + * 1.2.6beta1-4 13 10206 12.so.0.1.2.6beta1-4 + * 1.0.16 10 10016 10.so.0.1.0.16 + * 1.2.6 13 10206 12.so.0.1.2.6 + * 1.2.7beta1-2 13 10207 12.so.0.1.2.7beta1-2 + * 1.0.17rc1 10 10017 12.so.0.1.0.17rc1 + * 1.2.7rc1 13 10207 12.so.0.1.2.7rc1 + * 1.0.17 10 10017 12.so.0.1.0.17 + * 1.2.7 13 10207 12.so.0.1.2.7 + * 1.2.8beta1-5 13 10208 12.so.0.1.2.8beta1-5 + * 1.0.18rc1-5 10 10018 12.so.0.1.0.18rc1-5 + * 1.2.8rc1-5 13 10208 12.so.0.1.2.8rc1-5 + * 1.0.18 10 10018 12.so.0.1.0.18 + * 1.2.8 13 10208 12.so.0.1.2.8 + * 1.2.9beta1-3 13 10209 12.so.0.1.2.9beta1-3 + * 1.2.9beta4-11 13 10209 12.so.0.9[.0] + * 1.2.9rc1 13 10209 12.so.0.9[.0] + * 1.2.9 13 10209 12.so.0.9[.0] + * 1.2.10beta1-7 13 10210 12.so.0.10[.0] + * 1.2.10rc1-2 13 10210 12.so.0.10[.0] + * 1.2.10 13 10210 12.so.0.10[.0] + * 1.4.0beta1-5 14 10400 14.so.0.0[.0] + * 1.2.11beta1-4 13 10211 12.so.0.11[.0] + * 1.4.0beta7-8 14 10400 14.so.0.0[.0] + * 1.2.11 13 10211 12.so.0.11[.0] + * 1.2.12 13 10212 12.so.0.12[.0] + * 1.4.0beta9-14 14 10400 14.so.0.0[.0] + * 1.2.13 13 10213 12.so.0.13[.0] + * 1.4.0beta15-36 14 10400 14.so.0.0[.0] + * 1.4.0beta37-87 14 10400 14.so.14.0[.0] + * 1.4.0rc01 14 10400 14.so.14.0[.0] + * 1.4.0beta88-109 14 10400 14.so.14.0[.0] + * 1.4.0rc02-08 14 10400 14.so.14.0[.0] + * 1.4.0 14 10400 14.so.14.0[.0] + * 1.4.1beta01-03 14 10401 14.so.14.1[.0] + * 1.4.1rc01 14 10401 14.so.14.1[.0] + * 1.4.1beta04-12 14 10401 14.so.14.1[.0] + * 1.4.1 14 10401 14.so.14.1[.0] + * 1.4.2 14 10402 14.so.14.2[.0] + * 1.4.3 14 10403 14.so.14.3[.0] + * 1.4.4 14 10404 14.so.14.4[.0] + * 1.5.0beta01-58 15 10500 15.so.15.0[.0] + * 1.5.0rc01-07 15 10500 15.so.15.0[.0] + * 1.5.0 15 10500 15.so.15.0[.0] + * 1.5.1beta01-11 15 10501 15.so.15.1[.0] + * 1.5.1rc01-02 15 10501 15.so.15.1[.0] + * 1.5.1 15 10501 15.so.15.1[.0] + * 1.5.2beta01-03 15 10502 15.so.15.2[.0] + * 1.5.2rc01-03 15 10502 15.so.15.2[.0] + * 1.5.2 15 10502 15.so.15.2[.0] + * 1.5.3beta01-10 15 10503 15.so.15.3[.0] + * 1.5.3rc01-02 15 10503 15.so.15.3[.0] + * 1.5.3beta11 15 10503 15.so.15.3[.0] + * 1.5.3 [omitted] + * 1.5.4beta01-08 15 10504 15.so.15.4[.0] + * 1.5.4rc01 15 10504 15.so.15.4[.0] + * 1.5.4 15 10504 15.so.15.4[.0] + * 1.5.5beta01-08 15 10505 15.so.15.5[.0] + * 1.5.5rc01 15 10505 15.so.15.5[.0] + * 1.5.5 15 10505 15.so.15.5[.0] + * 1.5.6beta01-07 15 10506 15.so.15.6[.0] + * 1.5.6rc01-03 15 10506 15.so.15.6[.0] + * 1.5.6 15 10506 15.so.15.6[.0] + * 1.5.7beta01-05 15 10507 15.so.15.7[.0] + * 1.5.7rc01-03 15 10507 15.so.15.7[.0] + * 1.5.7 15 10507 15.so.15.7[.0] + * 1.6.0beta01-40 16 10600 16.so.16.0[.0] + * 1.6.0rc01-08 16 10600 16.so.16.0[.0] + * 1.6.0 16 10600 16.so.16.0[.0] + * 1.6.1beta01-10 16 10601 16.so.16.1[.0] + * 1.6.1rc01 16 10601 16.so.16.1[.0] + * 1.6.1 16 10601 16.so.16.1[.0] + * + * Henceforth the source version will match the shared-library major + * and minor numbers; the shared-library major version number will be + * used for changes in backward compatibility, as it is intended. The + * PNG_LIBPNG_VER macro, which is not used within libpng but is available + * for applications, is an unsigned integer of the form xyyzz corresponding + * to the source version x.y.z (leading zeros in y and z). Beta versions + * were given the previous public release number plus a letter, until + * version 1.0.6j; from then on they were given the upcoming public + * release number plus "betaNN" or "rcNN". + * + * Binary incompatibility exists only when applications make direct access + * to the info_ptr or png_ptr members through png.h, and the compiled + * application is loaded with a different version of the library. + * + * DLLNUM will change each time there are forward or backward changes + * in binary compatibility (e.g., when a new feature is added). + * + * See libpng-manual.txt or libpng.3 for more information. The PNG + * specification is available as a W3C Recommendation and as an ISO + * Specification, defines should NOT be changed. + */ +#define PNG_INFO_gAMA 0x0001 +#define PNG_INFO_sBIT 0x0002 +#define PNG_INFO_cHRM 0x0004 +#define PNG_INFO_PLTE 0x0008 +#define PNG_INFO_tRNS 0x0010 +#define PNG_INFO_bKGD 0x0020 +#define PNG_INFO_hIST 0x0040 +#define PNG_INFO_pHYs 0x0080 +#define PNG_INFO_oFFs 0x0100 +#define PNG_INFO_tIME 0x0200 +#define PNG_INFO_pCAL 0x0400 +#define PNG_INFO_sRGB 0x0800 /* GR-P, 0.96a */ +#define PNG_INFO_iCCP 0x1000 /* ESR, 1.0.6 */ +#define PNG_INFO_sPLT 0x2000 /* ESR, 1.0.6 */ +#define PNG_INFO_sCAL 0x4000 /* ESR, 1.0.6 */ +#define PNG_INFO_IDAT 0x8000 /* ESR, 1.0.6 */ + +/* This is used for the transformation routines, as some of them + * change these values for the row. It also should enable using + * the routines for other purposes. + */ +typedef struct png_row_info_struct +{ + png_uint_32 width; /* width of row */ + png_size_t rowbytes; /* number of bytes in row */ + png_byte color_type; /* color type of row */ + png_byte bit_depth; /* bit depth of row */ + png_byte channels; /* number of channels (1, 2, 3, or 4) */ + png_byte pixel_depth; /* bits per pixel (depth * channels) */ +} png_row_info; + +typedef png_row_info * png_row_infop; +typedef png_row_info * * png_row_infopp; + +/* These are the function types for the I/O functions and for the functions + * that allow the user to override the default I/O functions with his or her + * own. The png_error_ptr type should match that of user-supplied warning + * and error functions, while the png_rw_ptr type should match that of the + * user read/write data functions. Note that the 'write' function must not + * modify the buffer it is passed. The 'read' function, on the other hand, is + * expected to return the read data in the buffer. + */ +typedef PNG_CALLBACK(void, *png_error_ptr, (png_structp, png_const_charp)); +typedef PNG_CALLBACK(void, *png_rw_ptr, (png_structp, png_bytep, png_size_t)); +typedef PNG_CALLBACK(void, *png_flush_ptr, (png_structp)); +typedef PNG_CALLBACK(void, *png_read_status_ptr, (png_structp, png_uint_32, + int)); +typedef PNG_CALLBACK(void, *png_write_status_ptr, (png_structp, png_uint_32, + int)); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +typedef PNG_CALLBACK(void, *png_progressive_info_ptr, (png_structp, png_infop)); +typedef PNG_CALLBACK(void, *png_progressive_end_ptr, (png_structp, png_infop)); + +/* The following callback receives png_uint_32 row_number, int pass for the + * png_bytep data of the row. When transforming an interlaced image the + * row number is the row number within the sub-image of the interlace pass, so + * the value will increase to the height of the sub-image (not the full image) + * then reset to 0 for the next pass. + * + * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to + * find the output pixel (x,y) given an interlaced sub-image pixel + * (row,col,pass). (See below for these macros.) + */ +typedef PNG_CALLBACK(void, *png_progressive_row_ptr, (png_structp, png_bytep, + png_uint_32, int)); +#endif + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +typedef PNG_CALLBACK(void, *png_user_transform_ptr, (png_structp, png_row_infop, + png_bytep)); +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +typedef PNG_CALLBACK(int, *png_user_chunk_ptr, (png_structp, + png_unknown_chunkp)); +#endif +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +/* not used anywhere */ +/* typedef PNG_CALLBACK(void, *png_unknown_chunk_ptr, (png_structp)); */ +#endif + +#ifdef PNG_SETJMP_SUPPORTED +/* This must match the function definition in , and the application + * must include this before png.h to obtain the definition of jmp_buf. The + * function is required to be PNG_NORETURN, but this is not checked. If the + * function does return the application will crash via an abort() or similar + * system level call. + * + * If you get a warning here while building the library you may need to make + * changes to ensure that pnglibconf.h records the calling convention used by + * your compiler. This may be very difficult - try using a different compiler + * to build the library! + */ +PNG_FUNCTION(void, (PNGCAPI *png_longjmp_ptr), PNGARG((jmp_buf, int)), typedef); +#endif + +/* Transform masks for the high-level interface */ +#define PNG_TRANSFORM_IDENTITY 0x0000 /* read and write */ +#define PNG_TRANSFORM_STRIP_16 0x0001 /* read only */ +#define PNG_TRANSFORM_STRIP_ALPHA 0x0002 /* read only */ +#define PNG_TRANSFORM_PACKING 0x0004 /* read and write */ +#define PNG_TRANSFORM_PACKSWAP 0x0008 /* read and write */ +#define PNG_TRANSFORM_EXPAND 0x0010 /* read only */ +#define PNG_TRANSFORM_INVERT_MONO 0x0020 /* read and write */ +#define PNG_TRANSFORM_SHIFT 0x0040 /* read and write */ +#define PNG_TRANSFORM_BGR 0x0080 /* read and write */ +#define PNG_TRANSFORM_SWAP_ALPHA 0x0100 /* read and write */ +#define PNG_TRANSFORM_SWAP_ENDIAN 0x0200 /* read and write */ +#define PNG_TRANSFORM_INVERT_ALPHA 0x0400 /* read and write */ +#define PNG_TRANSFORM_STRIP_FILLER 0x0800 /* write only */ +/* Added to libpng-1.2.34 */ +#define PNG_TRANSFORM_STRIP_FILLER_BEFORE PNG_TRANSFORM_STRIP_FILLER +#define PNG_TRANSFORM_STRIP_FILLER_AFTER 0x1000 /* write only */ +/* Added to libpng-1.4.0 */ +#define PNG_TRANSFORM_GRAY_TO_RGB 0x2000 /* read only */ +/* Added to libpng-1.5.4 */ +#define PNG_TRANSFORM_EXPAND_16 0x4000 /* read only */ +#define PNG_TRANSFORM_SCALE_16 0x8000 /* read only */ + +/* Flags for MNG supported features */ +#define PNG_FLAG_MNG_EMPTY_PLTE 0x01 +#define PNG_FLAG_MNG_FILTER_64 0x04 +#define PNG_ALL_MNG_FEATURES 0x05 + +/* NOTE: prior to 1.5 these functions had no 'API' style declaration, + * this allowed the zlib default functions to be used on Windows + * platforms. In 1.5 the zlib default malloc (which just calls malloc and + * ignores the first argument) should be completely compatible with the + * following. + */ +typedef PNG_CALLBACK(png_voidp, *png_malloc_ptr, (png_structp, + png_alloc_size_t)); +typedef PNG_CALLBACK(void, *png_free_ptr, (png_structp, png_voidp)); + +/* Section 3: exported functions + * Here are the function definitions most commonly used. This is not + * the place to find out how to use libpng. See libpng-manual.txt for the + * full explanation, see example.c for the summary. This just provides + * a simple one line description of the use of each function. + * + * The PNG_EXPORT() and PNG_EXPORTA() macros used below are defined in + * pngconf.h and in the *.dfn files in the scripts directory. + * + * PNG_EXPORT(ordinal, type, name, (args)); + * + * ordinal: ordinal that is used while building + * *.def files. The ordinal value is only + * relevant when preprocessing png.h with + * the *.dfn files for building symbol table + * entries, and are removed by pngconf.h. + * type: return type of the function + * name: function name + * args: function arguments, with types + * + * When we wish to append attributes to a function prototype we use + * the PNG_EXPORTA() macro instead. + * + * PNG_EXPORTA(ordinal, type, name, (args), attributes); + * + * ordinal, type, name, and args: same as in PNG_EXPORT(). + * attributes: function attributes + */ + +/* Returns the version number of the library */ +PNG_EXPORT(1, png_uint_32, png_access_version_number, (void)); + +/* Tell lib we have already handled the first magic bytes. + * Handling more than 8 bytes from the beginning of the file is an error. + */ +PNG_EXPORT(2, void, png_set_sig_bytes, (png_structrp png_ptr, int num_bytes)); + +/* Check sig[start] through sig[start + num_to_check - 1] to see if it's a + * PNG file. Returns zero if the supplied bytes match the 8-byte PNG + * signature, and non-zero otherwise. Having num_to_check == 0 or + * start > 7 will always fail (ie return non-zero). + */ +PNG_EXPORT(3, int, png_sig_cmp, (png_const_bytep sig, png_size_t start, + png_size_t num_to_check)); + +/* Simple signature checking function. This is the same as calling + * png_check_sig(sig, n) := !png_sig_cmp(sig, 0, n). + */ +#define png_check_sig(sig, n) !png_sig_cmp((sig), 0, (n)) + +/* Allocate and initialize png_ptr struct for reading, and any other memory. */ +PNG_EXPORTA(4, png_structp, png_create_read_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn), + PNG_ALLOCATED); + +/* Allocate and initialize png_ptr struct for writing, and any other memory */ +PNG_EXPORTA(5, png_structp, png_create_write_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn), + PNG_ALLOCATED); + +PNG_EXPORT(6, png_size_t, png_get_compression_buffer_size, + (png_const_structrp png_ptr)); + +PNG_EXPORT(7, void, png_set_compression_buffer_size, (png_structrp png_ptr, + png_size_t size)); + +/* Moved from pngconf.h in 1.4.0 and modified to ensure setjmp/longjmp + * match up. + */ +#ifdef PNG_SETJMP_SUPPORTED +/* This function returns the jmp_buf built in to *png_ptr. It must be + * supplied with an appropriate 'longjmp' function to use on that jmp_buf + * unless the default error function is overridden in which case NULL is + * acceptable. The size of the jmp_buf is checked against the actual size + * allocated by the library - the call will return NULL on a mismatch + * indicating an ABI mismatch. + */ +PNG_EXPORT(8, jmp_buf*, png_set_longjmp_fn, (png_structrp png_ptr, + png_longjmp_ptr longjmp_fn, size_t jmp_buf_size)); +# define png_jmpbuf(png_ptr) \ + (*png_set_longjmp_fn((png_ptr), longjmp, (sizeof (jmp_buf)))) +#else +# define png_jmpbuf(png_ptr) \ + (LIBPNG_WAS_COMPILED_WITH__PNG_NO_SETJMP) +#endif +/* This function should be used by libpng applications in place of + * longjmp(png_ptr->jmpbuf, val). If longjmp_fn() has been set, it + * will use it; otherwise it will call PNG_ABORT(). This function was + * added in libpng-1.5.0. + */ +PNG_EXPORTA(9, void, png_longjmp, (png_const_structrp png_ptr, int val), + PNG_NORETURN); + +#ifdef PNG_READ_SUPPORTED +/* Reset the compression stream */ +PNG_EXPORTA(10, int, png_reset_zstream, (png_structrp png_ptr), PNG_DEPRECATED); +#endif + +/* New functions added in libpng-1.0.2 (not enabled by default until 1.2.0) */ +#ifdef PNG_USER_MEM_SUPPORTED +PNG_EXPORTA(11, png_structp, png_create_read_struct_2, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, + png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn), + PNG_ALLOCATED); +PNG_EXPORTA(12, png_structp, png_create_write_struct_2, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, + png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn), + PNG_ALLOCATED); +#endif + +/* Write the PNG file signature. */ +PNG_EXPORT(13, void, png_write_sig, (png_structrp png_ptr)); + +/* Write a PNG chunk - size, type, (optional) data, CRC. */ +PNG_EXPORT(14, void, png_write_chunk, (png_structrp png_ptr, png_const_bytep + chunk_name, png_const_bytep data, png_size_t length)); + +/* Write the start of a PNG chunk - length and chunk name. */ +PNG_EXPORT(15, void, png_write_chunk_start, (png_structrp png_ptr, + png_const_bytep chunk_name, png_uint_32 length)); + +/* Write the data of a PNG chunk started with png_write_chunk_start(). */ +PNG_EXPORT(16, void, png_write_chunk_data, (png_structrp png_ptr, + png_const_bytep data, png_size_t length)); + +/* Finish a chunk started with png_write_chunk_start() (includes CRC). */ +PNG_EXPORT(17, void, png_write_chunk_end, (png_structrp png_ptr)); + +/* Allocate and initialize the info structure */ +PNG_EXPORTA(18, png_infop, png_create_info_struct, (png_const_structrp png_ptr), + PNG_ALLOCATED); + +/* DEPRECATED: this function allowed init structures to be created using the + * default allocation method (typically malloc). Use is deprecated in 1.6.0 and + * the API will be removed in the future. + */ +PNG_EXPORTA(19, void, png_info_init_3, (png_infopp info_ptr, + png_size_t png_info_struct_size), PNG_DEPRECATED); + +/* Writes all the PNG information before the image. */ +PNG_EXPORT(20, void, png_write_info_before_PLTE, + (png_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(21, void, png_write_info, + (png_structrp png_ptr, png_const_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the information before the actual image data. */ +PNG_EXPORT(22, void, png_read_info, + (png_structrp png_ptr, png_inforp info_ptr)); +#endif + +#ifdef PNG_TIME_RFC1123_SUPPORTED + /* Convert to a US string format: there is no localization support in this + * routine. The original implementation used a 29 character buffer in + * png_struct, this will be removed in future versions. + */ +#if PNG_LIBPNG_VER < 10700 +/* To do: remove this from libpng17 (and from libpng17/png.c and pngstruct.h) */ +PNG_EXPORTA(23, png_const_charp, png_convert_to_rfc1123, (png_structrp png_ptr, + png_const_timep ptime),PNG_DEPRECATED); +#endif +PNG_EXPORT(241, int, png_convert_to_rfc1123_buffer, (char out[29], + png_const_timep ptime)); +#endif + +#ifdef PNG_CONVERT_tIME_SUPPORTED +/* Convert from a struct tm to png_time */ +PNG_EXPORT(24, void, png_convert_from_struct_tm, (png_timep ptime, + const struct tm * ttime)); + +/* Convert from time_t to png_time. Uses gmtime() */ +PNG_EXPORT(25, void, png_convert_from_time_t, (png_timep ptime, time_t ttime)); +#endif /* PNG_CONVERT_tIME_SUPPORTED */ + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expand data to 24-bit RGB, or 8-bit grayscale, with alpha if available. */ +PNG_EXPORT(26, void, png_set_expand, (png_structrp png_ptr)); +PNG_EXPORT(27, void, png_set_expand_gray_1_2_4_to_8, (png_structrp png_ptr)); +PNG_EXPORT(28, void, png_set_palette_to_rgb, (png_structrp png_ptr)); +PNG_EXPORT(29, void, png_set_tRNS_to_alpha, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* Expand to 16-bit channels, forces conversion of palette to RGB and expansion + * of a tRNS chunk if present. + */ +PNG_EXPORT(221, void, png_set_expand_16, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Use blue, green, red order for pixels. */ +PNG_EXPORT(30, void, png_set_bgr, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +/* Expand the grayscale to 24-bit RGB if necessary. */ +PNG_EXPORT(31, void, png_set_gray_to_rgb, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +/* Reduce RGB to grayscale. */ +#define PNG_ERROR_ACTION_NONE 1 +#define PNG_ERROR_ACTION_WARN 2 +#define PNG_ERROR_ACTION_ERROR 3 +#define PNG_RGB_TO_GRAY_DEFAULT (-1)/*for red/green coefficients*/ + +PNG_FP_EXPORT(32, void, png_set_rgb_to_gray, (png_structrp png_ptr, + int error_action, double red, double green)) +PNG_FIXED_EXPORT(33, void, png_set_rgb_to_gray_fixed, (png_structrp png_ptr, + int error_action, png_fixed_point red, png_fixed_point green)) + +PNG_EXPORT(34, png_byte, png_get_rgb_to_gray_status, (png_const_structrp + png_ptr)); +#endif + +#ifdef PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +PNG_EXPORT(35, void, png_build_grayscale_palette, (int bit_depth, + png_colorp palette)); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* How the alpha channel is interpreted - this affects how the color channels of + * a PNG file are returned when an alpha channel, or tRNS chunk in a palette + * file, is present. + * + * This has no effect on the way pixels are written into a PNG output + * datastream. The color samples in a PNG datastream are never premultiplied + * with the alpha samples. + * + * The default is to return data according to the PNG specification: the alpha + * channel is a linear measure of the contribution of the pixel to the + * corresponding composited pixel. The gamma encoded color channels must be + * scaled according to the contribution and to do this it is necessary to undo + * the encoding, scale the color values, perform the composition and reencode + * the values. This is the 'PNG' mode. + * + * The alternative is to 'associate' the alpha with the color information by + * storing color channel values that have been scaled by the alpha. The + * advantage is that the color channels can be resampled (the image can be + * scaled) in this form. The disadvantage is that normal practice is to store + * linear, not (gamma) encoded, values and this requires 16-bit channels for + * still images rather than the 8-bit channels that are just about sufficient if + * gamma encoding is used. In addition all non-transparent pixel values, + * including completely opaque ones, must be gamma encoded to produce the final + * image. This is the 'STANDARD', 'ASSOCIATED' or 'PREMULTIPLIED' mode (the + * latter being the two common names for associated alpha color channels.) + * + * Since it is not necessary to perform arithmetic on opaque color values so + * long as they are not to be resampled and are in the final color space it is + * possible to optimize the handling of alpha by storing the opaque pixels in + * the PNG format (adjusted for the output color space) while storing partially + * opaque pixels in the standard, linear, format. The accuracy required for + * standard alpha composition is relatively low, because the pixels are + * isolated, therefore typically the accuracy loss in storing 8-bit linear + * values is acceptable. (This is not true if the alpha channel is used to + * simulate transparency over large areas - use 16 bits or the PNG mode in + * this case!) This is the 'OPTIMIZED' mode. For this mode a pixel is + * treated as opaque only if the alpha value is equal to the maximum value. + * + * The final choice is to gamma encode the alpha channel as well. This is + * broken because, in practice, no implementation that uses this choice + * correctly undoes the encoding before handling alpha composition. Use this + * choice only if other serious errors in the software or hardware you use + * mandate it; the typical serious error is for dark halos to appear around + * opaque areas of the composited PNG image because of arithmetic overflow. + * + * The API function png_set_alpha_mode specifies which of these choices to use + * with an enumerated 'mode' value and the gamma of the required output: + */ +#define PNG_ALPHA_PNG 0 /* according to the PNG standard */ +#define PNG_ALPHA_STANDARD 1 /* according to Porter/Duff */ +#define PNG_ALPHA_ASSOCIATED 1 /* as above; this is the normal practice */ +#define PNG_ALPHA_PREMULTIPLIED 1 /* as above */ +#define PNG_ALPHA_OPTIMIZED 2 /* 'PNG' for opaque pixels, else 'STANDARD' */ +#define PNG_ALPHA_BROKEN 3 /* the alpha channel is gamma encoded */ + +PNG_FP_EXPORT(227, void, png_set_alpha_mode, (png_structrp png_ptr, int mode, + double output_gamma)) +PNG_FIXED_EXPORT(228, void, png_set_alpha_mode_fixed, (png_structrp png_ptr, + int mode, png_fixed_point output_gamma)) +#endif + +#if defined(PNG_GAMMA_SUPPORTED) || defined(PNG_READ_ALPHA_MODE_SUPPORTED) +/* The output_gamma value is a screen gamma in libpng terminology: it expresses + * how to decode the output values, not how they are encoded. The values used + * correspond to the normal numbers used to describe the overall gamma of a + * computer display system; for example 2.2 for an sRGB conformant system. The + * values are scaled by 100000 in the _fixed version of the API (so 220000 for + * sRGB.) + * + * The inverse of the value is always used to provide a default for the PNG file + * encoding if it has no gAMA chunk and if png_set_gamma() has not been called + * to override the PNG gamma information. + * + * When the ALPHA_OPTIMIZED mode is selected the output gamma is used to encode + * opaque pixels however pixels with lower alpha values are not encoded, + * regardless of the output gamma setting. + * + * When the standard Porter Duff handling is requested with mode 1 the output + * encoding is set to be linear and the output_gamma value is only relevant + * as a default for input data that has no gamma information. The linear output + * encoding will be overridden if png_set_gamma() is called - the results may be + * highly unexpected! + * + * The following numbers are derived from the sRGB standard and the research + * behind it. sRGB is defined to be approximated by a PNG gAMA chunk value of + * 0.45455 (1/2.2) for PNG. The value implicitly includes any viewing + * correction required to take account of any differences in the color + * environment of the original scene and the intended display environment; the + * value expresses how to *decode* the image for display, not how the original + * data was *encoded*. + * + * sRGB provides a peg for the PNG standard by defining a viewing environment. + * sRGB itself, and earlier TV standards, actually use a more complex transform + * (a linear portion then a gamma 2.4 power law) than PNG can express. (PNG is + * limited to simple power laws.) By saying that an image for direct display on + * an sRGB conformant system should be stored with a gAMA chunk value of 45455 + * (11.3.3.2 and 11.3.3.5 of the ISO PNG specification) the PNG specification + * makes it possible to derive values for other display systems and + * environments. + * + * The Mac value is deduced from the sRGB based on an assumption that the actual + * extra viewing correction used in early Mac display systems was implemented as + * a power 1.45 lookup table. + * + * Any system where a programmable lookup table is used or where the behavior of + * the final display device characteristics can be changed requires system + * specific code to obtain the current characteristic. However this can be + * difficult and most PNG gamma correction only requires an approximate value. + * + * By default, if png_set_alpha_mode() is not called, libpng assumes that all + * values are unencoded, linear, values and that the output device also has a + * linear characteristic. This is only very rarely correct - it is invariably + * better to call png_set_alpha_mode() with PNG_DEFAULT_sRGB than rely on the + * default if you don't know what the right answer is! + * + * The special value PNG_GAMMA_MAC_18 indicates an older Mac system (pre Mac OS + * 10.6) which used a correction table to implement a somewhat lower gamma on an + * otherwise sRGB system. + * + * Both these values are reserved (not simple gamma values) in order to allow + * more precise correction internally in the future. + * + * NOTE: the following values can be passed to either the fixed or floating + * point APIs, but the floating point API will also accept floating point + * values. + */ +#define PNG_DEFAULT_sRGB -1 /* sRGB gamma and color space */ +#define PNG_GAMMA_MAC_18 -2 /* Old Mac '1.8' gamma and color space */ +#define PNG_GAMMA_sRGB 220000 /* Television standards--matches sRGB gamma */ +#define PNG_GAMMA_LINEAR PNG_FP_1 /* Linear */ +#endif + +/* The following are examples of calls to png_set_alpha_mode to achieve the + * required overall gamma correction and, where necessary, alpha + * premultiplication. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * This is the default libpng handling of the alpha channel - it is not + * pre-multiplied into the color components. In addition the call states + * that the output is for a sRGB system and causes all PNG files without gAMA + * chunks to be assumed to be encoded using sRGB. + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * In this case the output is assumed to be something like an sRGB conformant + * display preceeded by a power-law lookup table of power 1.45. This is how + * early Mac systems behaved. + * + * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_GAMMA_LINEAR); + * This is the classic Jim Blinn approach and will work in academic + * environments where everything is done by the book. It has the shortcoming + * of assuming that input PNG data with no gamma information is linear - this + * is unlikely to be correct unless the PNG files where generated locally. + * Most of the time the output precision will be so low as to show + * significant banding in dark areas of the image. + * + * png_set_expand_16(pp); + * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_DEFAULT_sRGB); + * This is a somewhat more realistic Jim Blinn inspired approach. PNG files + * are assumed to have the sRGB encoding if not marked with a gamma value and + * the output is always 16 bits per component. This permits accurate scaling + * and processing of the data. If you know that your input PNG files were + * generated locally you might need to replace PNG_DEFAULT_sRGB with the + * correct value for your system. + * + * png_set_alpha_mode(pp, PNG_ALPHA_OPTIMIZED, PNG_DEFAULT_sRGB); + * If you just need to composite the PNG image onto an existing background + * and if you control the code that does this you can use the optimization + * setting. In this case you just copy completely opaque pixels to the + * output. For pixels that are not completely transparent (you just skip + * those) you do the composition math using png_composite or png_composite_16 + * below then encode the resultant 8-bit or 16-bit values to match the output + * encoding. + * + * Other cases + * If neither the PNG nor the standard linear encoding work for you because + * of the software or hardware you use then you have a big problem. The PNG + * case will probably result in halos around the image. The linear encoding + * will probably result in a washed out, too bright, image (it's actually too + * contrasty.) Try the ALPHA_OPTIMIZED mode above - this will probably + * substantially reduce the halos. Alternatively try: + * + * png_set_alpha_mode(pp, PNG_ALPHA_BROKEN, PNG_DEFAULT_sRGB); + * This option will also reduce the halos, but there will be slight dark + * halos round the opaque parts of the image where the background is light. + * In the OPTIMIZED mode the halos will be light halos where the background + * is dark. Take your pick - the halos are unavoidable unless you can get + * your hardware/software fixed! (The OPTIMIZED approach is slightly + * faster.) + * + * When the default gamma of PNG files doesn't match the output gamma. + * If you have PNG files with no gamma information png_set_alpha_mode allows + * you to provide a default gamma, but it also sets the ouput gamma to the + * matching value. If you know your PNG files have a gamma that doesn't + * match the output you can take advantage of the fact that + * png_set_alpha_mode always sets the output gamma but only sets the PNG + * default if it is not already set: + * + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB); + * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC); + * The first call sets both the default and the output gamma values, the + * second call overrides the output gamma without changing the default. This + * is easier than achieving the same effect with png_set_gamma. You must use + * PNG_ALPHA_PNG for the first call - internal checking in png_set_alpha will + * fire if more than one call to png_set_alpha_mode and png_set_background is + * made in the same read operation, however multiple calls with PNG_ALPHA_PNG + * are ignored. + */ + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED +PNG_EXPORT(36, void, png_set_strip_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED) +PNG_EXPORT(37, void, png_set_swap_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED) +PNG_EXPORT(38, void, png_set_invert_alpha, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) +/* Add a filler byte to 8-bit Gray or 24-bit RGB images. */ +PNG_EXPORT(39, void, png_set_filler, (png_structrp png_ptr, png_uint_32 filler, + int flags)); +/* The values of the PNG_FILLER_ defines should NOT be changed */ +# define PNG_FILLER_BEFORE 0 +# define PNG_FILLER_AFTER 1 +/* Add an alpha byte to 8-bit Gray or 24-bit RGB images. */ +PNG_EXPORT(40, void, png_set_add_alpha, (png_structrp png_ptr, + png_uint_32 filler, int flags)); +#endif /* PNG_READ_FILLER_SUPPORTED || PNG_WRITE_FILLER_SUPPORTED */ + +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Swap bytes in 16-bit depth files. */ +PNG_EXPORT(41, void, png_set_swap, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED) +/* Use 1 byte per pixel in 1, 2, or 4-bit depth files. */ +PNG_EXPORT(42, void, png_set_packing, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED) || \ + defined(PNG_WRITE_PACKSWAP_SUPPORTED) +/* Swap packing order of pixels in bytes. */ +PNG_EXPORT(43, void, png_set_packswap, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) +/* Converts files to legal bit depths. */ +PNG_EXPORT(44, void, png_set_shift, (png_structrp png_ptr, png_const_color_8p + true_bits)); +#endif + +#if defined(PNG_READ_INTERLACING_SUPPORTED) || \ + defined(PNG_WRITE_INTERLACING_SUPPORTED) +/* Have the code handle the interlacing. Returns the number of passes. + * MUST be called before png_read_update_info or png_start_read_image, + * otherwise it will not have the desired effect. Note that it is still + * necessary to call png_read_row or png_read_rows png_get_image_height + * times for each pass. +*/ +PNG_EXPORT(45, int, png_set_interlace_handling, (png_structrp png_ptr)); +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +/* Invert monochrome files */ +PNG_EXPORT(46, void, png_set_invert_mono, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED +/* Handle alpha and tRNS by replacing with a background color. Prior to + * libpng-1.5.4 this API must not be called before the PNG file header has been + * read. Doing so will result in unexpected behavior and possible warnings or + * errors if the PNG file contains a bKGD chunk. + */ +PNG_FP_EXPORT(47, void, png_set_background, (png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, double background_gamma)) +PNG_FIXED_EXPORT(215, void, png_set_background_fixed, (png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, png_fixed_point background_gamma)) +#endif +#ifdef PNG_READ_BACKGROUND_SUPPORTED +# define PNG_BACKGROUND_GAMMA_UNKNOWN 0 +# define PNG_BACKGROUND_GAMMA_SCREEN 1 +# define PNG_BACKGROUND_GAMMA_FILE 2 +# define PNG_BACKGROUND_GAMMA_UNIQUE 3 +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +/* Scale a 16-bit depth file down to 8-bit, accurately. */ +PNG_EXPORT(229, void, png_set_scale_16, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_16_TO_8 SUPPORTED /* Name prior to 1.5.4 */ +/* Strip the second byte of information from a 16-bit depth file. */ +PNG_EXPORT(48, void, png_set_strip_16, (png_structrp png_ptr)); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* Turn on quantizing, and reduce the palette to the number of colors + * available. + */ +PNG_EXPORT(49, void, png_set_quantize, (png_structrp png_ptr, + png_colorp palette, int num_palette, int maximum_colors, + png_const_uint_16p histogram, int full_quantize)); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* The threshold on gamma processing is configurable but hard-wired into the + * library. The following is the floating point variant. + */ +#define PNG_GAMMA_THRESHOLD (PNG_GAMMA_THRESHOLD_FIXED*.00001) + +/* Handle gamma correction. Screen_gamma=(display_exponent). + * NOTE: this API simply sets the screen and file gamma values. It will + * therefore override the value for gamma in a PNG file if it is called after + * the file header has been read - use with care - call before reading the PNG + * file for best results! + * + * These routines accept the same gamma values as png_set_alpha_mode (described + * above). The PNG_GAMMA_ defines and PNG_DEFAULT_sRGB can be passed to either + * API (floating point or fixed.) Notice, however, that the 'file_gamma' value + * is the inverse of a 'screen gamma' value. + */ +PNG_FP_EXPORT(50, void, png_set_gamma, (png_structrp png_ptr, + double screen_gamma, double override_file_gamma)) +PNG_FIXED_EXPORT(208, void, png_set_gamma_fixed, (png_structrp png_ptr, + png_fixed_point screen_gamma, png_fixed_point override_file_gamma)) +#endif + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +/* Set how many lines between output flushes - 0 for no flushing */ +PNG_EXPORT(51, void, png_set_flush, (png_structrp png_ptr, int nrows)); +/* Flush the current PNG output buffer */ +PNG_EXPORT(52, void, png_write_flush, (png_structrp png_ptr)); +#endif + +/* Optional update palette with requested transformations */ +PNG_EXPORT(53, void, png_start_read_image, (png_structrp png_ptr)); + +/* Optional call to update the users info structure */ +PNG_EXPORT(54, void, png_read_update_info, (png_structrp png_ptr, + png_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read one or more rows of image data. */ +PNG_EXPORT(55, void, png_read_rows, (png_structrp png_ptr, png_bytepp row, + png_bytepp display_row, png_uint_32 num_rows)); +#endif + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read a row of data. */ +PNG_EXPORT(56, void, png_read_row, (png_structrp png_ptr, png_bytep row, + png_bytep display_row)); +#endif + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the whole image into memory at once. */ +PNG_EXPORT(57, void, png_read_image, (png_structrp png_ptr, png_bytepp image)); +#endif + +/* Write a row of image data */ +PNG_EXPORT(58, void, png_write_row, (png_structrp png_ptr, + png_const_bytep row)); + +/* Write a few rows of image data: (*row) is not written; however, the type + * is declared as writeable to maintain compatibility with previous versions + * of libpng and to allow the 'display_row' array from read_rows to be passed + * unchanged to write_rows. + */ +PNG_EXPORT(59, void, png_write_rows, (png_structrp png_ptr, png_bytepp row, + png_uint_32 num_rows)); + +/* Write the image data */ +PNG_EXPORT(60, void, png_write_image, (png_structrp png_ptr, png_bytepp image)); + +/* Write the end of the PNG file. */ +PNG_EXPORT(61, void, png_write_end, (png_structrp png_ptr, + png_inforp info_ptr)); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the end of the PNG file. */ +PNG_EXPORT(62, void, png_read_end, (png_structrp png_ptr, png_inforp info_ptr)); +#endif + +/* Free any memory associated with the png_info_struct */ +PNG_EXPORT(63, void, png_destroy_info_struct, (png_const_structrp png_ptr, + png_infopp info_ptr_ptr)); + +/* Free any memory associated with the png_struct and the png_info_structs */ +PNG_EXPORT(64, void, png_destroy_read_struct, (png_structpp png_ptr_ptr, + png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr)); + +/* Free any memory associated with the png_struct and the png_info_structs */ +PNG_EXPORT(65, void, png_destroy_write_struct, (png_structpp png_ptr_ptr, + png_infopp info_ptr_ptr)); + +/* Set the libpng method of handling chunk CRC errors */ +PNG_EXPORT(66, void, png_set_crc_action, (png_structrp png_ptr, int crit_action, + int ancil_action)); + +/* Values for png_set_crc_action() say how to handle CRC errors in + * ancillary and critical chunks, and whether to use the data contained + * therein. Note that it is impossible to "discard" data in a critical + * chunk. For versions prior to 0.90, the action was always error/quit, + * whereas in version 0.90 and later, the action for CRC errors in ancillary + * chunks is warn/discard. These values should NOT be changed. + * + * value action:critical action:ancillary + */ +#define PNG_CRC_DEFAULT 0 /* error/quit warn/discard data */ +#define PNG_CRC_ERROR_QUIT 1 /* error/quit error/quit */ +#define PNG_CRC_WARN_DISCARD 2 /* (INVALID) warn/discard data */ +#define PNG_CRC_WARN_USE 3 /* warn/use data warn/use data */ +#define PNG_CRC_QUIET_USE 4 /* quiet/use data quiet/use data */ +#define PNG_CRC_NO_CHANGE 5 /* use current value use current value */ + +/* These functions give the user control over the scan-line filtering in + * libpng and the compression methods used by zlib. These functions are + * mainly useful for testing, as the defaults should work with most users. + * Those users who are tight on memory or want faster performance at the + * expense of compression can modify them. See the compression library + * header file (zlib.h) for an explination of the compression functions. + */ + +/* Set the filtering method(s) used by libpng. Currently, the only valid + * value for "method" is 0. + */ +PNG_EXPORT(67, void, png_set_filter, (png_structrp png_ptr, int method, + int filters)); + +/* Flags for png_set_filter() to say which filters to use. The flags + * are chosen so that they don't conflict with real filter types + * below, in case they are supplied instead of the #defined constants. + * These values should NOT be changed. + */ +#define PNG_NO_FILTERS 0x00 +#define PNG_FILTER_NONE 0x08 +#define PNG_FILTER_SUB 0x10 +#define PNG_FILTER_UP 0x20 +#define PNG_FILTER_AVG 0x40 +#define PNG_FILTER_PAETH 0x80 +#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \ + PNG_FILTER_AVG | PNG_FILTER_PAETH) + +/* Filter values (not flags) - used in pngwrite.c, pngwutil.c for now. + * These defines should NOT be changed. + */ +#define PNG_FILTER_VALUE_NONE 0 +#define PNG_FILTER_VALUE_SUB 1 +#define PNG_FILTER_VALUE_UP 2 +#define PNG_FILTER_VALUE_AVG 3 +#define PNG_FILTER_VALUE_PAETH 4 +#define PNG_FILTER_VALUE_LAST 5 + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* EXPERIMENTAL */ +/* The "heuristic_method" is given by one of the PNG_FILTER_HEURISTIC_ + * defines, either the default (minimum-sum-of-absolute-differences), or + * the experimental method (weighted-minimum-sum-of-absolute-differences). + * + * Weights are factors >= 1.0, indicating how important it is to keep the + * filter type consistent between rows. Larger numbers mean the current + * filter is that many times as likely to be the same as the "num_weights" + * previous filters. This is cumulative for each previous row with a weight. + * There needs to be "num_weights" values in "filter_weights", or it can be + * NULL if the weights aren't being specified. Weights have no influence on + * the selection of the first row filter. Well chosen weights can (in theory) + * improve the compression for a given image. + * + * Costs are factors >= 1.0 indicating the relative decoding costs of a + * filter type. Higher costs indicate more decoding expense, and are + * therefore less likely to be selected over a filter with lower computational + * costs. There needs to be a value in "filter_costs" for each valid filter + * type (given by PNG_FILTER_VALUE_LAST), or it can be NULL if you aren't + * setting the costs. Costs try to improve the speed of decompression without + * unduly increasing the compressed image size. + * + * A negative weight or cost indicates the default value is to be used, and + * values in the range [0.0, 1.0) indicate the value is to remain unchanged. + * The default values for both weights and costs are currently 1.0, but may + * change if good general weighting/cost heuristics can be found. If both + * the weights and costs are set to 1.0, this degenerates the WEIGHTED method + * to the UNWEIGHTED method, but with added encoding time/computation. + */ +PNG_FP_EXPORT(68, void, png_set_filter_heuristics, (png_structrp png_ptr, + int heuristic_method, int num_weights, png_const_doublep filter_weights, + png_const_doublep filter_costs)) +PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed, + (png_structrp png_ptr, int heuristic_method, int num_weights, + png_const_fixed_point_p filter_weights, + png_const_fixed_point_p filter_costs)) +#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */ + +/* Heuristic used for row filter selection. These defines should NOT be + * changed. + */ +#define PNG_FILTER_HEURISTIC_DEFAULT 0 /* Currently "UNWEIGHTED" */ +#define PNG_FILTER_HEURISTIC_UNWEIGHTED 1 /* Used by libpng < 0.95 */ +#define PNG_FILTER_HEURISTIC_WEIGHTED 2 /* Experimental feature */ +#define PNG_FILTER_HEURISTIC_LAST 3 /* Not a valid value */ + +#ifdef PNG_WRITE_SUPPORTED +/* Set the library compression level. Currently, valid values range from + * 0 - 9, corresponding directly to the zlib compression levels 0 - 9 + * (0 - no compression, 9 - "maximal" compression). Note that tests have + * shown that zlib compression levels 3-6 usually perform as well as level 9 + * for PNG images, and do considerably fewer caclulations. In the future, + * these values may not correspond directly to the zlib compression levels. + */ +PNG_EXPORT(69, void, png_set_compression_level, (png_structrp png_ptr, + int level)); + +PNG_EXPORT(70, void, png_set_compression_mem_level, (png_structrp png_ptr, + int mem_level)); + +PNG_EXPORT(71, void, png_set_compression_strategy, (png_structrp png_ptr, + int strategy)); + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +PNG_EXPORT(72, void, png_set_compression_window_bits, (png_structrp png_ptr, + int window_bits)); + +PNG_EXPORT(73, void, png_set_compression_method, (png_structrp png_ptr, + int method)); +#endif + +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +/* Also set zlib parameters for compressing non-IDAT chunks */ +PNG_EXPORT(222, void, png_set_text_compression_level, (png_structrp png_ptr, + int level)); + +PNG_EXPORT(223, void, png_set_text_compression_mem_level, (png_structrp png_ptr, + int mem_level)); + +PNG_EXPORT(224, void, png_set_text_compression_strategy, (png_structrp png_ptr, + int strategy)); + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +PNG_EXPORT(225, void, png_set_text_compression_window_bits, + (png_structrp png_ptr, int window_bits)); + +PNG_EXPORT(226, void, png_set_text_compression_method, (png_structrp png_ptr, + int method)); +#endif /* PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED */ + +/* These next functions are called for input/output, memory, and error + * handling. They are in the file pngrio.c, pngwio.c, and pngerror.c, + * and call standard C I/O routines such as fread(), fwrite(), and + * fprintf(). These functions can be made to use other I/O routines + * at run time for those applications that need to handle I/O in a + * different manner by calling png_set_???_fn(). See libpng-manual.txt for + * more information. + */ + +#ifdef PNG_STDIO_SUPPORTED +/* Initialize the input/output for the PNG file to the default functions. */ +PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, png_FILE_p fp)); +#endif + +/* Replace the (error and abort), and warning functions with user + * supplied functions. If no messages are to be printed you must still + * write and use replacement functions. The replacement error_fn should + * still do a longjmp to the last setjmp location if you are using this + * method of error handling. If error_fn or warning_fn is NULL, the + * default function will be used. + */ + +PNG_EXPORT(75, void, png_set_error_fn, (png_structrp png_ptr, + png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warning_fn)); + +/* Return the user pointer associated with the error functions */ +PNG_EXPORT(76, png_voidp, png_get_error_ptr, (png_const_structrp png_ptr)); + +/* Replace the default data output functions with a user supplied one(s). + * If buffered output is not used, then output_flush_fn can be set to NULL. + * If PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile time + * output_flush_fn will be ignored (and thus can be NULL). + * It is probably a mistake to use NULL for output_flush_fn if + * write_data_fn is not also NULL unless you have built libpng with + * PNG_WRITE_FLUSH_SUPPORTED undefined, because in this case libpng's + * default flush function, which uses the standard *FILE structure, will + * be used. + */ +PNG_EXPORT(77, void, png_set_write_fn, (png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn)); + +/* Replace the default data input function with a user supplied one. */ +PNG_EXPORT(78, void, png_set_read_fn, (png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr read_data_fn)); + +/* Return the user pointer associated with the I/O functions */ +PNG_EXPORT(79, png_voidp, png_get_io_ptr, (png_const_structrp png_ptr)); + +PNG_EXPORT(80, void, png_set_read_status_fn, (png_structrp png_ptr, + png_read_status_ptr read_row_fn)); + +PNG_EXPORT(81, void, png_set_write_status_fn, (png_structrp png_ptr, + png_write_status_ptr write_row_fn)); + +#ifdef PNG_USER_MEM_SUPPORTED +/* Replace the default memory allocation functions with user supplied one(s). */ +PNG_EXPORT(82, void, png_set_mem_fn, (png_structrp png_ptr, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn)); +/* Return the user pointer associated with the memory functions */ +PNG_EXPORT(83, png_voidp, png_get_mem_ptr, (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED +PNG_EXPORT(84, void, png_set_read_user_transform_fn, (png_structrp png_ptr, + png_user_transform_ptr read_user_transform_fn)); +#endif + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED +PNG_EXPORT(85, void, png_set_write_user_transform_fn, (png_structrp png_ptr, + png_user_transform_ptr write_user_transform_fn)); +#endif + +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +PNG_EXPORT(86, void, png_set_user_transform_info, (png_structrp png_ptr, + png_voidp user_transform_ptr, int user_transform_depth, + int user_transform_channels)); +/* Return the user pointer associated with the user transform functions */ +PNG_EXPORT(87, png_voidp, png_get_user_transform_ptr, + (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_USER_TRANSFORM_INFO_SUPPORTED +/* Return information about the row currently being processed. Note that these + * APIs do not fail but will return unexpected results if called outside a user + * transform callback. Also note that when transforming an interlaced image the + * row number is the row number within the sub-image of the interlace pass, so + * the value will increase to the height of the sub-image (not the full image) + * then reset to 0 for the next pass. + * + * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to + * find the output pixel (x,y) given an interlaced sub-image pixel + * (row,col,pass). (See below for these macros.) + */ +PNG_EXPORT(217, png_uint_32, png_get_current_row_number, (png_const_structrp)); +PNG_EXPORT(218, png_byte, png_get_current_pass_number, (png_const_structrp)); +#endif + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +/* This callback is called only for *unknown* chunks. If + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED is set then it is possible to set known + * chunks to be treated as unknown, however in this case the callback must do + * any processing required by the chunk (e.g. by calling the appropriate + * png_set_ APIs.) + * + * There is no write support - on write, by default, all the chunks in the + * 'unknown' list are written in the specified position. + * + * The integer return from the callback function is interpreted thus: + * + * negative: An error occured, png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be saved. A critical + * chunk will cause an error at this point unless it is to be saved. + * positive: The chunk was handled, libpng will ignore/discard it. + * + * See "INTERACTION WTIH USER CHUNK CALLBACKS" below for important notes about + * how this behavior will change in libpng 1.7 + */ +PNG_EXPORT(88, void, png_set_read_user_chunk_fn, (png_structrp png_ptr, + png_voidp user_chunk_ptr, png_user_chunk_ptr read_user_chunk_fn)); +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +PNG_EXPORT(89, png_voidp, png_get_user_chunk_ptr, (png_const_structrp png_ptr)); +#endif + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +/* Sets the function callbacks for the push reader, and a pointer to a + * user-defined structure available to the callback functions. + */ +PNG_EXPORT(90, void, png_set_progressive_read_fn, (png_structrp png_ptr, + png_voidp progressive_ptr, png_progressive_info_ptr info_fn, + png_progressive_row_ptr row_fn, png_progressive_end_ptr end_fn)); + +/* Returns the user pointer associated with the push read functions */ +PNG_EXPORT(91, png_voidp, png_get_progressive_ptr, + (png_const_structrp png_ptr)); + +/* Function to be called when data becomes available */ +PNG_EXPORT(92, void, png_process_data, (png_structrp png_ptr, + png_inforp info_ptr, png_bytep buffer, png_size_t buffer_size)); + +/* A function which may be called *only* within png_process_data to stop the + * processing of any more data. The function returns the number of bytes + * remaining, excluding any that libpng has cached internally. A subsequent + * call to png_process_data must supply these bytes again. If the argument + * 'save' is set to true the routine will first save all the pending data and + * will always return 0. + */ +PNG_EXPORT(219, png_size_t, png_process_data_pause, (png_structrp, int save)); + +/* A function which may be called *only* outside (after) a call to + * png_process_data. It returns the number of bytes of data to skip in the + * input. Normally it will return 0, but if it returns a non-zero value the + * application must skip than number of bytes of input data and pass the + * following data to the next call to png_process_data. + */ +PNG_EXPORT(220, png_uint_32, png_process_data_skip, (png_structrp)); + +#ifdef PNG_READ_INTERLACING_SUPPORTED +/* Function that combines rows. 'new_row' is a flag that should come from + * the callback and be non-NULL if anything needs to be done; the library + * stores its own version of the new data internally and ignores the passed + * in value. + */ +PNG_EXPORT(93, void, png_progressive_combine_row, (png_const_structrp png_ptr, + png_bytep old_row, png_const_bytep new_row)); +#endif /* PNG_READ_INTERLACING_SUPPORTED */ +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ + +PNG_EXPORTA(94, png_voidp, png_malloc, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); +/* Added at libpng version 1.4.0 */ +PNG_EXPORTA(95, png_voidp, png_calloc, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); + +/* Added at libpng version 1.2.4 */ +PNG_EXPORTA(96, png_voidp, png_malloc_warn, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED); + +/* Frees a pointer allocated by png_malloc() */ +PNG_EXPORT(97, void, png_free, (png_const_structrp png_ptr, png_voidp ptr)); + +/* Free data that was allocated internally */ +PNG_EXPORT(98, void, png_free_data, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 free_me, int num)); + +/* Reassign responsibility for freeing existing data, whether allocated + * by libpng or by the application; this works on the png_info structure passed + * in, it does not change the state for other png_info structures. + * + * It is unlikely that this function works correctly as of 1.6.0 and using it + * may result either in memory leaks or double free of allocated data. + */ +PNG_EXPORTA(99, void, png_data_freer, (png_const_structrp png_ptr, + png_inforp info_ptr, int freer, png_uint_32 mask), PNG_DEPRECATED); + +/* Assignments for png_data_freer */ +#define PNG_DESTROY_WILL_FREE_DATA 1 +#define PNG_SET_WILL_FREE_DATA 1 +#define PNG_USER_WILL_FREE_DATA 2 +/* Flags for png_ptr->free_me and info_ptr->free_me */ +#define PNG_FREE_HIST 0x0008 +#define PNG_FREE_ICCP 0x0010 +#define PNG_FREE_SPLT 0x0020 +#define PNG_FREE_ROWS 0x0040 +#define PNG_FREE_PCAL 0x0080 +#define PNG_FREE_SCAL 0x0100 +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +# define PNG_FREE_UNKN 0x0200 +#endif +/* PNG_FREE_LIST 0x0400 removed in 1.6.0 because it is ignored */ +#define PNG_FREE_PLTE 0x1000 +#define PNG_FREE_TRNS 0x2000 +#define PNG_FREE_TEXT 0x4000 +#define PNG_FREE_ALL 0x7fff +#define PNG_FREE_MUL 0x4220 /* PNG_FREE_SPLT|PNG_FREE_TEXT|PNG_FREE_UNKN */ + +#ifdef PNG_USER_MEM_SUPPORTED +PNG_EXPORTA(100, png_voidp, png_malloc_default, (png_const_structrp png_ptr, + png_alloc_size_t size), PNG_ALLOCATED PNG_DEPRECATED); +PNG_EXPORTA(101, void, png_free_default, (png_const_structrp png_ptr, + png_voidp ptr), PNG_DEPRECATED); +#endif + +#ifdef PNG_ERROR_TEXT_SUPPORTED +/* Fatal error in PNG image of libpng - can't continue */ +PNG_EXPORTA(102, void, png_error, (png_const_structrp png_ptr, + png_const_charp error_message), PNG_NORETURN); + +/* The same, but the chunk name is prepended to the error string. */ +PNG_EXPORTA(103, void, png_chunk_error, (png_const_structrp png_ptr, + png_const_charp error_message), PNG_NORETURN); + +#else +/* Fatal error in PNG image of libpng - can't continue */ +PNG_EXPORTA(104, void, png_err, (png_const_structrp png_ptr), PNG_NORETURN); +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* Non-fatal error in libpng. Can continue, but may have a problem. */ +PNG_EXPORT(105, void, png_warning, (png_const_structrp png_ptr, + png_const_charp warning_message)); + +/* Non-fatal error in libpng, chunk name is prepended to message. */ +PNG_EXPORT(106, void, png_chunk_warning, (png_const_structrp png_ptr, + png_const_charp warning_message)); +#endif + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +/* Benign error in libpng. Can continue, but may have a problem. + * User can choose whether to handle as a fatal error or as a warning. */ +PNG_EXPORT(107, void, png_benign_error, (png_const_structrp png_ptr, + png_const_charp warning_message)); + +#ifdef PNG_READ_SUPPORTED +/* Same, chunk name is prepended to message (only during read) */ +PNG_EXPORT(108, void, png_chunk_benign_error, (png_const_structrp png_ptr, + png_const_charp warning_message)); +#endif + +PNG_EXPORT(109, void, png_set_benign_errors, + (png_structrp png_ptr, int allowed)); +#else +# ifdef PNG_ALLOW_BENIGN_ERRORS +# define png_benign_error png_warning +# define png_chunk_benign_error png_chunk_warning +# else +# define png_benign_error png_error +# define png_chunk_benign_error png_chunk_error +# endif +#endif + +/* The png_set_ functions are for storing values in the png_info_struct. + * Similarly, the png_get_ calls are used to read values from the + * png_info_struct, either storing the parameters in the passed variables, or + * setting pointers into the png_info_struct where the data is stored. The + * png_get_ functions return a non-zero value if the data was available + * in info_ptr, or return zero and do not change any of the parameters if the + * data was not available. + * + * These functions should be used instead of directly accessing png_info + * to avoid problems with future changes in the size and internal layout of + * png_info_struct. + */ +/* Returns "flag" if chunk data is valid in info_ptr. */ +PNG_EXPORT(110, png_uint_32, png_get_valid, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 flag)); + +/* Returns number of bytes needed to hold a transformed row. */ +PNG_EXPORT(111, png_size_t, png_get_rowbytes, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +#ifdef PNG_INFO_IMAGE_SUPPORTED +/* Returns row_pointers, which is an array of pointers to scanlines that was + * returned from png_read_png(). + */ +PNG_EXPORT(112, png_bytepp, png_get_rows, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Set row_pointers, which is an array of pointers to scanlines for use + * by png_write_png(). + */ +PNG_EXPORT(113, void, png_set_rows, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytepp row_pointers)); +#endif + +/* Returns number of color channels in image. */ +PNG_EXPORT(114, png_byte, png_get_channels, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +#ifdef PNG_EASY_ACCESS_SUPPORTED +/* Returns image width in pixels. */ +PNG_EXPORT(115, png_uint_32, png_get_image_width, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image height in pixels. */ +PNG_EXPORT(116, png_uint_32, png_get_image_height, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image bit_depth. */ +PNG_EXPORT(117, png_byte, png_get_bit_depth, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image color_type. */ +PNG_EXPORT(118, png_byte, png_get_color_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image filter_type. */ +PNG_EXPORT(119, png_byte, png_get_filter_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image interlace_type. */ +PNG_EXPORT(120, png_byte, png_get_interlace_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image compression_type. */ +PNG_EXPORT(121, png_byte, png_get_compression_type, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); + +/* Returns image resolution in pixels per meter, from pHYs chunk data. */ +PNG_EXPORT(122, png_uint_32, png_get_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(123, png_uint_32, png_get_x_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(124, png_uint_32, png_get_y_pixels_per_meter, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +/* Returns pixel aspect ratio, computed from pHYs chunk data. */ +PNG_FP_EXPORT(125, float, png_get_pixel_aspect_ratio, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +PNG_FIXED_EXPORT(210, png_fixed_point, png_get_pixel_aspect_ratio_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) + +/* Returns image x, y offset in pixels or microns, from oFFs chunk data. */ +PNG_EXPORT(126, png_int_32, png_get_x_offset_pixels, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(127, png_int_32, png_get_y_offset_pixels, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(128, png_int_32, png_get_x_offset_microns, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); +PNG_EXPORT(129, png_int_32, png_get_y_offset_microns, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +#endif /* PNG_EASY_ACCESS_SUPPORTED */ + +#ifdef PNG_READ_SUPPORTED +/* Returns pointer to signature string read from PNG header */ +PNG_EXPORT(130, png_const_bytep, png_get_signature, (png_const_structrp png_ptr, + png_const_inforp info_ptr)); +#endif + +#ifdef PNG_bKGD_SUPPORTED +PNG_EXPORT(131, png_uint_32, png_get_bKGD, (png_const_structrp png_ptr, + png_inforp info_ptr, png_color_16p *background)); +#endif + +#ifdef PNG_bKGD_SUPPORTED +PNG_EXPORT(132, void, png_set_bKGD, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_color_16p background)); +#endif + +#ifdef PNG_cHRM_SUPPORTED +PNG_FP_EXPORT(133, png_uint_32, png_get_cHRM, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *white_x, double *white_y, double *red_x, + double *red_y, double *green_x, double *green_y, double *blue_x, + double *blue_y)) +PNG_FP_EXPORT(230, png_uint_32, png_get_cHRM_XYZ, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *red_X, double *red_Y, double *red_Z, + double *green_X, double *green_Y, double *green_Z, double *blue_X, + double *blue_Y, double *blue_Z)) +PNG_FIXED_EXPORT(134, png_uint_32, png_get_cHRM_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_white_x, png_fixed_point *int_white_y, + png_fixed_point *int_red_x, png_fixed_point *int_red_y, + png_fixed_point *int_green_x, png_fixed_point *int_green_y, + png_fixed_point *int_blue_x, png_fixed_point *int_blue_y)) +PNG_FIXED_EXPORT(231, png_uint_32, png_get_cHRM_XYZ_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_red_X, png_fixed_point *int_red_Y, + png_fixed_point *int_red_Z, png_fixed_point *int_green_X, + png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, + png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, + png_fixed_point *int_blue_Z)) +#endif + +#ifdef PNG_cHRM_SUPPORTED +PNG_FP_EXPORT(135, void, png_set_cHRM, (png_const_structrp png_ptr, + png_inforp info_ptr, + double white_x, double white_y, double red_x, double red_y, double green_x, + double green_y, double blue_x, double blue_y)) +PNG_FP_EXPORT(232, void, png_set_cHRM_XYZ, (png_const_structrp png_ptr, + png_inforp info_ptr, double red_X, double red_Y, double red_Z, + double green_X, double green_Y, double green_Z, double blue_X, + double blue_Y, double blue_Z)) +PNG_FIXED_EXPORT(136, void, png_set_cHRM_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_white_x, + png_fixed_point int_white_y, png_fixed_point int_red_x, + png_fixed_point int_red_y, png_fixed_point int_green_x, + png_fixed_point int_green_y, png_fixed_point int_blue_x, + png_fixed_point int_blue_y)) +PNG_FIXED_EXPORT(233, void, png_set_cHRM_XYZ_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_red_X, png_fixed_point int_red_Y, + png_fixed_point int_red_Z, png_fixed_point int_green_X, + png_fixed_point int_green_Y, png_fixed_point int_green_Z, + png_fixed_point int_blue_X, png_fixed_point int_blue_Y, + png_fixed_point int_blue_Z)) +#endif + +#ifdef PNG_gAMA_SUPPORTED +PNG_FP_EXPORT(137, png_uint_32, png_get_gAMA, (png_const_structrp png_ptr, + png_const_inforp info_ptr, double *file_gamma)) +PNG_FIXED_EXPORT(138, png_uint_32, png_get_gAMA_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_file_gamma)) +#endif + +#ifdef PNG_gAMA_SUPPORTED +PNG_FP_EXPORT(139, void, png_set_gAMA, (png_const_structrp png_ptr, + png_inforp info_ptr, double file_gamma)) +PNG_FIXED_EXPORT(140, void, png_set_gAMA_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, png_fixed_point int_file_gamma)) +#endif + +#ifdef PNG_hIST_SUPPORTED +PNG_EXPORT(141, png_uint_32, png_get_hIST, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_16p *hist)); +#endif + +#ifdef PNG_hIST_SUPPORTED +PNG_EXPORT(142, void, png_set_hIST, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_uint_16p hist)); +#endif + +PNG_EXPORT(143, png_uint_32, png_get_IHDR, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *width, png_uint_32 *height, + int *bit_depth, int *color_type, int *interlace_method, + int *compression_method, int *filter_method)); + +PNG_EXPORT(144, void, png_set_IHDR, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_method, int compression_method, + int filter_method)); + +#ifdef PNG_oFFs_SUPPORTED +PNG_EXPORT(145, png_uint_32, png_get_oFFs, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_int_32 *offset_x, png_int_32 *offset_y, + int *unit_type)); +#endif + +#ifdef PNG_oFFs_SUPPORTED +PNG_EXPORT(146, void, png_set_oFFs, (png_const_structrp png_ptr, + png_inforp info_ptr, png_int_32 offset_x, png_int_32 offset_y, + int unit_type)); +#endif + +#ifdef PNG_pCAL_SUPPORTED +PNG_EXPORT(147, png_uint_32, png_get_pCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, png_charp *purpose, png_int_32 *X0, + png_int_32 *X1, int *type, int *nparams, png_charp *units, + png_charpp *params)); +#endif + +#ifdef PNG_pCAL_SUPPORTED +PNG_EXPORT(148, void, png_set_pCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_charp purpose, png_int_32 X0, png_int_32 X1, + int type, int nparams, png_const_charp units, png_charpp params)); +#endif + +#ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(149, png_uint_32, png_get_pHYs, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, + int *unit_type)); +#endif + +#ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(150, void, png_set_pHYs, (png_const_structrp png_ptr, + png_inforp info_ptr, png_uint_32 res_x, png_uint_32 res_y, int unit_type)); +#endif + +PNG_EXPORT(151, png_uint_32, png_get_PLTE, (png_const_structrp png_ptr, + png_inforp info_ptr, png_colorp *palette, int *num_palette)); + +PNG_EXPORT(152, void, png_set_PLTE, (png_structrp png_ptr, + png_inforp info_ptr, png_const_colorp palette, int num_palette)); + +#ifdef PNG_sBIT_SUPPORTED +PNG_EXPORT(153, png_uint_32, png_get_sBIT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_color_8p *sig_bit)); +#endif + +#ifdef PNG_sBIT_SUPPORTED +PNG_EXPORT(154, void, png_set_sBIT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_color_8p sig_bit)); +#endif + +#ifdef PNG_sRGB_SUPPORTED +PNG_EXPORT(155, png_uint_32, png_get_sRGB, (png_const_structrp png_ptr, + png_const_inforp info_ptr, int *file_srgb_intent)); +#endif + +#ifdef PNG_sRGB_SUPPORTED +PNG_EXPORT(156, void, png_set_sRGB, (png_const_structrp png_ptr, + png_inforp info_ptr, int srgb_intent)); +PNG_EXPORT(157, void, png_set_sRGB_gAMA_and_cHRM, (png_const_structrp png_ptr, + png_inforp info_ptr, int srgb_intent)); +#endif + +#ifdef PNG_iCCP_SUPPORTED +PNG_EXPORT(158, png_uint_32, png_get_iCCP, (png_const_structrp png_ptr, + png_inforp info_ptr, png_charpp name, int *compression_type, + png_bytepp profile, png_uint_32 *proflen)); +#endif + +#ifdef PNG_iCCP_SUPPORTED +PNG_EXPORT(159, void, png_set_iCCP, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_charp name, int compression_type, + png_const_bytep profile, png_uint_32 proflen)); +#endif + +#ifdef PNG_sPLT_SUPPORTED +PNG_EXPORT(160, int, png_get_sPLT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_sPLT_tpp entries)); +#endif + +#ifdef PNG_sPLT_SUPPORTED +PNG_EXPORT(161, void, png_set_sPLT, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_sPLT_tp entries, int nentries)); +#endif + +#ifdef PNG_TEXT_SUPPORTED +/* png_get_text also returns the number of text chunks in *num_text */ +PNG_EXPORT(162, int, png_get_text, (png_const_structrp png_ptr, + png_inforp info_ptr, png_textp *text_ptr, int *num_text)); +#endif + +/* Note while png_set_text() will accept a structure whose text, + * language, and translated keywords are NULL pointers, the structure + * returned by png_get_text will always contain regular + * zero-terminated C strings. They might be empty strings but + * they will never be NULL pointers. + */ + +#ifdef PNG_TEXT_SUPPORTED +PNG_EXPORT(163, void, png_set_text, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_textp text_ptr, int num_text)); +#endif + +#ifdef PNG_tIME_SUPPORTED +PNG_EXPORT(164, png_uint_32, png_get_tIME, (png_const_structrp png_ptr, + png_inforp info_ptr, png_timep *mod_time)); +#endif + +#ifdef PNG_tIME_SUPPORTED +PNG_EXPORT(165, void, png_set_tIME, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_timep mod_time)); +#endif + +#ifdef PNG_tRNS_SUPPORTED +PNG_EXPORT(166, png_uint_32, png_get_tRNS, (png_const_structrp png_ptr, + png_inforp info_ptr, png_bytep *trans_alpha, int *num_trans, + png_color_16p *trans_color)); +#endif + +#ifdef PNG_tRNS_SUPPORTED +PNG_EXPORT(167, void, png_set_tRNS, (png_structrp png_ptr, + png_inforp info_ptr, png_const_bytep trans_alpha, int num_trans, + png_const_color_16p trans_color)); +#endif + +#ifdef PNG_sCAL_SUPPORTED +PNG_FP_EXPORT(168, png_uint_32, png_get_sCAL, (png_const_structrp png_ptr, + png_const_inforp info_ptr, int *unit, double *width, double *height)) +#if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || \ + defined(PNG_FLOATING_POINT_SUPPORTED) +/* NOTE: this API is currently implemented using floating point arithmetic, + * consequently it can only be used on systems with floating point support. + * In any case the range of values supported by png_fixed_point is small and it + * is highly recommended that png_get_sCAL_s be used instead. + */ +PNG_FIXED_EXPORT(214, png_uint_32, png_get_sCAL_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, + png_fixed_point *width, png_fixed_point *height)) +#endif +PNG_EXPORT(169, png_uint_32, png_get_sCAL_s, + (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit, + png_charpp swidth, png_charpp sheight)); + +PNG_FP_EXPORT(170, void, png_set_sCAL, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, double width, double height)) +PNG_FIXED_EXPORT(213, void, png_set_sCAL_fixed, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, png_fixed_point width, + png_fixed_point height)) +PNG_EXPORT(171, void, png_set_sCAL_s, (png_const_structrp png_ptr, + png_inforp info_ptr, int unit, + png_const_charp swidth, png_const_charp sheight)); +#endif /* PNG_sCAL_SUPPORTED */ + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +/* Provide the default handling for all unknown chunks or, optionally, for + * specific unknown chunks. + * + * NOTE: prior to 1.6.0 the handling specified for particular chunks on read was + * ignored and the default was used, the per-chunk setting only had an effect on + * write. If you wish to have chunk-specific handling on read in code that must + * work on earlier versions you must use a user chunk callback to specify the + * desired handling (keep or discard.) + * + * The 'keep' parameter is a PNG_HANDLE_CHUNK_ value as listed below. The + * parameter is interpreted as follows: + * + * READ: + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Known chunks: do normal libpng processing, do not keep the chunk (but + * see the comments below about PNG_HANDLE_AS_UNKNOWN_SUPPORTED) + * Unknown chunks: for a specific chunk use the global default, when used + * as the default discard the chunk data. + * PNG_HANDLE_CHUNK_NEVER: + * Discard the chunk data. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Keep the chunk data if the chunk is not critical else raise a chunk + * error. + * PNG_HANDLE_CHUNK_ALWAYS: + * Keep the chunk data. + * + * If the chunk data is saved it can be retrieved using png_get_unknown_chunks, + * below. Notice that specifying "AS_DEFAULT" as a global default is equivalent + * to specifying "NEVER", however when "AS_DEFAULT" is used for specific chunks + * it simply resets the behavior to the libpng default. + * + * INTERACTION WTIH USER CHUNK CALLBACKS: + * The per-chunk handling is always used when there is a png_user_chunk_ptr + * callback and the callback returns 0; the chunk is then always stored *unless* + * it is critical and the per-chunk setting is other than ALWAYS. Notice that + * the global default is *not* used in this case. (In effect the per-chunk + * value is incremented to at least IF_SAFE.) + * + * IMPORTANT NOTE: this behavior will change in libpng 1.7 - the global and + * per-chunk defaults will be honored. If you want to preserve the current + * behavior when your callback returns 0 you must set PNG_HANDLE_CHUNK_IF_SAFE + * as the default - if you don't do this libpng 1.6 will issue a warning. + * + * If you want unhandled unknown chunks to be discarded in libpng 1.6 and + * earlier simply return '1' (handled). + * + * PNG_HANDLE_AS_UNKNOWN_SUPPORTED: + * If this is *not* set known chunks will always be handled by libpng and + * will never be stored in the unknown chunk list. Known chunks listed to + * png_set_keep_unknown_chunks will have no effect. If it is set then known + * chunks listed with a keep other than AS_DEFAULT will *never* be processed + * by libpng, in addition critical chunks must either be processed by the + * callback or saved. + * + * The IHDR and IEND chunks must not be listed. Because this turns off the + * default handling for chunks that would otherwise be recognized the + * behavior of libpng transformations may well become incorrect! + * + * WRITE: + * When writing chunks the options only apply to the chunks specified by + * png_set_unknown_chunks (below), libpng will *always* write known chunks + * required by png_set_ calls and will always write the core critical chunks + * (as required for PLTE). + * + * Each chunk in the png_set_unknown_chunks list is looked up in the + * png_set_keep_unknown_chunks list to find the keep setting, this is then + * interpreted as follows: + * + * PNG_HANDLE_CHUNK_AS_DEFAULT: + * Write safe-to-copy chunks and write other chunks if the global + * default is set to _ALWAYS, otherwise don't write this chunk. + * PNG_HANDLE_CHUNK_NEVER: + * Do not write the chunk. + * PNG_HANDLE_CHUNK_IF_SAFE: + * Write the chunk if it is safe-to-copy, otherwise do not write it. + * PNG_HANDLE_CHUNK_ALWAYS: + * Write the chunk. + * + * Note that the default behavior is effectively the opposite of the read case - + * in read unknown chunks are not stored by default, in write they are written + * by default. Also the behavior of PNG_HANDLE_CHUNK_IF_SAFE is very different + * - on write the safe-to-copy bit is checked, on read the critical bit is + * checked and on read if the chunk is critical an error will be raised. + * + * num_chunks: + * =========== + * If num_chunks is positive, then the "keep" parameter specifies the manner + * for handling only those chunks appearing in the chunk_list array, + * otherwise the chunk list array is ignored. + * + * If num_chunks is 0 the "keep" parameter specifies the default behavior for + * unknown chunks, as described above. + * + * If num_chunks is negative, then the "keep" parameter specifies the manner + * for handling all unknown chunks plus all chunks recognized by libpng + * except for the IHDR, PLTE, tRNS, IDAT, and IEND chunks (which continue to + * be processed by libpng. + */ +PNG_EXPORT(172, void, png_set_keep_unknown_chunks, (png_structrp png_ptr, + int keep, png_const_bytep chunk_list, int num_chunks)); + +/* The "keep" PNG_HANDLE_CHUNK_ parameter for the specified chunk is returned; + * the result is therefore true (non-zero) if special handling is required, + * false for the default handling. + */ +PNG_EXPORT(173, int, png_handle_as_unknown, (png_const_structrp png_ptr, + png_const_bytep chunk_name)); +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +PNG_EXPORT(174, void, png_set_unknown_chunks, (png_const_structrp png_ptr, + png_inforp info_ptr, png_const_unknown_chunkp unknowns, + int num_unknowns)); + /* NOTE: prior to 1.6.0 this routine set the 'location' field of the added + * unknowns to the location currently stored in the png_struct. This is + * invariably the wrong value on write. To fix this call the following API + * for each chunk in the list with the correct location. If you know your + * code won't be compiled on earlier versions you can rely on + * png_set_unknown_chunks(write-ptr, png_get_unknown_chunks(read-ptr)) doing + * the correct thing. + */ + +PNG_EXPORT(175, void, png_set_unknown_chunk_location, + (png_const_structrp png_ptr, png_inforp info_ptr, int chunk, int location)); + +PNG_EXPORT(176, int, png_get_unknown_chunks, (png_const_structrp png_ptr, + png_inforp info_ptr, png_unknown_chunkpp entries)); +#endif + +/* Png_free_data() will turn off the "valid" flag for anything it frees. + * If you need to turn it off for a chunk that your application has freed, + * you can use png_set_invalid(png_ptr, info_ptr, PNG_INFO_CHNK); + */ +PNG_EXPORT(177, void, png_set_invalid, (png_const_structrp png_ptr, + png_inforp info_ptr, int mask)); + +#ifdef PNG_INFO_IMAGE_SUPPORTED +/* The "params" pointer is currently not used and is for future expansion. */ +PNG_EXPORT(178, void, png_read_png, (png_structrp png_ptr, png_inforp info_ptr, + int transforms, png_voidp params)); +PNG_EXPORT(179, void, png_write_png, (png_structrp png_ptr, png_inforp info_ptr, + int transforms, png_voidp params)); +#endif + +PNG_EXPORT(180, png_const_charp, png_get_copyright, + (png_const_structrp png_ptr)); +PNG_EXPORT(181, png_const_charp, png_get_header_ver, + (png_const_structrp png_ptr)); +PNG_EXPORT(182, png_const_charp, png_get_header_version, + (png_const_structrp png_ptr)); +PNG_EXPORT(183, png_const_charp, png_get_libpng_ver, + (png_const_structrp png_ptr)); + +#ifdef PNG_MNG_FEATURES_SUPPORTED +PNG_EXPORT(184, png_uint_32, png_permit_mng_features, (png_structrp png_ptr, + png_uint_32 mng_features_permitted)); +#endif + +/* For use in png_set_keep_unknown, added to version 1.2.6 */ +#define PNG_HANDLE_CHUNK_AS_DEFAULT 0 +#define PNG_HANDLE_CHUNK_NEVER 1 +#define PNG_HANDLE_CHUNK_IF_SAFE 2 +#define PNG_HANDLE_CHUNK_ALWAYS 3 +#define PNG_HANDLE_CHUNK_LAST 4 + +/* Strip the prepended error numbers ("#nnn ") from error and warning + * messages before passing them to the error or warning handler. + */ +#ifdef PNG_ERROR_NUMBERS_SUPPORTED +PNG_EXPORT(185, void, png_set_strip_error_numbers, (png_structrp png_ptr, + png_uint_32 strip_mode)); +#endif + +/* Added in libpng-1.2.6 */ +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +PNG_EXPORT(186, void, png_set_user_limits, (png_structrp png_ptr, + png_uint_32 user_width_max, png_uint_32 user_height_max)); +PNG_EXPORT(187, png_uint_32, png_get_user_width_max, + (png_const_structrp png_ptr)); +PNG_EXPORT(188, png_uint_32, png_get_user_height_max, + (png_const_structrp png_ptr)); +/* Added in libpng-1.4.0 */ +PNG_EXPORT(189, void, png_set_chunk_cache_max, (png_structrp png_ptr, + png_uint_32 user_chunk_cache_max)); +PNG_EXPORT(190, png_uint_32, png_get_chunk_cache_max, + (png_const_structrp png_ptr)); +/* Added in libpng-1.4.1 */ +PNG_EXPORT(191, void, png_set_chunk_malloc_max, (png_structrp png_ptr, + png_alloc_size_t user_chunk_cache_max)); +PNG_EXPORT(192, png_alloc_size_t, png_get_chunk_malloc_max, + (png_const_structrp png_ptr)); +#endif + +#if defined(PNG_INCH_CONVERSIONS_SUPPORTED) +PNG_EXPORT(193, png_uint_32, png_get_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_EXPORT(194, png_uint_32, png_get_x_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_EXPORT(195, png_uint_32, png_get_y_pixels_per_inch, + (png_const_structrp png_ptr, png_const_inforp info_ptr)); + +PNG_FP_EXPORT(196, float, png_get_x_offset_inches, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ +PNG_FIXED_EXPORT(211, png_fixed_point, png_get_x_offset_inches_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#endif + +PNG_FP_EXPORT(197, float, png_get_y_offset_inches, (png_const_structrp png_ptr, + png_const_inforp info_ptr)) +#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */ +PNG_FIXED_EXPORT(212, png_fixed_point, png_get_y_offset_inches_fixed, + (png_const_structrp png_ptr, png_const_inforp info_ptr)) +#endif + +# ifdef PNG_pHYs_SUPPORTED +PNG_EXPORT(198, png_uint_32, png_get_pHYs_dpi, (png_const_structrp png_ptr, + png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y, + int *unit_type)); +# endif /* PNG_pHYs_SUPPORTED */ +#endif /* PNG_INCH_CONVERSIONS_SUPPORTED */ + +/* Added in libpng-1.4.0 */ +#ifdef PNG_IO_STATE_SUPPORTED +PNG_EXPORT(199, png_uint_32, png_get_io_state, (png_const_structrp png_ptr)); + +/* Removed from libpng 1.6; use png_get_io_chunk_type. */ +PNG_REMOVED(200, png_const_bytep, png_get_io_chunk_name, (png_structrp png_ptr), + PNG_DEPRECATED) + +PNG_EXPORT(216, png_uint_32, png_get_io_chunk_type, + (png_const_structrp png_ptr)); + +/* The flags returned by png_get_io_state() are the following: */ +# define PNG_IO_NONE 0x0000 /* no I/O at this moment */ +# define PNG_IO_READING 0x0001 /* currently reading */ +# define PNG_IO_WRITING 0x0002 /* currently writing */ +# define PNG_IO_SIGNATURE 0x0010 /* currently at the file signature */ +# define PNG_IO_CHUNK_HDR 0x0020 /* currently at the chunk header */ +# define PNG_IO_CHUNK_DATA 0x0040 /* currently at the chunk data */ +# define PNG_IO_CHUNK_CRC 0x0080 /* currently at the chunk crc */ +# define PNG_IO_MASK_OP 0x000f /* current operation: reading/writing */ +# define PNG_IO_MASK_LOC 0x00f0 /* current location: sig/hdr/data/crc */ +#endif /* ?PNG_IO_STATE_SUPPORTED */ + +/* Interlace support. The following macros are always defined so that if + * libpng interlace handling is turned off the macros may be used to handle + * interlaced images within the application. + */ +#define PNG_INTERLACE_ADAM7_PASSES 7 + +/* Two macros to return the first row and first column of the original, + * full, image which appears in a given pass. 'pass' is in the range 0 + * to 6 and the result is in the range 0 to 7. + */ +#define PNG_PASS_START_ROW(pass) (((1&~(pass))<<(3-((pass)>>1)))&7) +#define PNG_PASS_START_COL(pass) (((1& (pass))<<(3-(((pass)+1)>>1)))&7) + +/* A macro to return the offset between pixels in the output row for a pair of + * pixels in the input - effectively the inverse of the 'COL_SHIFT' macro that + * follows. Note that ROW_OFFSET is the offset from one row to the next whereas + * COL_OFFSET is from one column to the next, within a row. + */ +#define PNG_PASS_ROW_OFFSET(pass) ((pass)>2?(8>>(((pass)-1)>>1)):8) +#define PNG_PASS_COL_OFFSET(pass) (1<<((7-(pass))>>1)) + +/* Two macros to help evaluate the number of rows or columns in each + * pass. This is expressed as a shift - effectively log2 of the number or + * rows or columns in each 8x8 tile of the original image. + */ +#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3) +#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3) + +/* Hence two macros to determine the number of rows or columns in a given + * pass of an image given its height or width. In fact these macros may + * return non-zero even though the sub-image is empty, because the other + * dimension may be empty for a small image. + */ +#define PNG_PASS_ROWS(height, pass) (((height)+(((1<>PNG_PASS_ROW_SHIFT(pass)) +#define PNG_PASS_COLS(width, pass) (((width)+(((1<>PNG_PASS_COL_SHIFT(pass)) + +/* For the reader row callbacks (both progressive and sequential) it is + * necessary to find the row in the output image given a row in an interlaced + * image, so two more macros: + */ +#define PNG_ROW_FROM_PASS_ROW(y_in, pass) \ + (((y_in)<>(((7-(off))-(pass))<<2)) & 0xF) | \ + ((0x01145AF0>>(((7-(off))-(pass))<<2)) & 0xF0)) + +#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \ + ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1) +#define PNG_COL_IN_INTERLACE_PASS(x, pass) \ + ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1) + +#ifdef PNG_READ_COMPOSITE_NODIV_SUPPORTED +/* With these routines we avoid an integer divide, which will be slower on + * most machines. However, it does take more operations than the corresponding + * divide method, so it may be slower on a few RISC systems. There are two + * shifts (by 8 or 16 bits) and an addition, versus a single integer divide. + * + * Note that the rounding factors are NOT supposed to be the same! 128 and + * 32768 are correct for the NODIV code; 127 and 32767 are correct for the + * standard method. + * + * [Optimized code by Greg Roelofs and Mark Adler...blame us for bugs. :-) ] + */ + + /* fg and bg should be in `gamma 1.0' space; alpha is the opacity */ + +# define png_composite(composite, fg, alpha, bg) \ + { png_uint_16 temp = (png_uint_16)((png_uint_16)(fg) \ + * (png_uint_16)(alpha) \ + + (png_uint_16)(bg)*(png_uint_16)(255 \ + - (png_uint_16)(alpha)) + 128); \ + (composite) = (png_byte)((temp + (temp >> 8)) >> 8); } + +# define png_composite_16(composite, fg, alpha, bg) \ + { png_uint_32 temp = (png_uint_32)((png_uint_32)(fg) \ + * (png_uint_32)(alpha) \ + + (png_uint_32)(bg)*(65535 \ + - (png_uint_32)(alpha)) + 32768); \ + (composite) = (png_uint_16)((temp + (temp >> 16)) >> 16); } + +#else /* Standard method using integer division */ + +# define png_composite(composite, fg, alpha, bg) \ + (composite) = (png_byte)(((png_uint_16)(fg) * (png_uint_16)(alpha) + \ + (png_uint_16)(bg) * (png_uint_16)(255 - (png_uint_16)(alpha)) + \ + 127) / 255) + +# define png_composite_16(composite, fg, alpha, bg) \ + (composite) = (png_uint_16)(((png_uint_32)(fg) * (png_uint_32)(alpha) + \ + (png_uint_32)(bg)*(png_uint_32)(65535 - (png_uint_32)(alpha)) + \ + 32767) / 65535) +#endif /* PNG_READ_COMPOSITE_NODIV_SUPPORTED */ + +#ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(201, png_uint_32, png_get_uint_32, (png_const_bytep buf)); +PNG_EXPORT(202, png_uint_16, png_get_uint_16, (png_const_bytep buf)); +PNG_EXPORT(203, png_int_32, png_get_int_32, (png_const_bytep buf)); +#endif + +PNG_EXPORT(204, png_uint_32, png_get_uint_31, (png_const_structrp png_ptr, + png_const_bytep buf)); +/* No png_get_int_16 -- may be added if there's a real need for it. */ + +/* Place a 32-bit number into a buffer in PNG byte order (big-endian). */ +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(205, void, png_save_uint_32, (png_bytep buf, png_uint_32 i)); +#endif +#ifdef PNG_SAVE_INT_32_SUPPORTED +PNG_EXPORT(206, void, png_save_int_32, (png_bytep buf, png_int_32 i)); +#endif + +/* Place a 16-bit number into a buffer in PNG byte order. + * The parameter is declared unsigned int, not png_uint_16, + * just to avoid potential problems on pre-ANSI C compilers. + */ +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); +/* No png_save_int_16 -- may be added if there's a real need for it. */ +#endif + +#ifdef PNG_USE_READ_MACROS +/* Inline macros to do direct reads of bytes from the input buffer. + * The png_get_int_32() routine assumes we are using two's complement + * format for negative values, which is almost certainly true. + */ +# define PNG_get_uint_32(buf) \ + (((png_uint_32)(*(buf)) << 24) + \ + ((png_uint_32)(*((buf) + 1)) << 16) + \ + ((png_uint_32)(*((buf) + 2)) << 8) + \ + ((png_uint_32)(*((buf) + 3)))) + + /* From libpng-1.4.0 until 1.4.4, the png_get_uint_16 macro (but not the + * function) incorrectly returned a value of type png_uint_32. + */ +# define PNG_get_uint_16(buf) \ + ((png_uint_16) \ + (((unsigned int)(*(buf)) << 8) + \ + ((unsigned int)(*((buf) + 1))))) + +# define PNG_get_int_32(buf) \ + ((png_int_32)((*(buf) & 0x80) \ + ? -((png_int_32)((png_get_uint_32(buf) ^ 0xffffffffL) + 1)) \ + : (png_int_32)png_get_uint_32(buf))) + + /* If PNG_PREFIX is defined the same thing as below happens in pnglibconf.h, + * but defining a macro name prefixed with PNG_PREFIX. + */ +# ifndef PNG_PREFIX +# define png_get_uint_32(buf) PNG_get_uint_32(buf) +# define png_get_uint_16(buf) PNG_get_uint_16(buf) +# define png_get_int_32(buf) PNG_get_int_32(buf) +# endif +#else +# ifdef PNG_PREFIX + /* No macros; revert to the (redefined) function */ +# define PNG_get_uint_32 (png_get_uint_32) +# define PNG_get_uint_16 (png_get_uint_16) +# define PNG_get_int_32 (png_get_int_32) +# endif +#endif + +/******************************************************************************* + * SIMPLIFIED API + ******************************************************************************* + * + * Please read the documentation in libpng-manual.txt (TODO: write said + * documentation) if you don't understand what follows. + * + * The simplified API hides the details of both libpng and the PNG file format + * itself. It allows PNG files to be read into a very limited number of + * in-memory bitmap formats or to be written from the same formats. If these + * formats do not accomodate your needs then you can, and should, use the more + * sophisticated APIs above - these support a wide variety of in-memory formats + * and a wide variety of sophisticated transformations to those formats as well + * as a wide variety of APIs to manipulate ancillary information. + * + * To read a PNG file using the simplified API: + * + * 1) Declare a 'png_image' structure (see below) on the stack and set the + * version field to PNG_IMAGE_VERSION. + * 2) Call the appropriate png_image_begin_read... function. + * 3) Set the png_image 'format' member to the required sample format. + * 4) Allocate a buffer for the image and, if required, the color-map. + * 5) Call png_image_finish_read to read the image and, if required, the + * color-map into your buffers. + * + * There are no restrictions on the format of the PNG input itself; all valid + * color types, bit depths, and interlace methods are acceptable, and the + * input image is transformed as necessary to the requested in-memory format + * during the png_image_finish_read() step. The only caveat is that if you + * request a color-mapped image from a PNG that is full-color or makes + * complex use of an alpha channel the transformation is extremely lossy and the + * result may look terrible. + * + * To write a PNG file using the simplified API: + * + * 1) Declare a 'png_image' structure on the stack and memset() it to all zero. + * 2) Initialize the members of the structure that describe the image, setting + * the 'format' member to the format of the image samples. + * 3) Call the appropriate png_image_write... function with a pointer to the + * image and, if necessary, the color-map to write the PNG data. + * + * png_image is a structure that describes the in-memory format of an image + * when it is being read or defines the in-memory format of an image that you + * need to write: + */ +#define PNG_IMAGE_VERSION 1 + +typedef struct png_control *png_controlp; +typedef struct +{ + png_controlp opaque; /* Initialize to NULL, free with png_image_free */ + png_uint_32 version; /* Set to PNG_IMAGE_VERSION */ + png_uint_32 width; /* Image width in pixels (columns) */ + png_uint_32 height; /* Image height in pixels (rows) */ + png_uint_32 format; /* Image format as defined below */ + png_uint_32 flags; /* A bit mask containing informational flags */ + png_uint_32 colormap_entries; + /* Number of entries in the color-map */ + + /* In the event of an error or warning the following field will be set to a + * non-zero value and the 'message' field will contain a '\0' terminated + * string with the libpng error or warning message. If both warnings and + * an error were encountered, only the error is recorded. If there + * are multiple warnings, only the first one is recorded. + * + * The upper 30 bits of this value are reserved, the low two bits contain + * a value as follows: + */ +# define PNG_IMAGE_WARNING 1 +# define PNG_IMAGE_ERROR 2 + /* + * The result is a two bit code such that a value more than 1 indicates + * a failure in the API just called: + * + * 0 - no warning or error + * 1 - warning + * 2 - error + * 3 - error preceded by warning + */ +# define PNG_IMAGE_FAILED(png_cntrl) ((((png_cntrl).warning_or_error)&0x03)>1) + + png_uint_32 warning_or_error; + + char message[64]; +} png_image, *png_imagep; + +/* The samples of the image have one to four channels whose components have + * original values in the range 0 to 1.0: + * + * 1: A single gray or luminance channel (G). + * 2: A gray/luminance channel and an alpha channel (GA). + * 3: Three red, green, blue color channels (RGB). + * 4: Three color channels and an alpha channel (RGBA). + * + * The components are encoded in one of two ways: + * + * a) As a small integer, value 0..255, contained in a single byte. For the + * alpha channel the original value is simply value/255. For the color or + * luminance channels the value is encoded according to the sRGB specification + * and matches the 8-bit format expected by typical display devices. + * + * The color/gray channels are not scaled (pre-multiplied) by the alpha + * channel and are suitable for passing to color management software. + * + * b) As a value in the range 0..65535, contained in a 2-byte integer. All + * channels can be converted to the original value by dividing by 65535; all + * channels are linear. Color channels use the RGB encoding (RGB end-points) of + * the sRGB specification. This encoding is identified by the + * PNG_FORMAT_FLAG_LINEAR flag below. + * + * When the simplified API needs to convert between sRGB and linear colorspaces, + * the actual sRGB transfer curve defined in the sRGB specification (see the + * article at http://en.wikipedia.org/wiki/SRGB) is used, not the gamma=1/2.2 + * approximation used elsewhere in libpng. + * + * When an alpha channel is present it is expected to denote pixel coverage + * of the color or luminance channels and is returned as an associated alpha + * channel: the color/gray channels are scaled (pre-multiplied) by the alpha + * value. + * + * The samples are either contained directly in the image data, between 1 and 8 + * bytes per pixel according to the encoding, or are held in a color-map indexed + * by bytes in the image data. In the case of a color-map the color-map entries + * are individual samples, encoded as above, and the image data has one byte per + * pixel to select the relevant sample from the color-map. + */ + +/* PNG_FORMAT_* + * + * #defines to be used in png_image::format. Each #define identifies a + * particular layout of sample data and, if present, alpha values. There are + * separate defines for each of the two component encodings. + * + * A format is built up using single bit flag values. All combinations are + * valid. Formats can be built up from the flag values or you can use one of + * the predefined values below. When testing formats always use the FORMAT_FLAG + * macros to test for individual features - future versions of the library may + * add new flags. + * + * When reading or writing color-mapped images the format should be set to the + * format of the entries in the color-map then png_image_{read,write}_colormap + * called to read or write the color-map and set the format correctly for the + * image data. Do not set the PNG_FORMAT_FLAG_COLORMAP bit directly! + * + * NOTE: libpng can be built with particular features disabled, if you see + * compiler errors because the definition of one of the following flags has been + * compiled out it is because libpng does not have the required support. It is + * possible, however, for the libpng configuration to enable the format on just + * read or just write; in that case you may see an error at run time. You can + * guard against this by checking for the definition of the appropriate + * "_SUPPORTED" macro, one of: + * + * PNG_SIMPLIFIED_{READ,WRITE}_{BGR,AFIRST}_SUPPORTED + */ +#define PNG_FORMAT_FLAG_ALPHA 0x01U /* format with an alpha channel */ +#define PNG_FORMAT_FLAG_COLOR 0x02U /* color format: otherwise grayscale */ +#define PNG_FORMAT_FLAG_LINEAR 0x04U /* 2 byte channels else 1 byte */ +#define PNG_FORMAT_FLAG_COLORMAP 0x08U /* image data is color-mapped */ + +#ifdef PNG_FORMAT_BGR_SUPPORTED +# define PNG_FORMAT_FLAG_BGR 0x10U /* BGR colors, else order is RGB */ +#endif + +#ifdef PNG_FORMAT_AFIRST_SUPPORTED +# define PNG_FORMAT_FLAG_AFIRST 0x20U /* alpha channel comes first */ +#endif + +/* Commonly used formats have predefined macros. + * + * First the single byte (sRGB) formats: + */ +#define PNG_FORMAT_GRAY 0 +#define PNG_FORMAT_GA PNG_FORMAT_FLAG_ALPHA +#define PNG_FORMAT_AG (PNG_FORMAT_GA|PNG_FORMAT_FLAG_AFIRST) +#define PNG_FORMAT_RGB PNG_FORMAT_FLAG_COLOR +#define PNG_FORMAT_BGR (PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_BGR) +#define PNG_FORMAT_RGBA (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_ARGB (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_AFIRST) +#define PNG_FORMAT_BGRA (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_ABGR (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_AFIRST) + +/* Then the linear 2-byte formats. When naming these "Y" is used to + * indicate a luminance (gray) channel. + */ +#define PNG_FORMAT_LINEAR_Y PNG_FORMAT_FLAG_LINEAR +#define PNG_FORMAT_LINEAR_Y_ALPHA (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_ALPHA) +#define PNG_FORMAT_LINEAR_RGB (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR) +#define PNG_FORMAT_LINEAR_RGB_ALPHA \ + (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA) + +/* With color-mapped formats the image data is one byte for each pixel, the byte + * is an index into the color-map which is formatted as above. To obtain a + * color-mapped format it is sufficient just to add the PNG_FOMAT_FLAG_COLORMAP + * to one of the above definitions, or you can use one of the definitions below. + */ +#define PNG_FORMAT_RGB_COLORMAP (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGR_COLORMAP (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_RGBA_COLORMAP (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ARGB_COLORMAP (PNG_FORMAT_ARGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGRA_COLORMAP (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ABGR_COLORMAP (PNG_FORMAT_ABGR|PNG_FORMAT_FLAG_COLORMAP) + +/* PNG_IMAGE macros + * + * These are convenience macros to derive information from a png_image + * structure. The PNG_IMAGE_SAMPLE_ macros return values appropriate to the + * actual image sample values - either the entries in the color-map or the + * pixels in the image. The PNG_IMAGE_PIXEL_ macros return corresponding values + * for the pixels and will always return 1 for color-mapped formats. The + * remaining macros return information about the rows in the image and the + * complete image. + * + * NOTE: All the macros that take a png_image::format parameter are compile time + * constants if the format parameter is, itself, a constant. Therefore these + * macros can be used in array declarations and case labels where required. + * Similarly the macros are also pre-processor constants (sizeof is not used) so + * they can be used in #if tests. + * + * First the information about the samples. + */ +#define PNG_IMAGE_SAMPLE_CHANNELS(fmt)\ + (((fmt)&(PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA))+1) + /* Return the total number of channels in a given format: 1..4 */ + +#define PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)\ + ((((fmt) & PNG_FORMAT_FLAG_LINEAR) >> 2)+1) + /* Return the size in bytes of a single component of a pixel or color-map + * entry (as appropriate) in the image: 1 or 2. + */ + +#define PNG_IMAGE_SAMPLE_SIZE(fmt)\ + (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)) + /* This is the size of the sample data for one sample. If the image is + * color-mapped it is the size of one color-map entry (and image pixels are + * one byte in size), otherwise it is the size of one image pixel. + */ + +#define PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(fmt)\ + (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * 256) + /* The maximum size of the color-map required by the format expressed in a + * count of components. This can be used to compile-time allocate a + * color-map: + * + * png_uint_16 colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(linear_fmt)]; + * + * png_byte colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(sRGB_fmt)]; + * + * Alternatively use the PNG_IMAGE_COLORMAP_SIZE macro below to use the + * information from one of the png_image_begin_read_ APIs and dynamically + * allocate the required memory. + */ + +/* Corresponding information about the pixels */ +#define PNG_IMAGE_PIXEL_(test,fmt)\ + (((fmt)&PNG_FORMAT_FLAG_COLORMAP)?1:test(fmt)) + +#define PNG_IMAGE_PIXEL_CHANNELS(fmt)\ + PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_CHANNELS,fmt) + /* The number of separate channels (components) in a pixel; 1 for a + * color-mapped image. + */ + +#define PNG_IMAGE_PIXEL_COMPONENT_SIZE(fmt)\ + PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_COMPONENT_SIZE,fmt) + /* The size, in bytes, of each component in a pixel; 1 for a color-mapped + * image. + */ + +#define PNG_IMAGE_PIXEL_SIZE(fmt) PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_SIZE,fmt) + /* The size, in bytes, of a complete pixel; 1 for a color-mapped image. */ + +/* Information about the whole row, or whole image */ +#define PNG_IMAGE_ROW_STRIDE(image)\ + (PNG_IMAGE_PIXEL_CHANNELS((image).format) * (image).width) + /* Return the total number of components in a single row of the image; this + * is the minimum 'row stride', the minimum count of components between each + * row. For a color-mapped image this is the minimum number of bytes in a + * row. + */ + +#define PNG_IMAGE_BUFFER_SIZE(image, row_stride)\ + (PNG_IMAGE_PIXEL_COMPONENT_SIZE((image).format)*(image).height*(row_stride)) + /* Return the size, in bytes, of an image buffer given a png_image and a row + * stride - the number of components to leave space for in each row. + */ + +#define PNG_IMAGE_SIZE(image)\ + PNG_IMAGE_BUFFER_SIZE(image, PNG_IMAGE_ROW_STRIDE(image)) + /* Return the size, in bytes, of the image in memory given just a png_image; + * the row stride is the minimum stride required for the image. + */ + +#define PNG_IMAGE_COLORMAP_SIZE(image)\ + (PNG_IMAGE_SAMPLE_SIZE((image).format) * (image).colormap_entries) + /* Return the size, in bytes, of the color-map of this image. If the image + * format is not a color-map format this will return a size sufficient for + * 256 entries in the given format; check PNG_FORMAT_FLAG_COLORMAP if + * you don't want to allocate a color-map in this case. + */ + +/* PNG_IMAGE_FLAG_* + * + * Flags containing additional information about the image are held in the + * 'flags' field of png_image. + */ +#define PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB 0x01 + /* This indicates the the RGB values of the in-memory bitmap do not + * correspond to the red, green and blue end-points defined by sRGB. + */ + +#define PNG_IMAGE_FLAG_FAST 0x02 + /* On write emphasise speed over compression; the resultant PNG file will be + * larger but will be produced significantly faster, particular for large + * images. Do not use this option for images which will be distributed, only + * used it when producing intermediate files that will be read back in + * repeatedly. For a typical 24-bit image the option will double the read + * speed at the cost of increasing the image size by 25%, however for many + * more compressible images the PNG file can be 10 times larger with only a + * slight speed gain. + */ + +#define PNG_IMAGE_FLAG_16BIT_sRGB 0x04 + /* On read if the image is a 16-bit per component image and there is no gAMA + * or sRGB chunk assume that the components are sRGB encoded. Notice that + * images output by the simplified API always have gamma information; setting + * this flag only affects the interpretation of 16-bit images from an + * external source. It is recommended that the application expose this flag + * to the user; the user can normally easily recognize the difference between + * linear and sRGB encoding. This flag has no effect on write - the data + * passed to the write APIs must have the correct encoding (as defined + * above.) + * + * If the flag is not set (the default) input 16-bit per component data is + * assumed to be linear. + * + * NOTE: the flag can only be set after the png_image_begin_read_ call, + * because that call initializes the 'flags' field. + */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* READ APIs + * --------- + * + * The png_image passed to the read APIs must have been initialized by setting + * the png_controlp field 'opaque' to NULL (or, safer, memset the whole thing.) + */ +#ifdef PNG_STDIO_SUPPORTED +PNG_EXPORT(234, int, png_image_begin_read_from_file, (png_imagep image, + const char *file_name)); + /* The named file is opened for read and the image header is filled in + * from the PNG header in the file. + */ + +PNG_EXPORT(235, int, png_image_begin_read_from_stdio, (png_imagep image, + FILE* file)); + /* The PNG header is read from the stdio FILE object. */ +#endif /* PNG_STDIO_SUPPORTED */ + +PNG_EXPORT(236, int, png_image_begin_read_from_memory, (png_imagep image, + png_const_voidp memory, png_size_t size)); + /* The PNG header is read from the given memory buffer. */ + +PNG_EXPORT(237, int, png_image_finish_read, (png_imagep image, + png_const_colorp background, void *buffer, png_int_32 row_stride, + void *colormap)); + /* Finish reading the image into the supplied buffer and clean up the + * png_image structure. + * + * row_stride is the step, in byte or 2-byte units as appropriate, + * between adjacent rows. A positive stride indicates that the top-most row + * is first in the buffer - the normal top-down arrangement. A negative + * stride indicates that the bottom-most row is first in the buffer. + * + * background need only be supplied if an alpha channel must be removed from + * a png_byte format and the removal is to be done by compositing on a solid + * color; otherwise it may be NULL and any composition will be done directly + * onto the buffer. The value is an sRGB color to use for the background, + * for grayscale output the green channel is used. + * + * background must be supplied when an alpha channel must be removed from a + * single byte color-mapped output format, in other words if: + * + * 1) The original format from png_image_begin_read_from_* had + * PNG_FORMAT_FLAG_ALPHA set. + * 2) The format set by the application does not. + * 3) The format set by the application has PNG_FORMAT_FLAG_COLORMAP set and + * PNG_FORMAT_FLAG_LINEAR *not* set. + * + * For linear output removing the alpha channel is always done by compositing + * on black and background is ignored. + * + * colormap must be supplied when PNG_FORMAT_FLAG_COLORMAP is set. It must + * be at least the size (in bytes) returned by PNG_IMAGE_COLORMAP_SIZE. + * image->colormap_entries will be updated to the actual number of entries + * written to the colormap; this may be less than the original value. + */ + +PNG_EXPORT(238, void, png_image_free, (png_imagep image)); + /* Free any data allocated by libpng in image->opaque, setting the pointer to + * NULL. May be called at any time after the structure is initialized. + */ +#endif /* PNG_SIMPLIFIED_READ_SUPPORTED */ + +#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED +/* WRITE APIS + * ---------- + * For write you must initialize a png_image structure to describe the image to + * be written. To do this use memset to set the whole structure to 0 then + * initialize fields describing your image. + * + * version: must be set to PNG_IMAGE_VERSION + * opaque: must be initialized to NULL + * width: image width in pixels + * height: image height in rows + * format: the format of the data (image and color-map) you wish to write + * flags: set to 0 unless one of the defined flags applies; set + * PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB for color format images where the RGB + * values do not correspond to the colors in sRGB. + * colormap_entries: set to the number of entries in the color-map (0 to 256) + */ +PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image, + const char *file, int convert_to_8bit, const void *buffer, + png_int_32 row_stride, const void *colormap)); + /* Write the image to the named file. */ + +PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, + int convert_to_8_bit, const void *buffer, png_int_32 row_stride, + const void *colormap)); + /* Write the image to the given (FILE*). */ + +/* With both write APIs if image is in one of the linear formats with 16-bit + * data then setting convert_to_8_bit will cause the output to be an 8-bit PNG + * gamma encoded according to the sRGB specification, otherwise a 16-bit linear + * encoded PNG file is written. + * + * With color-mapped data formats the colormap parameter point to a color-map + * with at least image->colormap_entries encoded in the specified format. If + * the format is linear the written PNG color-map will be converted to sRGB + * regardless of the convert_to_8_bit flag. + * + * With all APIs row_stride is handled as in the read APIs - it is the spacing + * from one row to the next in component sized units (1 or 2 bytes) and if + * negative indicates a bottom-up row layout in the buffer. + * + * Note that the write API does not support interlacing or sub-8-bit pixels. + */ +#endif /* PNG_SIMPLIFIED_WRITE_SUPPORTED */ +/******************************************************************************* + * END OF SIMPLIFIED API + ******************************************************************************/ + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +PNG_EXPORT(242, void, png_set_check_for_invalid_index, + (png_structrp png_ptr, int allowed)); +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +PNG_EXPORT(243, int, png_get_palette_max, (png_const_structp png_ptr, + png_const_infop info_ptr)); +# endif +#endif /* CHECK_FOR_INVALID_INDEX */ + +/******************************************************************************* + * IMPLEMENTATION OPTIONS + ******************************************************************************* + * + * Support for arbitrary implementation-specific optimizations. The API allows + * particular options to be turned on or off. 'Option' is the number of the + * option and 'onoff' is 0 (off) or non-0 (on). The value returned is given + * by the PNG_OPTION_ defines below. + * + * HARDWARE: normally hardware capabilites, such as the Intel SSE instructions, + * are detected at run time, however sometimes it may be impossible + * to do this in user mode, in which case it is necessary to discover + * the capabilities in an OS specific way. Such capabilities are + * listed here when libpng has support for them and must be turned + * ON by the application if present. + * + * SOFTWARE: sometimes software optimizations actually result in performance + * decrease on some architectures or systems, or with some sets of + * PNG images. 'Software' options allow such optimizations to be + * selected at run time. + */ +#ifdef PNG_SET_OPTION_SUPPORTED +#ifdef PNG_ARM_NEON_API_SUPPORTED +# define PNG_ARM_NEON 0 /* HARDWARE: ARM Neon SIMD instructions supported */ +#endif +#define PNG_OPTION_NEXT 2 /* Next option - numbers must be even */ + +/* Return values: NOTE: there are four values and 'off' is *not* zero */ +#define PNG_OPTION_UNSET 0 /* Unset - defaults to off */ +#define PNG_OPTION_INVALID 1 /* Option number out of range */ +#define PNG_OPTION_OFF 2 +#define PNG_OPTION_ON 3 + +PNG_EXPORT(244, int, png_set_option, (png_structrp png_ptr, int option, + int onoff)); +#endif + +/******************************************************************************* + * END OF HARDWARE OPTIONS + ******************************************************************************/ + +/* Maintainer: Put new public prototypes here ^, in libpng.3, and project + * defs, scripts/pnglibconf.h, and scripts/pnglibconf.h.prebuilt + */ + +/* The last ordinal number (this is the *last* one already used; the next + * one to use is one more than this.) Maintainer, remember to add an entry to + * scripts/symbols.def as well. + */ +#ifdef PNG_EXPORT_LAST_ORDINAL + PNG_EXPORT_LAST_ORDINAL(244); +#endif + +#endif /* PNG_VERSION_INFO_ONLY */ +/* Do not put anything past this line */ +#endif /* PNG_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngconf.h b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngconf.h new file mode 100644 index 0000000..b3ceeae --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngconf.h @@ -0,0 +1,616 @@ + +/* pngconf.h - machine configurable file for libpng + * + * libpng version 1.6.1 - March 28, 2013 + * + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + */ + +/* Any machine specific code is near the front of this file, so if you + * are configuring libpng for a machine, you may want to read the section + * starting here down to where it starts to typedef png_color, png_text, + * and png_info. + */ + +#ifndef PNGCONF_H +#define PNGCONF_H + +/* To do: Do all of this in scripts/pnglibconf.dfa */ +#ifdef PNG_SAFE_LIMITS_SUPPORTED +# ifdef PNG_USER_WIDTH_MAX +# undef PNG_USER_WIDTH_MAX +# define PNG_USER_WIDTH_MAX 1000000L +# endif +# ifdef PNG_USER_HEIGHT_MAX +# undef PNG_USER_HEIGHT_MAX +# define PNG_USER_HEIGHT_MAX 1000000L +# endif +# ifdef PNG_USER_CHUNK_MALLOC_MAX +# undef PNG_USER_CHUNK_MALLOC_MAX +# define PNG_USER_CHUNK_MALLOC_MAX 4000000L +# endif +# ifdef PNG_USER_CHUNK_CACHE_MAX +# undef PNG_USER_CHUNK_CACHE_MAX +# define PNG_USER_CHUNK_CACHE_MAX 128 +# endif +#endif + +#ifndef PNG_BUILDING_SYMBOL_TABLE /* else includes may cause problems */ + +/* From libpng 1.6.0 libpng requires an ANSI X3.159-1989 ("ISOC90") compliant C + * compiler for correct compilation. The following header files are required by + * the standard. If your compiler doesn't provide these header files, or they + * do not match the standard, you will need to provide/improve them. + */ +#include +#include + +/* Library header files. These header files are all defined by ISOC90; libpng + * expects conformant implementations, however, an ISOC90 conformant system need + * not provide these header files if the functionality cannot be implemented. + * In this case it will be necessary to disable the relevant parts of libpng in + * the build of pnglibconf.h. + * + * Prior to 1.6.0 string.h was included here; the API changes in 1.6.0 to not + * include this unnecessary header file. + */ + +#ifdef PNG_STDIO_SUPPORTED + /* Required for the definition of FILE: */ +# include +#endif + +#ifdef PNG_SETJMP_SUPPORTED + /* Required for the definition of jmp_buf and the declaration of longjmp: */ +# include +#endif + +#ifdef PNG_CONVERT_tIME_SUPPORTED + /* Required for struct tm: */ +# include +#endif + +#endif /* PNG_BUILDING_SYMBOL_TABLE */ + +/* Prior to 1.6.0 it was possible to turn off 'const' in declarations using + * PNG_NO_CONST; this is no longer supported except for data declarations which + * apparently still cause problems in 2011 on some compilers. + */ +#define PNG_CONST const /* backward compatibility only */ + +/* This controls optimization of the reading of 16 and 32 bit values + * from PNG files. It can be set on a per-app-file basis - it + * just changes whether a macro is used when the function is called. + * The library builder sets the default; if read functions are not + * built into the library the macro implementation is forced on. + */ +#ifndef PNG_READ_INT_FUNCTIONS_SUPPORTED +# define PNG_USE_READ_MACROS +#endif +#if !defined(PNG_NO_USE_READ_MACROS) && !defined(PNG_USE_READ_MACROS) +# if PNG_DEFAULT_READ_MACROS +# define PNG_USE_READ_MACROS +# endif +#endif + +/* COMPILER SPECIFIC OPTIONS. + * + * These options are provided so that a variety of difficult compilers + * can be used. Some are fixed at build time (e.g. PNG_API_RULE + * below) but still have compiler specific implementations, others + * may be changed on a per-file basis when compiling against libpng. + */ + +/* The PNGARG macro was used in versions of libpng prior to 1.6.0 to protect + * against legacy (pre ISOC90) compilers that did not understand function + * prototypes. It is not required for modern C compilers. + */ +#ifndef PNGARG +# define PNGARG(arglist) arglist +#endif + +/* Function calling conventions. + * ============================= + * Normally it is not necessary to specify to the compiler how to call + * a function - it just does it - however on x86 systems derived from + * Microsoft and Borland C compilers ('IBM PC', 'DOS', 'Windows' systems + * and some others) there are multiple ways to call a function and the + * default can be changed on the compiler command line. For this reason + * libpng specifies the calling convention of every exported function and + * every function called via a user supplied function pointer. This is + * done in this file by defining the following macros: + * + * PNGAPI Calling convention for exported functions. + * PNGCBAPI Calling convention for user provided (callback) functions. + * PNGCAPI Calling convention used by the ANSI-C library (required + * for longjmp callbacks and sometimes used internally to + * specify the calling convention for zlib). + * + * These macros should never be overridden. If it is necessary to + * change calling convention in a private build this can be done + * by setting PNG_API_RULE (which defaults to 0) to one of the values + * below to select the correct 'API' variants. + * + * PNG_API_RULE=0 Use PNGCAPI - the 'C' calling convention - throughout. + * This is correct in every known environment. + * PNG_API_RULE=1 Use the operating system convention for PNGAPI and + * the 'C' calling convention (from PNGCAPI) for + * callbacks (PNGCBAPI). This is no longer required + * in any known environment - if it has to be used + * please post an explanation of the problem to the + * libpng mailing list. + * + * These cases only differ if the operating system does not use the C + * calling convention, at present this just means the above cases + * (x86 DOS/Windows sytems) and, even then, this does not apply to + * Cygwin running on those systems. + * + * Note that the value must be defined in pnglibconf.h so that what + * the application uses to call the library matches the conventions + * set when building the library. + */ + +/* Symbol export + * ============= + * When building a shared library it is almost always necessary to tell + * the compiler which symbols to export. The png.h macro 'PNG_EXPORT' + * is used to mark the symbols. On some systems these symbols can be + * extracted at link time and need no special processing by the compiler, + * on other systems the symbols are flagged by the compiler and just + * the declaration requires a special tag applied (unfortunately) in a + * compiler dependent way. Some systems can do either. + * + * A small number of older systems also require a symbol from a DLL to + * be flagged to the program that calls it. This is a problem because + * we do not know in the header file included by application code that + * the symbol will come from a shared library, as opposed to a statically + * linked one. For this reason the application must tell us by setting + * the magic flag PNG_USE_DLL to turn on the special processing before + * it includes png.h. + * + * Four additional macros are used to make this happen: + * + * PNG_IMPEXP The magic (if any) to cause a symbol to be exported from + * the build or imported if PNG_USE_DLL is set - compiler + * and system specific. + * + * PNG_EXPORT_TYPE(type) A macro that pre or appends PNG_IMPEXP to + * 'type', compiler specific. + * + * PNG_DLL_EXPORT Set to the magic to use during a libpng build to + * make a symbol exported from the DLL. Not used in the + * public header files; see pngpriv.h for how it is used + * in the libpng build. + * + * PNG_DLL_IMPORT Set to the magic to force the libpng symbols to come + * from a DLL - used to define PNG_IMPEXP when + * PNG_USE_DLL is set. + */ + +/* System specific discovery. + * ========================== + * This code is used at build time to find PNG_IMPEXP, the API settings + * and PNG_EXPORT_TYPE(), it may also set a macro to indicate the DLL + * import processing is possible. On Windows systems it also sets + * compiler-specific macros to the values required to change the calling + * conventions of the various functions. + */ +#if defined(_Windows) || defined(_WINDOWS) || defined(WIN32) ||\ + defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) + /* Windows system (DOS doesn't support DLLs). Includes builds under Cygwin or + * MinGW on any architecture currently supported by Windows. Also includes + * Watcom builds but these need special treatment because they are not + * compatible with GCC or Visual C because of different calling conventions. + */ +# if PNG_API_RULE == 2 + /* If this line results in an error, either because __watcall is not + * understood or because of a redefine just below you cannot use *this* + * build of the library with the compiler you are using. *This* build was + * build using Watcom and applications must also be built using Watcom! + */ +# define PNGCAPI __watcall +# endif + +# if defined(__GNUC__) || (defined(_MSC_VER) && (_MSC_VER >= 800)) +# define PNGCAPI __cdecl +# if PNG_API_RULE == 1 + /* If this line results in an error __stdcall is not understood and + * PNG_API_RULE should not have been set to '1'. + */ +# define PNGAPI __stdcall +# endif +# else + /* An older compiler, or one not detected (erroneously) above, + * if necessary override on the command line to get the correct + * variants for the compiler. + */ +# ifndef PNGCAPI +# define PNGCAPI _cdecl +# endif +# if PNG_API_RULE == 1 && !defined(PNGAPI) +# define PNGAPI _stdcall +# endif +# endif /* compiler/api */ + /* NOTE: PNGCBAPI always defaults to PNGCAPI. */ + +# if defined(PNGAPI) && !defined(PNG_USER_PRIVATEBUILD) +# error "PNG_USER_PRIVATEBUILD must be defined if PNGAPI is changed" +# endif + +# if (defined(_MSC_VER) && _MSC_VER < 800) ||\ + (defined(__BORLANDC__) && __BORLANDC__ < 0x500) + /* older Borland and MSC + * compilers used '__export' and required this to be after + * the type. + */ +# ifndef PNG_EXPORT_TYPE +# define PNG_EXPORT_TYPE(type) type PNG_IMPEXP +# endif +# define PNG_DLL_EXPORT __export +# else /* newer compiler */ +# define PNG_DLL_EXPORT __declspec(dllexport) +# ifndef PNG_DLL_IMPORT +# define PNG_DLL_IMPORT __declspec(dllimport) +# endif +# endif /* compiler */ + +#else /* !Windows */ +# if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__) +# define PNGAPI _System +# else /* !Windows/x86 && !OS/2 */ + /* Use the defaults, or define PNG*API on the command line (but + * this will have to be done for every compile!) + */ +# endif /* other system, !OS/2 */ +#endif /* !Windows/x86 */ + +/* Now do all the defaulting . */ +#ifndef PNGCAPI +# define PNGCAPI +#endif +#ifndef PNGCBAPI +# define PNGCBAPI PNGCAPI +#endif +#ifndef PNGAPI +# define PNGAPI PNGCAPI +#endif + +/* PNG_IMPEXP may be set on the compilation system command line or (if not set) + * then in an internal header file when building the library, otherwise (when + * using the library) it is set here. + */ +#ifndef PNG_IMPEXP +# if defined(PNG_USE_DLL) && defined(PNG_DLL_IMPORT) + /* This forces use of a DLL, disallowing static linking */ +# define PNG_IMPEXP PNG_DLL_IMPORT +# endif + +# ifndef PNG_IMPEXP +# define PNG_IMPEXP +# endif +#endif + +/* In 1.5.2 the definition of PNG_FUNCTION has been changed to always treat + * 'attributes' as a storage class - the attributes go at the start of the + * function definition, and attributes are always appended regardless of the + * compiler. This considerably simplifies these macros but may cause problems + * if any compilers both need function attributes and fail to handle them as + * a storage class (this is unlikely.) + */ +#ifndef PNG_FUNCTION +# define PNG_FUNCTION(type, name, args, attributes) attributes type name args +#endif + +#ifndef PNG_EXPORT_TYPE +# define PNG_EXPORT_TYPE(type) PNG_IMPEXP type +#endif + + /* The ordinal value is only relevant when preprocessing png.h for symbol + * table entries, so we discard it here. See the .dfn files in the + * scripts directory. + */ +#ifndef PNG_EXPORTA + +# define PNG_EXPORTA(ordinal, type, name, args, attributes)\ + PNG_FUNCTION(PNG_EXPORT_TYPE(type),(PNGAPI name),PNGARG(args), \ + extern attributes) +#endif + +/* ANSI-C (C90) does not permit a macro to be invoked with an empty argument, + * so make something non-empty to satisfy the requirement: + */ +#define PNG_EMPTY /*empty list*/ + +#define PNG_EXPORT(ordinal, type, name, args)\ + PNG_EXPORTA(ordinal, type, name, args, PNG_EMPTY) + +/* Use PNG_REMOVED to comment out a removed interface. */ +#ifndef PNG_REMOVED +# define PNG_REMOVED(ordinal, type, name, args, attributes) +#endif + +#ifndef PNG_CALLBACK +# define PNG_CALLBACK(type, name, args) type (PNGCBAPI name) PNGARG(args) +#endif + +/* Support for compiler specific function attributes. These are used + * so that where compiler support is available incorrect use of API + * functions in png.h will generate compiler warnings. + * + * Added at libpng-1.2.41. + */ + +#ifndef PNG_NO_PEDANTIC_WARNINGS +# ifndef PNG_PEDANTIC_WARNINGS_SUPPORTED +# define PNG_PEDANTIC_WARNINGS_SUPPORTED +# endif +#endif + +#ifdef PNG_PEDANTIC_WARNINGS_SUPPORTED + /* Support for compiler specific function attributes. These are used + * so that where compiler support is available, incorrect use of API + * functions in png.h will generate compiler warnings. Added at libpng + * version 1.2.41. Disabling these removes the warnings but may also produce + * less efficient code. + */ +# if defined(__GNUC__) +# ifndef PNG_USE_RESULT +# define PNG_USE_RESULT __attribute__((__warn_unused_result__)) +# endif +# ifndef PNG_NORETURN +# define PNG_NORETURN __attribute__((__noreturn__)) +# endif +# if __GNUC__ >= 3 +# ifndef PNG_ALLOCATED +# define PNG_ALLOCATED __attribute__((__malloc__)) +# endif +# ifndef PNG_DEPRECATED +# define PNG_DEPRECATED __attribute__((__deprecated__)) +# endif +# ifndef PNG_PRIVATE +# if 0 /* Doesn't work so we use deprecated instead*/ +# define PNG_PRIVATE \ + __attribute__((warning("This function is not exported by libpng."))) +# else +# define PNG_PRIVATE \ + __attribute__((__deprecated__)) +# endif +# endif +# if ((__GNUC__ != 3) || !defined(__GNUC_MINOR__) || (__GNUC_MINOR__ >= 1)) +# ifndef PNG_RESTRICT +# define PNG_RESTRICT __restrict +# endif +# endif /* __GNUC__ == 3.0 */ +# endif /* __GNUC__ >= 3 */ + +# elif defined(_MSC_VER) && (_MSC_VER >= 1300) +# ifndef PNG_USE_RESULT +# define PNG_USE_RESULT /* not supported */ +# endif +# ifndef PNG_NORETURN +# define PNG_NORETURN __declspec(noreturn) +# endif +# ifndef PNG_ALLOCATED +# if (_MSC_VER >= 1400) +# define PNG_ALLOCATED __declspec(restrict) +# endif +# endif +# ifndef PNG_DEPRECATED +# define PNG_DEPRECATED __declspec(deprecated) +# endif +# ifndef PNG_PRIVATE +# define PNG_PRIVATE __declspec(deprecated) +# endif +# ifndef PNG_RESTRICT +# if (_MSC_VER >= 1400) +# define PNG_RESTRICT __restrict +# endif +# endif + +# elif defined(__WATCOMC__) +# ifndef PNG_RESTRICT +# define PNG_RESTRICT __restrict +# endif +# endif /* _MSC_VER */ +#endif /* PNG_PEDANTIC_WARNINGS */ + +#ifndef PNG_DEPRECATED +# define PNG_DEPRECATED /* Use of this function is deprecated */ +#endif +#ifndef PNG_USE_RESULT +# define PNG_USE_RESULT /* The result of this function must be checked */ +#endif +#ifndef PNG_NORETURN +# define PNG_NORETURN /* This function does not return */ +#endif +#ifndef PNG_ALLOCATED +# define PNG_ALLOCATED /* The result of the function is new memory */ +#endif +#ifndef PNG_PRIVATE +# define PNG_PRIVATE /* This is a private libpng function */ +#endif +#ifndef PNG_RESTRICT +# define PNG_RESTRICT /* The C99 "restrict" feature */ +#endif +#ifndef PNG_FP_EXPORT /* A floating point API. */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +# define PNG_FP_EXPORT(ordinal, type, name, args)\ + PNG_EXPORT(ordinal, type, name, args); +# else /* No floating point APIs */ +# define PNG_FP_EXPORT(ordinal, type, name, args) +# endif +#endif +#ifndef PNG_FIXED_EXPORT /* A fixed point API. */ +# ifdef PNG_FIXED_POINT_SUPPORTED +# define PNG_FIXED_EXPORT(ordinal, type, name, args)\ + PNG_EXPORT(ordinal, type, name, args); +# else /* No fixed point APIs */ +# define PNG_FIXED_EXPORT(ordinal, type, name, args) +# endif +#endif + +#ifndef PNG_BUILDING_SYMBOL_TABLE +/* Some typedefs to get us started. These should be safe on most of the common + * platforms. + * + * png_uint_32 and png_int_32 may, currently, be larger than required to hold a + * 32-bit value however this is not normally advisable. + * + * png_uint_16 and png_int_16 should always be two bytes in size - this is + * verified at library build time. + * + * png_byte must always be one byte in size. + * + * The checks below use constants from limits.h, as defined by the ISOC90 + * standard. + */ +#if CHAR_BIT == 8 && UCHAR_MAX == 255 + typedef unsigned char png_byte; +#else +# error "libpng requires 8 bit bytes" +#endif + +#if INT_MIN == -32768 && INT_MAX == 32767 + typedef int png_int_16; +#elif SHRT_MIN == -32768 && SHRT_MAX == 32767 + typedef short png_int_16; +#else +# error "libpng requires a signed 16 bit type" +#endif + +#if UINT_MAX == 65535 + typedef unsigned int png_uint_16; +#elif USHRT_MAX == 65535 + typedef unsigned short png_uint_16; +#else +# error "libpng requires an unsigned 16 bit type" +#endif + +#if INT_MIN < -2147483646 && INT_MAX > 2147483646 + typedef int png_int_32; +#elif LONG_MIN < -2147483646 && LONG_MAX > 2147483646 + typedef long int png_int_32; +#else +# error "libpng requires a signed 32 bit (or more) type" +#endif + +#if UINT_MAX > 4294967294 + typedef unsigned int png_uint_32; +#elif ULONG_MAX > 4294967294 + typedef unsigned long int png_uint_32; +#else +# error "libpng requires an unsigned 32 bit (or more) type" +#endif + +/* Prior to 1.6.0 it was possible to disable the use of size_t, 1.6.0, however, + * requires an ISOC90 compiler and relies on consistent behavior of sizeof. + */ +typedef size_t png_size_t; +typedef ptrdiff_t png_ptrdiff_t; + +/* libpng needs to know the maximum value of 'size_t' and this controls the + * definition of png_alloc_size_t, below. This maximum value of size_t limits + * but does not control the maximum allocations the library makes - there is + * direct application control of this through png_set_user_limits(). + */ +#ifndef PNG_SMALL_SIZE_T + /* Compiler specific tests for systems where size_t is known to be less than + * 32 bits (some of these systems may no longer work because of the lack of + * 'far' support; see above.) + */ +# if (defined(__TURBOC__) && !defined(__FLAT__)) ||\ + (defined(_MSC_VER) && defined(MAXSEG_64K)) +# define PNG_SMALL_SIZE_T +# endif +#endif + +/* png_alloc_size_t is guaranteed to be no smaller than png_size_t, and no + * smaller than png_uint_32. Casts from png_size_t or png_uint_32 to + * png_alloc_size_t are not necessary; in fact, it is recommended not to use + * them at all so that the compiler can complain when something turns out to be + * problematic. + * + * Casts in the other direction (from png_alloc_size_t to png_size_t or + * png_uint_32) should be explicitly applied; however, we do not expect to + * encounter practical situations that require such conversions. + * + * PNG_SMALL_SIZE_T must be defined if the maximum value of size_t is less than + * 4294967295 - i.e. less than the maximum value of png_uint_32. + */ +#ifdef PNG_SMALL_SIZE_T + typedef png_uint_32 png_alloc_size_t; +#else + typedef png_size_t png_alloc_size_t; +#endif + +/* Prior to 1.6.0 libpng offered limited support for Microsoft C compiler + * implementations of Intel CPU specific support of user-mode segmented address + * spaces, where 16-bit pointers address more than 65536 bytes of memory using + * separate 'segment' registers. The implementation requires two different + * types of pointer (only one of which includes the segment value.) + * + * If required this support is available in version 1.2 of libpng and may be + * available in versions through 1.5, although the correctness of the code has + * not been verified recently. + */ + +/* Typedef for floating-point numbers that are converted to fixed-point with a + * multiple of 100,000, e.g., gamma + */ +typedef png_int_32 png_fixed_point; + +/* Add typedefs for pointers */ +typedef void * png_voidp; +typedef const void * png_const_voidp; +typedef png_byte * png_bytep; +typedef const png_byte * png_const_bytep; +typedef png_uint_32 * png_uint_32p; +typedef const png_uint_32 * png_const_uint_32p; +typedef png_int_32 * png_int_32p; +typedef const png_int_32 * png_const_int_32p; +typedef png_uint_16 * png_uint_16p; +typedef const png_uint_16 * png_const_uint_16p; +typedef png_int_16 * png_int_16p; +typedef const png_int_16 * png_const_int_16p; +typedef char * png_charp; +typedef const char * png_const_charp; +typedef png_fixed_point * png_fixed_point_p; +typedef const png_fixed_point * png_const_fixed_point_p; +typedef png_size_t * png_size_tp; +typedef const png_size_t * png_const_size_tp; + +#ifdef PNG_STDIO_SUPPORTED +typedef FILE * png_FILE_p; +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +typedef double * png_doublep; +typedef const double * png_const_doublep; +#endif + +/* Pointers to pointers; i.e. arrays */ +typedef png_byte * * png_bytepp; +typedef png_uint_32 * * png_uint_32pp; +typedef png_int_32 * * png_int_32pp; +typedef png_uint_16 * * png_uint_16pp; +typedef png_int_16 * * png_int_16pp; +typedef const char * * png_const_charpp; +typedef char * * png_charpp; +typedef png_fixed_point * * png_fixed_point_pp; +#ifdef PNG_FLOATING_POINT_SUPPORTED +typedef double * * png_doublepp; +#endif + +/* Pointers to pointers to pointers; i.e., pointer to array */ +typedef char * * * png_charppp; + +#endif /* PNG_BUILDING_SYMBOL_TABLE */ + +#endif /* PNGCONF_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngerror.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngerror.c new file mode 100644 index 0000000..f40cc83 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngerror.c @@ -0,0 +1,932 @@ + +/* pngerror.c - stub functions for i/o and memory allocation + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all error handling. Users who + * need special error handling are expected to write replacement functions + * and use png_set_error_fn() to use those functions. See the instructions + * at each function. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +static PNG_FUNCTION(void, png_default_error,PNGARG((png_const_structrp png_ptr, + png_const_charp error_message)),PNG_NORETURN); + +#ifdef PNG_WARNINGS_SUPPORTED +static void /* PRIVATE */ +png_default_warning PNGARG((png_const_structrp png_ptr, + png_const_charp warning_message)); +#endif /* PNG_WARNINGS_SUPPORTED */ + +/* This function is called whenever there is a fatal error. This function + * should not be changed. If there is a need to handle errors differently, + * you should supply a replacement error function and use png_set_error_fn() + * to replace the error function at run-time. + */ +#ifdef PNG_ERROR_TEXT_SUPPORTED +PNG_FUNCTION(void,PNGAPI +png_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + char msg[16]; + if (png_ptr != NULL) + { + if (png_ptr->flags& + (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) + { + if (*error_message == PNG_LITERAL_SHARP) + { + /* Strip "#nnnn " from beginning of error message. */ + int offset; + for (offset = 1; offset<15; offset++) + if (error_message[offset] == ' ') + break; + + if (png_ptr->flags&PNG_FLAG_STRIP_ERROR_TEXT) + { + int i; + for (i = 0; i < offset - 1; i++) + msg[i] = error_message[i + 1]; + msg[i - 1] = '\0'; + error_message = msg; + } + + else + error_message += offset; + } + + else + { + if (png_ptr->flags&PNG_FLAG_STRIP_ERROR_TEXT) + { + msg[0] = '0'; + msg[1] = '\0'; + error_message = msg; + } + } + } + } +#endif + if (png_ptr != NULL && png_ptr->error_fn != NULL) + (*(png_ptr->error_fn))(png_constcast(png_structrp,png_ptr), + error_message); + + /* If the custom handler doesn't exist, or if it returns, + use the default handler, which will not return. */ + png_default_error(png_ptr, error_message); +} +#else +PNG_FUNCTION(void,PNGAPI +png_err,(png_const_structrp png_ptr),PNG_NORETURN) +{ + /* Prior to 1.5.2 the error_fn received a NULL pointer, expressed + * erroneously as '\0', instead of the empty string "". This was + * apparently an error, introduced in libpng-1.2.20, and png_default_error + * will crash in this case. + */ + if (png_ptr != NULL && png_ptr->error_fn != NULL) + (*(png_ptr->error_fn))(png_constcast(png_structrp,png_ptr), ""); + + /* If the custom handler doesn't exist, or if it returns, + use the default handler, which will not return. */ + png_default_error(png_ptr, ""); +} +#endif /* PNG_ERROR_TEXT_SUPPORTED */ + +/* Utility to safely appends strings to a buffer. This never errors out so + * error checking is not required in the caller. + */ +size_t +png_safecat(png_charp buffer, size_t bufsize, size_t pos, + png_const_charp string) +{ + if (buffer != NULL && pos < bufsize) + { + if (string != NULL) + while (*string != '\0' && pos < bufsize-1) + buffer[pos++] = *string++; + + buffer[pos] = '\0'; + } + + return pos; +} + +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED) +/* Utility to dump an unsigned value into a buffer, given a start pointer and + * and end pointer (which should point just *beyond* the end of the buffer!) + * Returns the pointer to the start of the formatted string. + */ +png_charp +png_format_number(png_const_charp start, png_charp end, int format, + png_alloc_size_t number) +{ + int count = 0; /* number of digits output */ + int mincount = 1; /* minimum number required */ + int output = 0; /* digit output (for the fixed point format) */ + + *--end = '\0'; + + /* This is written so that the loop always runs at least once, even with + * number zero. + */ + while (end > start && (number != 0 || count < mincount)) + { + + static const char digits[] = "0123456789ABCDEF"; + + switch (format) + { + case PNG_NUMBER_FORMAT_fixed: + /* Needs five digits (the fraction) */ + mincount = 5; + if (output || number % 10 != 0) + { + *--end = digits[number % 10]; + output = 1; + } + number /= 10; + break; + + case PNG_NUMBER_FORMAT_02u: + /* Expects at least 2 digits. */ + mincount = 2; + /* FALL THROUGH */ + + case PNG_NUMBER_FORMAT_u: + *--end = digits[number % 10]; + number /= 10; + break; + + case PNG_NUMBER_FORMAT_02x: + /* This format expects at least two digits */ + mincount = 2; + /* FALL THROUGH */ + + case PNG_NUMBER_FORMAT_x: + *--end = digits[number & 0xf]; + number >>= 4; + break; + + default: /* an error */ + number = 0; + break; + } + + /* Keep track of the number of digits added */ + ++count; + + /* Float a fixed number here: */ + if (format == PNG_NUMBER_FORMAT_fixed) if (count == 5) if (end > start) + { + /* End of the fraction, but maybe nothing was output? In that case + * drop the decimal point. If the number is a true zero handle that + * here. + */ + if (output) + *--end = '.'; + else if (number == 0) /* and !output */ + *--end = '0'; + } + } + + return end; +} +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* This function is called whenever there is a non-fatal error. This function + * should not be changed. If there is a need to handle warnings differently, + * you should supply a replacement warning function and use + * png_set_error_fn() to replace the warning function at run-time. + */ +void PNGAPI +png_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ + int offset = 0; + if (png_ptr != NULL) + { +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + if (png_ptr->flags& + (PNG_FLAG_STRIP_ERROR_NUMBERS|PNG_FLAG_STRIP_ERROR_TEXT)) +#endif + { + if (*warning_message == PNG_LITERAL_SHARP) + { + for (offset = 1; offset < 15; offset++) + if (warning_message[offset] == ' ') + break; + } + } + } + if (png_ptr != NULL && png_ptr->warning_fn != NULL) + (*(png_ptr->warning_fn))(png_constcast(png_structrp,png_ptr), + warning_message + offset); + else + png_default_warning(png_ptr, warning_message + offset); +} + +/* These functions support 'formatted' warning messages with up to + * PNG_WARNING_PARAMETER_COUNT parameters. In the format string the parameter + * is introduced by @, where 'number' starts at 1. This follows the + * standard established by X/Open for internationalizable error messages. + */ +void +png_warning_parameter(png_warning_parameters p, int number, + png_const_charp string) +{ + if (number > 0 && number <= PNG_WARNING_PARAMETER_COUNT) + (void)png_safecat(p[number-1], (sizeof p[number-1]), 0, string); +} + +void +png_warning_parameter_unsigned(png_warning_parameters p, int number, int format, + png_alloc_size_t value) +{ + char buffer[PNG_NUMBER_BUFFER_SIZE]; + png_warning_parameter(p, number, PNG_FORMAT_NUMBER(buffer, format, value)); +} + +void +png_warning_parameter_signed(png_warning_parameters p, int number, int format, + png_int_32 value) +{ + png_alloc_size_t u; + png_charp str; + char buffer[PNG_NUMBER_BUFFER_SIZE]; + + /* Avoid overflow by doing the negate in a png_alloc_size_t: */ + u = (png_alloc_size_t)value; + if (value < 0) + u = ~u + 1; + + str = PNG_FORMAT_NUMBER(buffer, format, u); + + if (value < 0 && str > buffer) + *--str = '-'; + + png_warning_parameter(p, number, str); +} + +void +png_formatted_warning(png_const_structrp png_ptr, png_warning_parameters p, + png_const_charp message) +{ + /* The internal buffer is just 192 bytes - enough for all our messages, + * overflow doesn't happen because this code checks! If someone figures + * out how to send us a message longer than 192 bytes, all that will + * happen is that the message will be truncated appropriately. + */ + size_t i = 0; /* Index in the msg[] buffer: */ + char msg[192]; + + /* Each iteration through the following loop writes at most one character + * to msg[i++] then returns here to validate that there is still space for + * the trailing '\0'. It may (in the case of a parameter) read more than + * one character from message[]; it must check for '\0' and continue to the + * test if it finds the end of string. + */ + while (i<(sizeof msg)-1 && *message != '\0') + { + /* '@' at end of string is now just printed (previously it was skipped); + * it is an error in the calling code to terminate the string with @. + */ + if (p != NULL && *message == '@' && message[1] != '\0') + { + int parameter_char = *++message; /* Consume the '@' */ + static const char valid_parameters[] = "123456789"; + int parameter = 0; + + /* Search for the parameter digit, the index in the string is the + * parameter to use. + */ + while (valid_parameters[parameter] != parameter_char && + valid_parameters[parameter] != '\0') + ++parameter; + + /* If the parameter digit is out of range it will just get printed. */ + if (parameter < PNG_WARNING_PARAMETER_COUNT) + { + /* Append this parameter */ + png_const_charp parm = p[parameter]; + png_const_charp pend = p[parameter] + (sizeof p[parameter]); + + /* No need to copy the trailing '\0' here, but there is no guarantee + * that parm[] has been initialized, so there is no guarantee of a + * trailing '\0': + */ + while (i<(sizeof msg)-1 && *parm != '\0' && parm < pend) + msg[i++] = *parm++; + + /* Consume the parameter digit too: */ + ++message; + continue; + } + + /* else not a parameter and there is a character after the @ sign; just + * copy that. This is known not to be '\0' because of the test above. + */ + } + + /* At this point *message can't be '\0', even in the bad parameter case + * above where there is a lone '@' at the end of the message string. + */ + msg[i++] = *message++; + } + + /* i is always less than (sizeof msg), so: */ + msg[i] = '\0'; + + /* And this is the formatted message. It may be larger than + * PNG_MAX_ERROR_TEXT, but that is only used for 'chunk' errors and these + * are not (currently) formatted. + */ + png_warning(png_ptr, msg); +} +#endif /* PNG_WARNINGS_SUPPORTED */ + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_benign_error(png_const_structrp png_ptr, png_const_charp error_message) +{ + if (png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN) + { +# ifdef PNG_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + png_ptr->chunk_name != 0) + png_chunk_warning(png_ptr, error_message); + else +# endif + png_warning(png_ptr, error_message); + } + + else + { +# ifdef PNG_READ_SUPPORTED + if ((png_ptr->mode & PNG_IS_READ_STRUCT) != 0 && + png_ptr->chunk_name != 0) + png_chunk_error(png_ptr, error_message); + else +# endif + png_error(png_ptr, error_message); + } +} + +void /* PRIVATE */ +png_app_warning(png_const_structrp png_ptr, png_const_charp error_message) +{ + if (png_ptr->flags & PNG_FLAG_APP_WARNINGS_WARN) + png_warning(png_ptr, error_message); + else + png_error(png_ptr, error_message); +} + +void /* PRIVATE */ +png_app_error(png_const_structrp png_ptr, png_const_charp error_message) +{ + if (png_ptr->flags & PNG_FLAG_APP_ERRORS_WARN) + png_warning(png_ptr, error_message); + else + png_error(png_ptr, error_message); +} +#endif /* BENIGN_ERRORS */ + +/* These utilities are used internally to build an error message that relates + * to the current chunk. The chunk name comes from png_ptr->chunk_name, + * this is used to prefix the message. The message is limited in length + * to 63 bytes, the name characters are output as hex digits wrapped in [] + * if the character is invalid. + */ +#define isnonalpha(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) +static PNG_CONST char png_digit[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' +}; + +#define PNG_MAX_ERROR_TEXT 196 /* Currently limited be profile_error in png.c */ +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_ERROR_TEXT_SUPPORTED) +static void /* PRIVATE */ +png_format_buffer(png_const_structrp png_ptr, png_charp buffer, png_const_charp + error_message) +{ + png_uint_32 chunk_name = png_ptr->chunk_name; + int iout = 0, ishift = 24; + + while (ishift >= 0) + { + int c = (int)(chunk_name >> ishift) & 0xff; + + ishift -= 8; + if (isnonalpha(c)) + { + buffer[iout++] = PNG_LITERAL_LEFT_SQUARE_BRACKET; + buffer[iout++] = png_digit[(c & 0xf0) >> 4]; + buffer[iout++] = png_digit[c & 0x0f]; + buffer[iout++] = PNG_LITERAL_RIGHT_SQUARE_BRACKET; + } + + else + { + buffer[iout++] = (char)c; + } + } + + if (error_message == NULL) + buffer[iout] = '\0'; + + else + { + int iin = 0; + + buffer[iout++] = ':'; + buffer[iout++] = ' '; + + while (iin < PNG_MAX_ERROR_TEXT-1 && error_message[iin] != '\0') + buffer[iout++] = error_message[iin++]; + + /* iin < PNG_MAX_ERROR_TEXT, so the following is safe: */ + buffer[iout] = '\0'; + } +} +#endif /* PNG_WARNINGS_SUPPORTED || PNG_ERROR_TEXT_SUPPORTED */ + +#if defined(PNG_READ_SUPPORTED) && defined(PNG_ERROR_TEXT_SUPPORTED) +PNG_FUNCTION(void,PNGAPI +png_chunk_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ + char msg[18+PNG_MAX_ERROR_TEXT]; + if (png_ptr == NULL) + png_error(png_ptr, error_message); + + else + { + png_format_buffer(png_ptr, msg, error_message); + png_error(png_ptr, msg); + } +} +#endif /* PNG_READ_SUPPORTED && PNG_ERROR_TEXT_SUPPORTED */ + +#ifdef PNG_WARNINGS_SUPPORTED +void PNGAPI +png_chunk_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ + char msg[18+PNG_MAX_ERROR_TEXT]; + if (png_ptr == NULL) + png_warning(png_ptr, warning_message); + + else + { + png_format_buffer(png_ptr, msg, warning_message); + png_warning(png_ptr, msg); + } +} +#endif /* PNG_WARNINGS_SUPPORTED */ + +#ifdef PNG_READ_SUPPORTED +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_chunk_benign_error(png_const_structrp png_ptr, png_const_charp + error_message) +{ + if (png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN) + png_chunk_warning(png_ptr, error_message); + + else + png_chunk_error(png_ptr, error_message); +} +#endif +#endif /* PNG_READ_SUPPORTED */ + +void /* PRIVATE */ +png_chunk_report(png_const_structrp png_ptr, png_const_charp message, int error) +{ + /* This is always supported, but for just read or just write it + * unconditionally does the right thing. + */ +# if defined(PNG_READ_SUPPORTED) && defined(PNG_WRITE_SUPPORTED) + if (png_ptr->mode & PNG_IS_READ_STRUCT) +# endif + +# ifdef PNG_READ_SUPPORTED + { + if (error < PNG_CHUNK_ERROR) + png_chunk_warning(png_ptr, message); + + else + png_chunk_benign_error(png_ptr, message); + } +# endif + +# if defined(PNG_READ_SUPPORTED) && defined(PNG_WRITE_SUPPORTED) + else if (!(png_ptr->mode & PNG_IS_READ_STRUCT)) +# endif + +# ifdef PNG_WRITE_SUPPORTED + { + if (error < PNG_CHUNK_WRITE_ERROR) + png_app_warning(png_ptr, message); + + else + png_app_error(png_ptr, message); + } +# endif +} + +#ifdef PNG_ERROR_TEXT_SUPPORTED +#ifdef PNG_FLOATING_POINT_SUPPORTED +PNG_FUNCTION(void, +png_fixed_error,(png_const_structrp png_ptr, png_const_charp name),PNG_NORETURN) +{ +# define fixed_message "fixed point overflow in " +# define fixed_message_ln ((sizeof fixed_message)-1) + int iin; + char msg[fixed_message_ln+PNG_MAX_ERROR_TEXT]; + memcpy(msg, fixed_message, fixed_message_ln); + iin = 0; + if (name != NULL) while (iin < (PNG_MAX_ERROR_TEXT-1) && name[iin] != 0) + { + msg[fixed_message_ln + iin] = name[iin]; + ++iin; + } + msg[fixed_message_ln + iin] = 0; + png_error(png_ptr, msg); +} +#endif +#endif + +#ifdef PNG_SETJMP_SUPPORTED +/* This API only exists if ANSI-C style error handling is used, + * otherwise it is necessary for png_default_error to be overridden. + */ +jmp_buf* PNGAPI +png_set_longjmp_fn(png_structrp png_ptr, png_longjmp_ptr longjmp_fn, + size_t jmp_buf_size) +{ + /* From libpng 1.6.0 the app gets one chance to set a 'jmpbuf_size' value + * and it must not change after that. Libpng doesn't care how big the + * buffer is, just that it doesn't change. + * + * If the buffer size is no *larger* than the size of jmp_buf when libpng is + * compiled a built in jmp_buf is returned; this preserves the pre-1.6.0 + * semantics that this call will not fail. If the size is larger, however, + * the buffer is allocated and this may fail, causing the function to return + * NULL. + */ + if (png_ptr == NULL) + return NULL; + + if (png_ptr->jmp_buf_ptr == NULL) + { + png_ptr->jmp_buf_size = 0; /* not allocated */ + + if (jmp_buf_size <= (sizeof png_ptr->jmp_buf_local)) + png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; + + else + { + png_ptr->jmp_buf_ptr = png_voidcast(jmp_buf *, + png_malloc_warn(png_ptr, jmp_buf_size)); + + if (png_ptr->jmp_buf_ptr == NULL) + return NULL; /* new NULL return on OOM */ + + png_ptr->jmp_buf_size = jmp_buf_size; + } + } + + else /* Already allocated: check the size */ + { + size_t size = png_ptr->jmp_buf_size; + + if (size == 0) + { + size = (sizeof png_ptr->jmp_buf_local); + if (png_ptr->jmp_buf_ptr != &png_ptr->jmp_buf_local) + { + /* This is an internal error in libpng: somehow we have been left + * with a stack allocated jmp_buf when the application regained + * control. It's always possible to fix this up, but for the moment + * this is a png_error because that makes it easy to detect. + */ + png_error(png_ptr, "Libpng jmp_buf still allocated"); + /* png_ptr->jmp_buf_ptr = &png_ptr->jmp_buf_local; */ + } + } + + if (size != jmp_buf_size) + { + png_warning(png_ptr, "Application jmp_buf size changed"); + return NULL; /* caller will probably crash: no choice here */ + } + } + + /* Finally fill in the function, now we have a satisfactory buffer. It is + * valid to change the function on every call. + */ + png_ptr->longjmp_fn = longjmp_fn; + return png_ptr->jmp_buf_ptr; +} + +void /* PRIVATE */ +png_free_jmpbuf(png_structrp png_ptr) +{ + if (png_ptr != NULL) + { + jmp_buf *jb = png_ptr->jmp_buf_ptr; + + /* A size of 0 is used to indicate a local, stack, allocation of the + * pointer; used here and in png.c + */ + if (jb != NULL && png_ptr->jmp_buf_size > 0) + { + + /* This stuff is so that a failure to free the error control structure + * does not leave libpng in a state with no valid error handling: the + * free always succeeds, if there is an error it gets ignored. + */ + if (jb != &png_ptr->jmp_buf_local) + { + /* Make an internal, libpng, jmp_buf to return here */ + jmp_buf free_jmp_buf; + + if (!setjmp(free_jmp_buf)) + { + png_ptr->jmp_buf_ptr = &free_jmp_buf; /* come back here */ + png_ptr->jmp_buf_size = 0; /* stack allocation */ + png_ptr->longjmp_fn = longjmp; + png_free(png_ptr, jb); /* Return to setjmp on error */ + } + } + } + + /* *Always* cancel everything out: */ + png_ptr->jmp_buf_size = 0; + png_ptr->jmp_buf_ptr = NULL; + png_ptr->longjmp_fn = 0; + } +} +#endif + +/* This is the default error handling function. Note that replacements for + * this function MUST NOT RETURN, or the program will likely crash. This + * function is used by default, or if the program supplies NULL for the + * error function pointer in png_set_error_fn(). + */ +static PNG_FUNCTION(void /* PRIVATE */, +png_default_error,(png_const_structrp png_ptr, png_const_charp error_message), + PNG_NORETURN) +{ +#ifdef PNG_CONSOLE_IO_SUPPORTED +#ifdef PNG_ERROR_NUMBERS_SUPPORTED + /* Check on NULL only added in 1.5.4 */ + if (error_message != NULL && *error_message == PNG_LITERAL_SHARP) + { + /* Strip "#nnnn " from beginning of error message. */ + int offset; + char error_number[16]; + for (offset = 0; offset<15; offset++) + { + error_number[offset] = error_message[offset + 1]; + if (error_message[offset] == ' ') + break; + } + + if ((offset > 1) && (offset < 15)) + { + error_number[offset - 1] = '\0'; + fprintf(stderr, "libpng error no. %s: %s", + error_number, error_message + offset + 1); + fprintf(stderr, PNG_STRING_NEWLINE); + } + + else + { + fprintf(stderr, "libpng error: %s, offset=%d", + error_message, offset); + fprintf(stderr, PNG_STRING_NEWLINE); + } + } + else +#endif + { + fprintf(stderr, "libpng error: %s", error_message ? error_message : + "undefined"); + fprintf(stderr, PNG_STRING_NEWLINE); + } +#else + PNG_UNUSED(error_message) /* Make compiler happy */ +#endif + png_longjmp(png_ptr, 1); +} + +PNG_FUNCTION(void,PNGAPI +png_longjmp,(png_const_structrp, int),PNG_NORETURN) +{ +#ifdef PNG_SETJMP_SUPPORTED + if (png_ptr && png_ptr->longjmp_fn && png_ptr->jmp_buf_ptr) + png_ptr->longjmp_fn(*png_ptr->jmp_buf_ptr, val); +#endif + + /* Here if not setjmp support or if png_ptr is null. */ + PNG_ABORT(); +} + +#ifdef PNG_WARNINGS_SUPPORTED +/* This function is called when there is a warning, but the library thinks + * it can continue anyway. Replacement functions don't have to do anything + * here if you don't want them to. In the default configuration, png_ptr is + * not used, but it is passed in case it may be useful. + */ +static void /* PRIVATE */ +png_default_warning(png_const_structrp png_ptr, png_const_charp warning_message) +{ +#ifdef PNG_CONSOLE_IO_SUPPORTED +# ifdef PNG_ERROR_NUMBERS_SUPPORTED + if (*warning_message == PNG_LITERAL_SHARP) + { + int offset; + char warning_number[16]; + for (offset = 0; offset < 15; offset++) + { + warning_number[offset] = warning_message[offset + 1]; + if (warning_message[offset] == ' ') + break; + } + + if ((offset > 1) && (offset < 15)) + { + warning_number[offset + 1] = '\0'; + fprintf(stderr, "libpng warning no. %s: %s", + warning_number, warning_message + offset); + fprintf(stderr, PNG_STRING_NEWLINE); + } + + else + { + fprintf(stderr, "libpng warning: %s", + warning_message); + fprintf(stderr, PNG_STRING_NEWLINE); + } + } + else +# endif + + { + fprintf(stderr, "libpng warning: %s", warning_message); + fprintf(stderr, PNG_STRING_NEWLINE); + } +#else + PNG_UNUSED(warning_message) /* Make compiler happy */ +#endif + PNG_UNUSED(png_ptr) /* Make compiler happy */ +} +#endif /* PNG_WARNINGS_SUPPORTED */ + +/* This function is called when the application wants to use another method + * of handling errors and warnings. Note that the error function MUST NOT + * return to the calling routine or serious problems will occur. The return + * method used in the default routine calls longjmp(png_ptr->jmp_buf_ptr, 1) + */ +void PNGAPI +png_set_error_fn(png_structrp png_ptr, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warning_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->error_ptr = error_ptr; + png_ptr->error_fn = error_fn; +#ifdef PNG_WARNINGS_SUPPORTED + png_ptr->warning_fn = warning_fn; +#else + PNG_UNUSED(warning_fn) +#endif +} + + +/* This function returns a pointer to the error_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy and png_read_destroy are called. + */ +png_voidp PNGAPI +png_get_error_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return NULL; + + return ((png_voidp)png_ptr->error_ptr); +} + + +#ifdef PNG_ERROR_NUMBERS_SUPPORTED +void PNGAPI +png_set_strip_error_numbers(png_structrp png_ptr, png_uint_32 strip_mode) +{ + if (png_ptr != NULL) + { + png_ptr->flags &= + ((~(PNG_FLAG_STRIP_ERROR_NUMBERS | + PNG_FLAG_STRIP_ERROR_TEXT))&strip_mode); + } +} +#endif + +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) + /* Currently the above both depend on SETJMP_SUPPORTED, however it would be + * possible to implement without setjmp support just so long as there is some + * way to handle the error return here: + */ +PNG_FUNCTION(void /* PRIVATE */, +png_safe_error,(png_structp png_nonconst_ptr, png_const_charp error_message), + PNG_NORETURN) +{ + const png_const_structrp png_ptr = png_nonconst_ptr; + png_imagep image = png_voidcast(png_imagep, png_ptr->error_ptr); + + /* An error is always logged here, overwriting anything (typically a warning) + * that is already there: + */ + if (image != NULL) + { + png_safecat(image->message, (sizeof image->message), 0, error_message); + image->warning_or_error |= PNG_IMAGE_ERROR; + + /* Retrieve the jmp_buf from within the png_control, making this work for + * C++ compilation too is pretty tricky: C++ wants a pointer to the first + * element of a jmp_buf, but C doesn't tell us the type of that. + */ + if (image->opaque != NULL && image->opaque->error_buf != NULL) + longjmp(png_control_jmp_buf(image->opaque), 1); + + /* Missing longjmp buffer, the following is to help debugging: */ + { + size_t pos = png_safecat(image->message, (sizeof image->message), 0, + "bad longjmp: "); + png_safecat(image->message, (sizeof image->message), pos, + error_message); + } + } + + /* Here on an internal programming error. */ + abort(); +} + +#ifdef PNG_WARNINGS_SUPPORTED +void /* PRIVATE */ +png_safe_warning(png_structp png_nonconst_ptr, png_const_charp warning_message) +{ + const png_const_structrp png_ptr = png_nonconst_ptr; + png_imagep image = png_voidcast(png_imagep, png_ptr->error_ptr); + + /* A warning is only logged if there is no prior warning or error. */ + if (image->warning_or_error == 0) + { + png_safecat(image->message, (sizeof image->message), 0, warning_message); + image->warning_or_error |= PNG_IMAGE_WARNING; + } +} +#endif + +int /* PRIVATE */ +png_safe_execute(png_imagep image_in, int (*function)(png_voidp), png_voidp arg) +{ + volatile png_imagep image = image_in; + volatile int result; + volatile png_voidp saved_error_buf; + jmp_buf safe_jmpbuf; + + /* Safely execute function(arg) with png_error returning to this function. */ + saved_error_buf = image->opaque->error_buf; + result = setjmp(safe_jmpbuf) == 0; + + if (result) + { + + image->opaque->error_buf = safe_jmpbuf; + result = function(arg); + } + + image->opaque->error_buf = saved_error_buf; + + /* And do the cleanup prior to any failure return. */ + if (!result) + png_image_free(image); + + return result; +} +#endif /* SIMPLIFIED READ/WRITE */ +#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngget.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngget.c new file mode 100644 index 0000000..80ab055 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngget.c @@ -0,0 +1,1177 @@ + +/* pngget.c - retrieval of values from info struct + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +png_uint_32 PNGAPI +png_get_valid(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 flag) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->valid & flag); + + return(0); +} + +png_size_t PNGAPI +png_get_rowbytes(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->rowbytes); + + return(0); +} + +#ifdef PNG_INFO_IMAGE_SUPPORTED +png_bytepp PNGAPI +png_get_rows(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->row_pointers); + + return(0); +} +#endif + +#ifdef PNG_EASY_ACCESS_SUPPORTED +/* Easy access to info, added in libpng-0.99 */ +png_uint_32 PNGAPI +png_get_image_width(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->width; + + return (0); +} + +png_uint_32 PNGAPI +png_get_image_height(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->height; + + return (0); +} + +png_byte PNGAPI +png_get_bit_depth(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->bit_depth; + + return (0); +} + +png_byte PNGAPI +png_get_color_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->color_type; + + return (0); +} + +png_byte PNGAPI +png_get_filter_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->filter_type; + + return (0); +} + +png_byte PNGAPI +png_get_interlace_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->interlace_type; + + return (0); +} + +png_byte PNGAPI +png_get_compression_type(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return info_ptr->compression_type; + + return (0); +} + +png_uint_32 PNGAPI +png_get_x_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_debug1(1, "in %s retrieval function", + "png_get_x_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) + return (info_ptr->x_pixels_per_unit); + } +#endif + + return (0); +} + +png_uint_32 PNGAPI +png_get_y_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_debug1(1, "in %s retrieval function", + "png_get_y_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER) + return (info_ptr->y_pixels_per_unit); + } +#endif + + return (0); +} + +png_uint_32 PNGAPI +png_get_pixels_per_meter(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_debug1(1, "in %s retrieval function", "png_get_pixels_per_meter"); + + if (info_ptr->phys_unit_type == PNG_RESOLUTION_METER && + info_ptr->x_pixels_per_unit == info_ptr->y_pixels_per_unit) + return (info_ptr->x_pixels_per_unit); + } +#endif + + return (0); +} + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_pixel_aspect_ratio(png_const_structrp png_ptr, png_const_inforp + info_ptr) +{ +#ifdef PNG_READ_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio"); + + if (info_ptr->x_pixels_per_unit != 0) + return ((float)((float)info_ptr->y_pixels_per_unit + /(float)info_ptr->x_pixels_per_unit)); + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return ((float)0.0); +} +#endif + +#ifdef PNG_FIXED_POINT_SUPPORTED +png_fixed_point PNGAPI +png_get_pixel_aspect_ratio_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ +#ifdef PNG_READ_pHYs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs) + && info_ptr->x_pixels_per_unit > 0 && info_ptr->y_pixels_per_unit > 0 + && info_ptr->x_pixels_per_unit <= PNG_UINT_31_MAX + && info_ptr->y_pixels_per_unit <= PNG_UINT_31_MAX) + { + png_fixed_point res; + + png_debug1(1, "in %s retrieval function", "png_get_aspect_ratio_fixed"); + + /* The following casts work because a PNG 4 byte integer only has a valid + * range of 0..2^31-1; otherwise the cast might overflow. + */ + if (png_muldiv(&res, (png_int_32)info_ptr->y_pixels_per_unit, PNG_FP_1, + (png_int_32)info_ptr->x_pixels_per_unit)) + return res; + } +#else + PNG_UNUSED(png_ptr) + PNG_UNUSED(info_ptr) +#endif + + return 0; +} +#endif + +png_int_32 PNGAPI +png_get_x_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)) + { + png_debug1(1, "in %s retrieval function", "png_get_x_offset_microns"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) + return (info_ptr->x_offset); + } +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_y_offset_microns(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)) + { + png_debug1(1, "in %s retrieval function", "png_get_y_offset_microns"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_MICROMETER) + return (info_ptr->y_offset); + } +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_x_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)) + { + png_debug1(1, "in %s retrieval function", "png_get_x_offset_pixels"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) + return (info_ptr->x_offset); + } +#endif + + return (0); +} + +png_int_32 PNGAPI +png_get_y_offset_pixels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ +#ifdef PNG_oFFs_SUPPORTED + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)) + { + png_debug1(1, "in %s retrieval function", "png_get_y_offset_pixels"); + + if (info_ptr->offset_unit_type == PNG_OFFSET_PIXEL) + return (info_ptr->y_offset); + } +#endif + + return (0); +} + +#ifdef PNG_INCH_CONVERSIONS_SUPPORTED +static png_uint_32 +ppi_from_ppm(png_uint_32 ppm) +{ +#if 0 + /* The conversion is *(2.54/100), in binary (32 digits): + * .00000110100000001001110101001001 + */ + png_uint_32 t1001, t1101; + ppm >>= 1; /* .1 */ + t1001 = ppm + (ppm >> 3); /* .1001 */ + t1101 = t1001 + (ppm >> 1); /* .1101 */ + ppm >>= 20; /* .000000000000000000001 */ + t1101 += t1101 >> 15; /* .1101000000000001101 */ + t1001 >>= 11; /* .000000000001001 */ + t1001 += t1001 >> 12; /* .000000000001001000000001001 */ + ppm += t1001; /* .000000000001001000001001001 */ + ppm += t1101; /* .110100000001001110101001001 */ + return (ppm + 16) >> 5;/* .00000110100000001001110101001001 */ +#else + /* The argument is a PNG unsigned integer, so it is not permitted + * to be bigger than 2^31. + */ + png_fixed_point result; + if (ppm <= PNG_UINT_31_MAX && png_muldiv(&result, (png_int_32)ppm, 127, + 5000)) + return result; + + /* Overflow. */ + return 0; +#endif +} + +png_uint_32 PNGAPI +png_get_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_pixels_per_meter(png_ptr, info_ptr)); +} + +png_uint_32 PNGAPI +png_get_x_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_x_pixels_per_meter(png_ptr, info_ptr)); +} + +png_uint_32 PNGAPI +png_get_y_pixels_per_inch(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + return ppi_from_ppm(png_get_y_pixels_per_meter(png_ptr, info_ptr)); +} + +#ifdef PNG_FIXED_POINT_SUPPORTED +static png_fixed_point +png_fixed_inches_from_microns(png_const_structrp png_ptr, png_int_32 microns) +{ + /* Convert from metres * 1,000,000 to inches * 100,000, meters to + * inches is simply *(100/2.54), so we want *(10/2.54) == 500/127. + * Notice that this can overflow - a warning is output and 0 is + * returned. + */ + return png_muldiv_warn(png_ptr, microns, 500, 127); +} + +png_fixed_point PNGAPI +png_get_x_offset_inches_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ + return png_fixed_inches_from_microns(png_ptr, + png_get_x_offset_microns(png_ptr, info_ptr)); +} +#endif + +#ifdef PNG_FIXED_POINT_SUPPORTED +png_fixed_point PNGAPI +png_get_y_offset_inches_fixed(png_const_structrp png_ptr, + png_const_inforp info_ptr) +{ + return png_fixed_inches_from_microns(png_ptr, + png_get_y_offset_microns(png_ptr, info_ptr)); +} +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_x_offset_inches(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + /* To avoid the overflow do the conversion directly in floating + * point. + */ + return (float)(png_get_x_offset_microns(png_ptr, info_ptr) * .00003937); +} +#endif + +#ifdef PNG_FLOATING_POINT_SUPPORTED +float PNGAPI +png_get_y_offset_inches(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + /* To avoid the overflow do the conversion directly in floating + * point. + */ + return (float)(png_get_y_offset_microns(png_ptr, info_ptr) * .00003937); +} +#endif + +#ifdef PNG_pHYs_SUPPORTED +png_uint_32 PNGAPI +png_get_pHYs_dpi(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type) +{ + png_uint_32 retval = 0; + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_debug1(1, "in %s retrieval function", "pHYs"); + + if (res_x != NULL) + { + *res_x = info_ptr->x_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (res_y != NULL) + { + *res_y = info_ptr->y_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (unit_type != NULL) + { + *unit_type = (int)info_ptr->phys_unit_type; + retval |= PNG_INFO_pHYs; + + if (*unit_type == 1) + { + if (res_x != NULL) *res_x = (png_uint_32)(*res_x * .0254 + .50); + if (res_y != NULL) *res_y = (png_uint_32)(*res_y * .0254 + .50); + } + } + } + + return (retval); +} +#endif /* PNG_pHYs_SUPPORTED */ +#endif /* PNG_INCH_CONVERSIONS_SUPPORTED */ + +/* png_get_channels really belongs in here, too, but it's been around longer */ + +#endif /* PNG_EASY_ACCESS_SUPPORTED */ + + +png_byte PNGAPI +png_get_channels(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->channels); + + return (0); +} + +#ifdef PNG_READ_SUPPORTED +png_const_bytep PNGAPI +png_get_signature(png_const_structrp png_ptr, png_const_inforp info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return(info_ptr->signature); + + return (NULL); +} +#endif + +#ifdef PNG_bKGD_SUPPORTED +png_uint_32 PNGAPI +png_get_bKGD(png_const_structrp png_ptr, png_inforp info_ptr, + png_color_16p *background) +{ + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD) + && background != NULL) + { + png_debug1(1, "in %s retrieval function", "bKGD"); + + *background = &(info_ptr->background); + return (PNG_INFO_bKGD); + } + + return (0); +} +#endif + +#ifdef PNG_cHRM_SUPPORTED +/* The XYZ APIs were added in 1.5.5 to take advantage of the code added at the + * same time to correct the rgb grayscale coefficient defaults obtained from the + * cHRM chunk in 1.5.4 + */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cHRM(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *white_x, double *white_y, double *red_x, double *red_y, + double *green_x, double *green_y, double *blue_x, double *blue_y) +{ + /* Quiet API change: this code used to only return the end points if a cHRM + * chunk was present, but the end points can also come from iCCP or sRGB + * chunks, so in 1.6.0 the png_get_ APIs return the end points regardless and + * the png_set_ APIs merely check that set end points are mutually + * consistent. + */ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS)) + { + png_debug1(1, "in %s retrieval function", "cHRM"); + + if (white_x != NULL) + *white_x = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.whitex, "cHRM white X"); + if (white_y != NULL) + *white_y = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.whitey, "cHRM white Y"); + if (red_x != NULL) + *red_x = png_float(png_ptr, info_ptr->colorspace.end_points_xy.redx, + "cHRM red X"); + if (red_y != NULL) + *red_y = png_float(png_ptr, info_ptr->colorspace.end_points_xy.redy, + "cHRM red Y"); + if (green_x != NULL) + *green_x = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.greenx, "cHRM green X"); + if (green_y != NULL) + *green_y = png_float(png_ptr, + info_ptr->colorspace.end_points_xy.greeny, "cHRM green Y"); + if (blue_x != NULL) + *blue_x = png_float(png_ptr, info_ptr->colorspace.end_points_xy.bluex, + "cHRM blue X"); + if (blue_y != NULL) + *blue_y = png_float(png_ptr, info_ptr->colorspace.end_points_xy.bluey, + "cHRM blue Y"); + return (PNG_INFO_cHRM); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_cHRM_XYZ(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *red_X, double *red_Y, double *red_Z, double *green_X, + double *green_Y, double *green_Z, double *blue_X, double *blue_Y, + double *blue_Z) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS)) + { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ(float)"); + + if (red_X != NULL) + *red_X = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_X, + "cHRM red X"); + if (red_Y != NULL) + *red_Y = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_Y, + "cHRM red Y"); + if (red_Z != NULL) + *red_Z = png_float(png_ptr, info_ptr->colorspace.end_points_XYZ.red_Z, + "cHRM red Z"); + if (green_X != NULL) + *green_X = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_X, "cHRM green X"); + if (green_Y != NULL) + *green_Y = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_Y, "cHRM green Y"); + if (green_Z != NULL) + *green_Z = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.green_Z, "cHRM green Z"); + if (blue_X != NULL) + *blue_X = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_X, "cHRM blue X"); + if (blue_Y != NULL) + *blue_Y = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_Y, "cHRM blue Y"); + if (blue_Z != NULL) + *blue_Z = png_float(png_ptr, + info_ptr->colorspace.end_points_XYZ.blue_Z, "cHRM blue Z"); + return (PNG_INFO_cHRM); + } + + return (0); +} +# endif + +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *int_red_X, png_fixed_point *int_red_Y, + png_fixed_point *int_red_Z, png_fixed_point *int_green_X, + png_fixed_point *int_green_Y, png_fixed_point *int_green_Z, + png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y, + png_fixed_point *int_blue_Z) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS)) + { + png_debug1(1, "in %s retrieval function", "cHRM_XYZ"); + + if (int_red_X != NULL) + *int_red_X = info_ptr->colorspace.end_points_XYZ.red_X; + if (int_red_Y != NULL) + *int_red_Y = info_ptr->colorspace.end_points_XYZ.red_Y; + if (int_red_Z != NULL) + *int_red_Z = info_ptr->colorspace.end_points_XYZ.red_Z; + if (int_green_X != NULL) + *int_green_X = info_ptr->colorspace.end_points_XYZ.green_X; + if (int_green_Y != NULL) + *int_green_Y = info_ptr->colorspace.end_points_XYZ.green_Y; + if (int_green_Z != NULL) + *int_green_Z = info_ptr->colorspace.end_points_XYZ.green_Z; + if (int_blue_X != NULL) + *int_blue_X = info_ptr->colorspace.end_points_XYZ.blue_X; + if (int_blue_Y != NULL) + *int_blue_Y = info_ptr->colorspace.end_points_XYZ.blue_Y; + if (int_blue_Z != NULL) + *int_blue_Z = info_ptr->colorspace.end_points_XYZ.blue_Z; + return (PNG_INFO_cHRM); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_cHRM_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *white_x, png_fixed_point *white_y, png_fixed_point *red_x, + png_fixed_point *red_y, png_fixed_point *green_x, png_fixed_point *green_y, + png_fixed_point *blue_x, png_fixed_point *blue_y) +{ + png_debug1(1, "in %s retrieval function", "cHRM"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_ENDPOINTS)) + { + if (white_x != NULL) + *white_x = info_ptr->colorspace.end_points_xy.whitex; + if (white_y != NULL) + *white_y = info_ptr->colorspace.end_points_xy.whitey; + if (red_x != NULL) + *red_x = info_ptr->colorspace.end_points_xy.redx; + if (red_y != NULL) + *red_y = info_ptr->colorspace.end_points_xy.redy; + if (green_x != NULL) + *green_x = info_ptr->colorspace.end_points_xy.greenx; + if (green_y != NULL) + *green_y = info_ptr->colorspace.end_points_xy.greeny; + if (blue_x != NULL) + *blue_x = info_ptr->colorspace.end_points_xy.bluex; + if (blue_y != NULL) + *blue_y = info_ptr->colorspace.end_points_xy.bluey; + return (PNG_INFO_cHRM); + } + + return (0); +} +# endif +#endif + +#ifdef PNG_gAMA_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_gAMA_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_fixed_point *file_gamma) +{ + png_debug1(1, "in %s retrieval function", "gAMA"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) && + file_gamma != NULL) + { + *file_gamma = info_ptr->colorspace.gamma; + return (PNG_INFO_gAMA); + } + + return (0); +} +# endif + +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_gAMA(png_const_structrp png_ptr, png_const_inforp info_ptr, + double *file_gamma) +{ + png_debug1(1, "in %s retrieval function", "gAMA(float)"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) && + file_gamma != NULL) + { + *file_gamma = png_float(png_ptr, info_ptr->colorspace.gamma, + "png_get_gAMA"); + return (PNG_INFO_gAMA); + } + + return (0); +} +# endif +#endif + +#ifdef PNG_sRGB_SUPPORTED +png_uint_32 PNGAPI +png_get_sRGB(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *file_srgb_intent) +{ + png_debug1(1, "in %s retrieval function", "sRGB"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sRGB) + && file_srgb_intent != NULL) + { + *file_srgb_intent = info_ptr->colorspace.rendering_intent; + return (PNG_INFO_sRGB); + } + + return (0); +} +#endif + +#ifdef PNG_iCCP_SUPPORTED +png_uint_32 PNGAPI +png_get_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, + png_charpp name, int *compression_type, + png_bytepp profile, png_uint_32 *proflen) +{ + png_debug1(1, "in %s retrieval function", "iCCP"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_iCCP) + && name != NULL && compression_type != NULL && profile != NULL && + proflen != NULL) + { + *name = info_ptr->iccp_name; + *profile = info_ptr->iccp_profile; + *proflen = png_get_uint_32(info_ptr->iccp_profile); + /* This is somewhat irrelevant since the profile data returned has + * actually been uncompressed. + */ + *compression_type = PNG_COMPRESSION_TYPE_BASE; + return (PNG_INFO_iCCP); + } + + return (0); +} +#endif + +#ifdef PNG_sPLT_SUPPORTED +int PNGAPI +png_get_sPLT(png_const_structrp png_ptr, png_inforp info_ptr, + png_sPLT_tpp spalettes) +{ + if (png_ptr != NULL && info_ptr != NULL && spalettes != NULL) + { + *spalettes = info_ptr->splt_palettes; + return info_ptr->splt_palettes_num; + } + + return (0); +} +#endif + +#ifdef PNG_hIST_SUPPORTED +png_uint_32 PNGAPI +png_get_hIST(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_16p *hist) +{ + png_debug1(1, "in %s retrieval function", "hIST"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST) + && hist != NULL) + { + *hist = info_ptr->hist; + return (PNG_INFO_hIST); + } + + return (0); +} +#endif + +png_uint_32 PNGAPI +png_get_IHDR(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *width, png_uint_32 *height, int *bit_depth, + int *color_type, int *interlace_type, int *compression_type, + int *filter_type) +{ + png_debug1(1, "in %s retrieval function", "IHDR"); + + if (png_ptr == NULL || info_ptr == NULL || width == NULL || + height == NULL || bit_depth == NULL || color_type == NULL) + return (0); + + *width = info_ptr->width; + *height = info_ptr->height; + *bit_depth = info_ptr->bit_depth; + *color_type = info_ptr->color_type; + + if (compression_type != NULL) + *compression_type = info_ptr->compression_type; + + if (filter_type != NULL) + *filter_type = info_ptr->filter_type; + + if (interlace_type != NULL) + *interlace_type = info_ptr->interlace_type; + + /* This is redundant if we can be sure that the info_ptr values were all + * assigned in png_set_IHDR(). We do the check anyhow in case an + * application has ignored our advice not to mess with the members + * of info_ptr directly. + */ + png_check_IHDR(png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type, + info_ptr->compression_type, info_ptr->filter_type); + + return (1); +} + +#ifdef PNG_oFFs_SUPPORTED +png_uint_32 PNGAPI +png_get_oFFs(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_int_32 *offset_x, png_int_32 *offset_y, int *unit_type) +{ + png_debug1(1, "in %s retrieval function", "oFFs"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs) + && offset_x != NULL && offset_y != NULL && unit_type != NULL) + { + *offset_x = info_ptr->x_offset; + *offset_y = info_ptr->y_offset; + *unit_type = (int)info_ptr->offset_unit_type; + return (PNG_INFO_oFFs); + } + + return (0); +} +#endif + +#ifdef PNG_pCAL_SUPPORTED +png_uint_32 PNGAPI +png_get_pCAL(png_const_structrp png_ptr, png_inforp info_ptr, + png_charp *purpose, png_int_32 *X0, png_int_32 *X1, int *type, int *nparams, + png_charp *units, png_charpp *params) +{ + png_debug1(1, "in %s retrieval function", "pCAL"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_pCAL) + && purpose != NULL && X0 != NULL && X1 != NULL && type != NULL && + nparams != NULL && units != NULL && params != NULL) + { + *purpose = info_ptr->pcal_purpose; + *X0 = info_ptr->pcal_X0; + *X1 = info_ptr->pcal_X1; + *type = (int)info_ptr->pcal_type; + *nparams = (int)info_ptr->pcal_nparams; + *units = info_ptr->pcal_units; + *params = info_ptr->pcal_params; + return (PNG_INFO_pCAL); + } + + return (0); +} +#endif + +#ifdef PNG_sCAL_SUPPORTED +# ifdef PNG_FIXED_POINT_SUPPORTED +# if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || \ + defined(PNG_FLOATING_POINT_SUPPORTED) +png_uint_32 PNGAPI +png_get_sCAL_fixed(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, png_fixed_point *width, png_fixed_point *height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL)) + { + *unit = info_ptr->scal_unit; + /*TODO: make this work without FP support; the API is currently eliminated + * if neither floating point APIs nor internal floating point arithmetic + * are enabled. + */ + *width = png_fixed(png_ptr, atof(info_ptr->scal_s_width), "sCAL width"); + *height = png_fixed(png_ptr, atof(info_ptr->scal_s_height), + "sCAL height"); + return (PNG_INFO_sCAL); + } + + return(0); +} +# endif /* FLOATING_ARITHMETIC */ +# endif /* FIXED_POINT */ +# ifdef PNG_FLOATING_POINT_SUPPORTED +png_uint_32 PNGAPI +png_get_sCAL(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, double *width, double *height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL)) + { + *unit = info_ptr->scal_unit; + *width = atof(info_ptr->scal_s_width); + *height = atof(info_ptr->scal_s_height); + return (PNG_INFO_sCAL); + } + + return(0); +} +# endif /* FLOATING POINT */ +png_uint_32 PNGAPI +png_get_sCAL_s(png_const_structrp png_ptr, png_const_inforp info_ptr, + int *unit, png_charpp width, png_charpp height) +{ + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_sCAL)) + { + *unit = info_ptr->scal_unit; + *width = info_ptr->scal_s_width; + *height = info_ptr->scal_s_height; + return (PNG_INFO_sCAL); + } + + return(0); +} +#endif /* sCAL */ + +#ifdef PNG_pHYs_SUPPORTED +png_uint_32 PNGAPI +png_get_pHYs(png_const_structrp png_ptr, png_const_inforp info_ptr, + png_uint_32 *res_x, png_uint_32 *res_y, int *unit_type) +{ + png_uint_32 retval = 0; + + png_debug1(1, "in %s retrieval function", "pHYs"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_pHYs)) + { + if (res_x != NULL) + { + *res_x = info_ptr->x_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (res_y != NULL) + { + *res_y = info_ptr->y_pixels_per_unit; + retval |= PNG_INFO_pHYs; + } + + if (unit_type != NULL) + { + *unit_type = (int)info_ptr->phys_unit_type; + retval |= PNG_INFO_pHYs; + } + } + + return (retval); +} +#endif /* pHYs */ + +png_uint_32 PNGAPI +png_get_PLTE(png_const_structrp png_ptr, png_inforp info_ptr, + png_colorp *palette, int *num_palette) +{ + png_debug1(1, "in %s retrieval function", "PLTE"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_PLTE) + && palette != NULL) + { + *palette = info_ptr->palette; + *num_palette = info_ptr->num_palette; + png_debug1(3, "num_palette = %d", *num_palette); + return (PNG_INFO_PLTE); + } + + return (0); +} + +#ifdef PNG_sBIT_SUPPORTED +png_uint_32 PNGAPI +png_get_sBIT(png_const_structrp png_ptr, png_inforp info_ptr, + png_color_8p *sig_bit) +{ + png_debug1(1, "in %s retrieval function", "sBIT"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_sBIT) + && sig_bit != NULL) + { + *sig_bit = &(info_ptr->sig_bit); + return (PNG_INFO_sBIT); + } + + return (0); +} +#endif + +#ifdef PNG_TEXT_SUPPORTED +int PNGAPI +png_get_text(png_const_structrp png_ptr, png_inforp info_ptr, + png_textp *text_ptr, int *num_text) +{ + if (png_ptr != NULL && info_ptr != NULL && info_ptr->num_text > 0) + { + png_debug1(1, "in 0x%lx retrieval function", + (unsigned long)png_ptr->chunk_name); + + if (text_ptr != NULL) + *text_ptr = info_ptr->text; + + if (num_text != NULL) + *num_text = info_ptr->num_text; + + return info_ptr->num_text; + } + + if (num_text != NULL) + *num_text = 0; + + return(0); +} +#endif + +#ifdef PNG_tIME_SUPPORTED +png_uint_32 PNGAPI +png_get_tIME(png_const_structrp png_ptr, png_inforp info_ptr, + png_timep *mod_time) +{ + png_debug1(1, "in %s retrieval function", "tIME"); + + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_tIME) + && mod_time != NULL) + { + *mod_time = &(info_ptr->mod_time); + return (PNG_INFO_tIME); + } + + return (0); +} +#endif + +#ifdef PNG_tRNS_SUPPORTED +png_uint_32 PNGAPI +png_get_tRNS(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytep *trans_alpha, int *num_trans, png_color_16p *trans_color) +{ + png_uint_32 retval = 0; + if (png_ptr != NULL && info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS)) + { + png_debug1(1, "in %s retrieval function", "tRNS"); + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (trans_alpha != NULL) + { + *trans_alpha = info_ptr->trans_alpha; + retval |= PNG_INFO_tRNS; + } + + if (trans_color != NULL) + *trans_color = &(info_ptr->trans_color); + } + + else /* if (info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) */ + { + if (trans_color != NULL) + { + *trans_color = &(info_ptr->trans_color); + retval |= PNG_INFO_tRNS; + } + + if (trans_alpha != NULL) + *trans_alpha = NULL; + } + + if (num_trans != NULL) + { + *num_trans = info_ptr->num_trans; + retval |= PNG_INFO_tRNS; + } + } + + return (retval); +} +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +int PNGAPI +png_get_unknown_chunks(png_const_structrp png_ptr, png_inforp info_ptr, + png_unknown_chunkpp unknowns) +{ + if (png_ptr != NULL && info_ptr != NULL && unknowns != NULL) + { + *unknowns = info_ptr->unknown_chunks; + return info_ptr->unknown_chunks_num; + } + + return (0); +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +png_byte PNGAPI +png_get_rgb_to_gray_status (png_const_structrp png_ptr) +{ + return (png_byte)(png_ptr ? png_ptr->rgb_to_gray_status : 0); +} +#endif + +#ifdef PNG_USER_CHUNKS_SUPPORTED +png_voidp PNGAPI +png_get_user_chunk_ptr(png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_ptr : NULL); +} +#endif + +png_size_t PNGAPI +png_get_compression_buffer_size(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return 0; + +# ifdef PNG_WRITE_SUPPORTED + if (png_ptr->mode & PNG_IS_READ_STRUCT) +# endif + { +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + return png_ptr->IDAT_read_size; +# else + return PNG_IDAT_READ_SIZE; +# endif + } + +# ifdef PNG_WRITE_SUPPORTED + else + return png_ptr->zbuffer_size; +# endif +} + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +/* These functions were added to libpng 1.2.6 and were enabled + * by default in libpng-1.4.0 */ +png_uint_32 PNGAPI +png_get_user_width_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_width_max : 0); +} + +png_uint_32 PNGAPI +png_get_user_height_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_height_max : 0); +} + +/* This function was added to libpng 1.4.0 */ +png_uint_32 PNGAPI +png_get_chunk_cache_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_cache_max : 0); +} + +/* This function was added to libpng 1.4.1 */ +png_alloc_size_t PNGAPI +png_get_chunk_malloc_max (png_const_structrp png_ptr) +{ + return (png_ptr ? png_ptr->user_chunk_malloc_max : 0); +} +#endif /* ?PNG_SET_USER_LIMITS_SUPPORTED */ + +/* These functions were added to libpng 1.4.0 */ +#ifdef PNG_IO_STATE_SUPPORTED +png_uint_32 PNGAPI +png_get_io_state (png_const_structrp png_ptr) +{ + return png_ptr->io_state; +} + +png_uint_32 PNGAPI +png_get_io_chunk_type (png_const_structrp png_ptr) +{ + return png_ptr->chunk_name; +} +#endif /* ?PNG_IO_STATE_SUPPORTED */ + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +# ifdef PNG_GET_PALETTE_MAX_SUPPORTED +int PNGAPI +png_get_palette_max(png_const_structp png_ptr, png_const_infop info_ptr) +{ + if (png_ptr != NULL && info_ptr != NULL) + return png_ptr->num_palette_max; + + return (-1); +} +# endif +#endif + +#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pnginfo.h b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pnginfo.h new file mode 100644 index 0000000..898a406 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pnginfo.h @@ -0,0 +1,260 @@ + +/* pnginfo.h - header file for PNG reference library + * + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + + /* png_info is a structure that holds the information in a PNG file so + * that the application can find out the characteristics of the image. + * If you are reading the file, this structure will tell you what is + * in the PNG file. If you are writing the file, fill in the information + * you want to put into the PNG file, using png_set_*() functions, then + * call png_write_info(). + * + * The names chosen should be very close to the PNG specification, so + * consult that document for information about the meaning of each field. + * + * With libpng < 0.95, it was only possible to directly set and read the + * the values in the png_info_struct, which meant that the contents and + * order of the values had to remain fixed. With libpng 0.95 and later, + * however, there are now functions that abstract the contents of + * png_info_struct from the application, so this makes it easier to use + * libpng with dynamic libraries, and even makes it possible to use + * libraries that don't have all of the libpng ancillary chunk-handing + * functionality. In libpng-1.5.0 this was moved into a separate private + * file that is not visible to applications. + * + * The following members may have allocated storage attached that should be + * cleaned up before the structure is discarded: palette, trans, text, + * pcal_purpose, pcal_units, pcal_params, hist, iccp_name, iccp_profile, + * splt_palettes, scal_unit, row_pointers, and unknowns. By default, these + * are automatically freed when the info structure is deallocated, if they were + * allocated internally by libpng. This behavior can be changed by means + * of the png_data_freer() function. + * + * More allocation details: all the chunk-reading functions that + * change these members go through the corresponding png_set_* + * functions. A function to clear these members is available: see + * png_free_data(). The png_set_* functions do not depend on being + * able to point info structure members to any of the storage they are + * passed (they make their own copies), EXCEPT that the png_set_text + * functions use the same storage passed to them in the text_ptr or + * itxt_ptr structure argument, and the png_set_rows and png_set_unknowns + * functions do not make their own copies. + */ +#ifndef PNGINFO_H +#define PNGINFO_H + +struct png_info_def +{ + /* The following are necessary for every PNG file */ + png_uint_32 width; /* width of image in pixels (from IHDR) */ + png_uint_32 height; /* height of image in pixels (from IHDR) */ + png_uint_32 valid; /* valid chunk data (see PNG_INFO_ below) */ + png_size_t rowbytes; /* bytes needed to hold an untransformed row */ + png_colorp palette; /* array of color values (valid & PNG_INFO_PLTE) */ + png_uint_16 num_palette; /* number of color entries in "palette" (PLTE) */ + png_uint_16 num_trans; /* number of transparent palette color (tRNS) */ + png_byte bit_depth; /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */ + png_byte color_type; /* see PNG_COLOR_TYPE_ below (from IHDR) */ + /* The following three should have been named *_method not *_type */ + png_byte compression_type; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */ + png_byte filter_type; /* must be PNG_FILTER_TYPE_BASE (from IHDR) */ + png_byte interlace_type; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */ + + /* The following are set by png_set_IHDR, called from the application on + * write, but the are never actually used by the write code. + */ + png_byte channels; /* number of data channels per pixel (1, 2, 3, 4) */ + png_byte pixel_depth; /* number of bits per pixel */ + png_byte spare_byte; /* to align the data, and for future use */ + +#ifdef PNG_READ_SUPPORTED + /* This is never set during write */ + png_byte signature[8]; /* magic bytes read by libpng from start of file */ +#endif + + /* The rest of the data is optional. If you are reading, check the + * valid field to see if the information in these are valid. If you + * are writing, set the valid field to those chunks you want written, + * and initialize the appropriate fields below. + */ + +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) + /* png_colorspace only contains 'flags' if neither GAMMA or COLORSPACE are + * defined. When COLORSPACE is switched on all the colorspace-defining + * chunks should be enabled, when GAMMA is switched on all the gamma-defining + * chunks should be enabled. If this is not done it becomes possible to read + * inconsistent PNG files and assign a probably incorrect interpretation to + * the information. (In other words, by carefully choosing which chunks to + * recognize the system configuration can select an interpretation for PNG + * files containing ambiguous data and this will result in inconsistent + * behavior between different libpng builds!) + */ + png_colorspace colorspace; +#endif + +#ifdef PNG_iCCP_SUPPORTED + /* iCCP chunk data. */ + png_charp iccp_name; /* profile name */ + png_bytep iccp_profile; /* International Color Consortium profile data */ + png_uint_32 iccp_proflen; /* ICC profile data length */ +#endif + +#ifdef PNG_TEXT_SUPPORTED + /* The tEXt, and zTXt chunks contain human-readable textual data in + * uncompressed, compressed, and optionally compressed forms, respectively. + * The data in "text" is an array of pointers to uncompressed, + * null-terminated C strings. Each chunk has a keyword that describes the + * textual data contained in that chunk. Keywords are not required to be + * unique, and the text string may be empty. Any number of text chunks may + * be in an image. + */ + int num_text; /* number of comments read or comments to write */ + int max_text; /* current size of text array */ + png_textp text; /* array of comments read or comments to write */ +#endif /* PNG_TEXT_SUPPORTED */ + +#ifdef PNG_tIME_SUPPORTED + /* The tIME chunk holds the last time the displayed image data was + * modified. See the png_time struct for the contents of this struct. + */ + png_time mod_time; +#endif + +#ifdef PNG_sBIT_SUPPORTED + /* The sBIT chunk specifies the number of significant high-order bits + * in the pixel data. Values are in the range [1, bit_depth], and are + * only specified for the channels in the pixel data. The contents of + * the low-order bits is not specified. Data is valid if + * (valid & PNG_INFO_sBIT) is non-zero. + */ + png_color_8 sig_bit; /* significant bits in color channels */ +#endif + +#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_EXPAND_SUPPORTED) || \ +defined(PNG_READ_BACKGROUND_SUPPORTED) + /* The tRNS chunk supplies transparency data for paletted images and + * other image types that don't need a full alpha channel. There are + * "num_trans" transparency values for a paletted image, stored in the + * same order as the palette colors, starting from index 0. Values + * for the data are in the range [0, 255], ranging from fully transparent + * to fully opaque, respectively. For non-paletted images, there is a + * single color specified that should be treated as fully transparent. + * Data is valid if (valid & PNG_INFO_tRNS) is non-zero. + */ + png_bytep trans_alpha; /* alpha values for paletted image */ + png_color_16 trans_color; /* transparent color for non-palette image */ +#endif + +#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + /* The bKGD chunk gives the suggested image background color if the + * display program does not have its own background color and the image + * is needs to composited onto a background before display. The colors + * in "background" are normally in the same color space/depth as the + * pixel data. Data is valid if (valid & PNG_INFO_bKGD) is non-zero. + */ + png_color_16 background; +#endif + +#ifdef PNG_oFFs_SUPPORTED + /* The oFFs chunk gives the offset in "offset_unit_type" units rightwards + * and downwards from the top-left corner of the display, page, or other + * application-specific co-ordinate space. See the PNG_OFFSET_ defines + * below for the unit types. Valid if (valid & PNG_INFO_oFFs) non-zero. + */ + png_int_32 x_offset; /* x offset on page */ + png_int_32 y_offset; /* y offset on page */ + png_byte offset_unit_type; /* offset units type */ +#endif + +#ifdef PNG_pHYs_SUPPORTED + /* The pHYs chunk gives the physical pixel density of the image for + * display or printing in "phys_unit_type" units (see PNG_RESOLUTION_ + * defines below). Data is valid if (valid & PNG_INFO_pHYs) is non-zero. + */ + png_uint_32 x_pixels_per_unit; /* horizontal pixel density */ + png_uint_32 y_pixels_per_unit; /* vertical pixel density */ + png_byte phys_unit_type; /* resolution type (see PNG_RESOLUTION_ below) */ +#endif + +#ifdef PNG_hIST_SUPPORTED + /* The hIST chunk contains the relative frequency or importance of the + * various palette entries, so that a viewer can intelligently select a + * reduced-color palette, if required. Data is an array of "num_palette" + * values in the range [0,65535]. Data valid if (valid & PNG_INFO_hIST) + * is non-zero. + */ + png_uint_16p hist; +#endif + +#ifdef PNG_pCAL_SUPPORTED + /* The pCAL chunk describes a transformation between the stored pixel + * values and original physical data values used to create the image. + * The integer range [0, 2^bit_depth - 1] maps to the floating-point + * range given by [pcal_X0, pcal_X1], and are further transformed by a + * (possibly non-linear) transformation function given by "pcal_type" + * and "pcal_params" into "pcal_units". Please see the PNG_EQUATION_ + * defines below, and the PNG-Group's PNG extensions document for a + * complete description of the transformations and how they should be + * implemented, and for a description of the ASCII parameter strings. + * Data values are valid if (valid & PNG_INFO_pCAL) non-zero. + */ + png_charp pcal_purpose; /* pCAL chunk description string */ + png_int_32 pcal_X0; /* minimum value */ + png_int_32 pcal_X1; /* maximum value */ + png_charp pcal_units; /* Latin-1 string giving physical units */ + png_charpp pcal_params; /* ASCII strings containing parameter values */ + png_byte pcal_type; /* equation type (see PNG_EQUATION_ below) */ + png_byte pcal_nparams; /* number of parameters given in pcal_params */ +#endif + +/* New members added in libpng-1.0.6 */ + png_uint_32 free_me; /* flags items libpng is responsible for freeing */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + /* Storage for unknown chunks that the library doesn't recognize. */ + png_unknown_chunkp unknown_chunks; + + /* The type of this field is limited by the type of + * png_struct::user_chunk_cache_max, else overflow can occur. + */ + int unknown_chunks_num; +#endif + +#ifdef PNG_sPLT_SUPPORTED + /* Data on sPLT chunks (there may be more than one). */ + png_sPLT_tp splt_palettes; + int splt_palettes_num; /* Match type returned by png_get API */ +#endif + +#ifdef PNG_sCAL_SUPPORTED + /* The sCAL chunk describes the actual physical dimensions of the + * subject matter of the graphic. The chunk contains a unit specification + * a byte value, and two ASCII strings representing floating-point + * values. The values are width and height corresponsing to one pixel + * in the image. Data values are valid if (valid & PNG_INFO_sCAL) is + * non-zero. + */ + png_byte scal_unit; /* unit of physical scale */ + png_charp scal_s_width; /* string containing height */ + png_charp scal_s_height; /* string containing width */ +#endif + +#ifdef PNG_INFO_IMAGE_SUPPORTED + /* Memory has been allocated if (valid & PNG_ALLOCATED_INFO_ROWS) + non-zero */ + /* Data valid if (valid & PNG_INFO_IDAT) non-zero */ + png_bytepp row_pointers; /* the image bits */ +#endif + +}; +#endif /* PNGINFO_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngmem.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngmem.c new file mode 100644 index 0000000..43e2948 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngmem.c @@ -0,0 +1,277 @@ + +/* pngmem.c - stub functions for memory allocation + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all memory allocation. Users who + * need special memory handling are expected to supply replacement + * functions for png_malloc() and png_free(), and to use + * png_create_read_struct_2() and png_create_write_struct_2() to + * identify the replacement functions. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) +/* Free a png_struct */ +void /* PRIVATE */ +png_destroy_png_struct(png_structrp png_ptr) +{ + if (png_ptr != NULL) + { + /* png_free might call png_error and may certainly call + * png_get_mem_ptr, so fake a temporary png_struct to support this. + */ + png_struct dummy_struct = *png_ptr; + memset(png_ptr, 0, (sizeof *png_ptr)); + png_free(&dummy_struct, png_ptr); + +# ifdef PNG_SETJMP_SUPPORTED + /* We may have a jmp_buf left to deallocate. */ + png_free_jmpbuf(&dummy_struct); +# endif + } +} + +/* Allocate memory. For reasonable files, size should never exceed + * 64K. However, zlib may allocate more then 64K if you don't tell + * it not to. See zconf.h and png.h for more information. zlib does + * need to allocate exactly 64K, so whatever you call here must + * have the ability to do that. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_calloc,(png_const_structrp png_ptr, png_alloc_size_t size),PNG_ALLOCATED) +{ + png_voidp ret; + + ret = png_malloc(png_ptr, size); + + if (ret != NULL) + memset(ret, 0, size); + + return ret; +} + +/* png_malloc_base, an internal function added at libpng 1.6.0, does the work of + * allocating memory, taking into account limits and PNG_USER_MEM_SUPPORTED. + * Checking and error handling must happen outside this routine; it returns NULL + * if the allocation cannot be done (for any reason.) + */ +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_malloc_base,(png_const_structrp, png_alloc_size_t size), + PNG_ALLOCATED) +{ + /* Moved to png_malloc_base from png_malloc_default in 1.6.0; the DOS + * allocators have also been removed in 1.6.0, so any 16-bit system now has + * to implement a user memory handler. This checks to be sure it isn't + * called with big numbers. + */ +#ifdef PNG_USER_MEM_SUPPORTED + PNG_UNUSED(png_ptr) +#endif + if (size > 0 && size <= PNG_SIZE_MAX +# ifdef PNG_MAX_MALLOC_64K + && size <= 65536U +# endif + ) + { +#ifdef PNG_USER_MEM_SUPPORTED + if (png_ptr != NULL && png_ptr->malloc_fn != NULL) + return png_ptr->malloc_fn(png_constcast(png_structrp,png_ptr), size); + + else +#endif + return malloc((size_t)size); /* checked for truncation above */ + } + + else + return NULL; +} + +/* This is really here only to work round a spurious warning in GCC 4.6 and 4.7 + * that arises because of the checks in png_realloc_array that are repeated in + * png_malloc_array. + */ +static png_voidp +png_malloc_array_checked(png_const_structrp png_ptr, int nelements, + size_t element_size) +{ + png_alloc_size_t req = nelements; /* known to be > 0 */ + + if (req <= PNG_SIZE_MAX/element_size) + return png_malloc_base(png_ptr, req * element_size); + + /* The failure case when the request is too large */ + return NULL; +} + +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_malloc_array,(png_const_structrp png_ptr, int nelements, + size_t element_size),PNG_ALLOCATED) +{ + if (nelements <= 0 || element_size == 0) + png_error(png_ptr, "internal error: array alloc"); + + return png_malloc_array_checked(png_ptr, nelements, element_size); +} + +PNG_FUNCTION(png_voidp /* PRIVATE */, +png_realloc_array,(png_const_structrp png_ptr, png_const_voidp old_array, + int old_elements, int add_elements, size_t element_size),PNG_ALLOCATED) +{ + /* These are internal errors: */ + if (add_elements <= 0 || element_size == 0 || old_elements < 0 || + (old_array == NULL && old_elements > 0)) + png_error(png_ptr, "internal error: array realloc"); + + /* Check for overflow on the elements count (so the caller does not have to + * check.) + */ + if (add_elements <= INT_MAX - old_elements) + { + png_voidp new_array = png_malloc_array_checked(png_ptr, + old_elements+add_elements, element_size); + + if (new_array != NULL) + { + /* Because png_malloc_array worked the size calculations below cannot + * overflow. + */ + if (old_elements > 0) + memcpy(new_array, old_array, element_size*(unsigned)old_elements); + + memset((char*)new_array + element_size*(unsigned)old_elements, 0, + element_size*(unsigned)add_elements); + + return new_array; + } + } + + return NULL; /* error */ +} + +/* Various functions that have different error handling are derived from this. + * png_malloc always exists, but if PNG_USER_MEM_SUPPORTED is defined a separate + * function png_malloc_default is also provided. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc,(png_const_structrp png_ptr, png_alloc_size_t size),PNG_ALLOCATED) +{ + png_voidp ret; + + if (png_ptr == NULL) + return NULL; + + ret = png_malloc_base(png_ptr, size); + + if (ret == NULL) + png_error(png_ptr, "Out of memory"); /* 'm' means png_malloc */ + + return ret; +} + +#ifdef PNG_USER_MEM_SUPPORTED +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc_default,(png_const_structrp png_ptr, png_alloc_size_t size), + PNG_ALLOCATED PNG_DEPRECATED) +{ + png_voidp ret; + + if (png_ptr == NULL) + return NULL; + + /* Passing 'NULL' here bypasses the application provided memory handler. */ + ret = png_malloc_base(NULL/*use malloc*/, size); + + if (ret == NULL) + png_error(png_ptr, "Out of Memory"); /* 'M' means png_malloc_default */ + + return ret; +} +#endif /* PNG_USER_MEM_SUPPORTED */ + +/* This function was added at libpng version 1.2.3. The png_malloc_warn() + * function will issue a png_warning and return NULL instead of issuing a + * png_error, if it fails to allocate the requested memory. + */ +PNG_FUNCTION(png_voidp,PNGAPI +png_malloc_warn,(png_const_structrp png_ptr, png_alloc_size_t size), + PNG_ALLOCATED) +{ + if (png_ptr != NULL) + { + png_voidp ret = png_malloc_base(png_ptr, size); + + if (ret != NULL) + return ret; + + png_warning(png_ptr, "Out of memory"); + } + + return NULL; +} + +/* Free a pointer allocated by png_malloc(). If ptr is NULL, return + * without taking any action. + */ +void PNGAPI +png_free(png_const_structrp png_ptr, png_voidp ptr) +{ + if (png_ptr == NULL || ptr == NULL) + return; + +#ifdef PNG_USER_MEM_SUPPORTED + if (png_ptr->free_fn != NULL) + png_ptr->free_fn(png_constcast(png_structrp,png_ptr), ptr); + + else + png_free_default(png_ptr, ptr); +} + +PNG_FUNCTION(void,PNGAPI +png_free_default,(png_const_structrp png_ptr, png_voidp ptr),PNG_DEPRECATED) +{ + if (png_ptr == NULL || ptr == NULL) + return; +#endif /* PNG_USER_MEM_SUPPORTED */ + + free(ptr); +} + +#ifdef PNG_USER_MEM_SUPPORTED +/* This function is called when the application wants to use another method + * of allocating and freeing memory. + */ +void PNGAPI +png_set_mem_fn(png_structrp png_ptr, png_voidp mem_ptr, png_malloc_ptr + malloc_fn, png_free_ptr free_fn) +{ + if (png_ptr != NULL) + { + png_ptr->mem_ptr = mem_ptr; + png_ptr->malloc_fn = malloc_fn; + png_ptr->free_fn = free_fn; + } +} + +/* This function returns a pointer to the mem_ptr associated with the user + * functions. The application should free any memory associated with this + * pointer before png_write_destroy and png_read_destroy are called. + */ +png_voidp PNGAPI +png_get_mem_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return NULL; + + return png_ptr->mem_ptr; +} +#endif /* PNG_USER_MEM_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpread.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpread.c new file mode 100644 index 0000000..a133331 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpread.c @@ -0,0 +1,1291 @@ + +/* pngpread.c - read a png file in push mode + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + +/* Push model modes */ +#define PNG_READ_SIG_MODE 0 +#define PNG_READ_CHUNK_MODE 1 +#define PNG_READ_IDAT_MODE 2 +#define PNG_SKIP_MODE 3 +#define PNG_READ_tEXt_MODE 4 +#define PNG_READ_zTXt_MODE 5 +#define PNG_READ_DONE_MODE 6 +#define PNG_READ_iTXt_MODE 7 +#define PNG_ERROR_MODE 8 + +void PNGAPI +png_process_data(png_structrp png_ptr, png_inforp info_ptr, + png_bytep buffer, png_size_t buffer_size) +{ + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_push_restore_buffer(png_ptr, buffer, buffer_size); + + while (png_ptr->buffer_size) + { + png_process_some_data(png_ptr, info_ptr); + } +} + +png_size_t PNGAPI +png_process_data_pause(png_structrp png_ptr, int save) +{ + if (png_ptr != NULL) + { + /* It's easiest for the caller if we do the save, then the caller doesn't + * have to supply the same data again: + */ + if (save) + png_push_save_buffer(png_ptr); + else + { + /* This includes any pending saved bytes: */ + png_size_t remaining = png_ptr->buffer_size; + png_ptr->buffer_size = 0; + + /* So subtract the saved buffer size, unless all the data + * is actually 'saved', in which case we just return 0 + */ + if (png_ptr->save_buffer_size < remaining) + return remaining - png_ptr->save_buffer_size; + } + } + + return 0; +} + +png_uint_32 PNGAPI +png_process_data_skip(png_structrp png_ptr) +{ + png_uint_32 remaining = 0; + + if (png_ptr != NULL && png_ptr->process_mode == PNG_SKIP_MODE && + png_ptr->skip_length > 0) + { + /* At the end of png_process_data the buffer size must be 0 (see the loop + * above) so we can detect a broken call here: + */ + if (png_ptr->buffer_size != 0) + png_error(png_ptr, + "png_process_data_skip called inside png_process_data"); + + /* If is impossible for there to be a saved buffer at this point - + * otherwise we could not be in SKIP mode. This will also happen if + * png_process_skip is called inside png_process_data (but only very + * rarely.) + */ + if (png_ptr->save_buffer_size != 0) + png_error(png_ptr, "png_process_data_skip called with saved data"); + + remaining = png_ptr->skip_length; + png_ptr->skip_length = 0; + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + } + + return remaining; +} + +/* What we do with the incoming data depends on what we were previously + * doing before we ran out of data... + */ +void /* PRIVATE */ +png_process_some_data(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr == NULL) + return; + + switch (png_ptr->process_mode) + { + case PNG_READ_SIG_MODE: + { + png_push_read_sig(png_ptr, info_ptr); + break; + } + + case PNG_READ_CHUNK_MODE: + { + png_push_read_chunk(png_ptr, info_ptr); + break; + } + + case PNG_READ_IDAT_MODE: + { + png_push_read_IDAT(png_ptr); + break; + } + + case PNG_SKIP_MODE: + { + png_push_crc_finish(png_ptr); + break; + } + + default: + { + png_ptr->buffer_size = 0; + break; + } + } +} + +/* Read any remaining signature bytes from the stream and compare them with + * the correct PNG signature. It is possible that this routine is called + * with bytes already read from the signature, either because they have been + * checked by the calling application, or because of multiple calls to this + * routine. + */ +void /* PRIVATE */ +png_push_read_sig(png_structrp png_ptr, png_inforp info_ptr) +{ + png_size_t num_checked = png_ptr->sig_bytes, + num_to_check = 8 - num_checked; + + if (png_ptr->buffer_size < num_to_check) + { + num_to_check = png_ptr->buffer_size; + } + + png_push_fill_buffer(png_ptr, &(info_ptr->signature[num_checked]), + num_to_check); + png_ptr->sig_bytes = (png_byte)(png_ptr->sig_bytes + num_to_check); + + if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check)) + { + if (num_checked < 4 && + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_error(png_ptr, "Not a PNG file"); + + else + png_error(png_ptr, "PNG file corrupted by ASCII conversion"); + } + else + { + if (png_ptr->sig_bytes >= 8) + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + } + } +} + +void /* PRIVATE */ +png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) +{ + png_uint_32 chunk_name; +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; /* unknown handling method */ +#endif + + /* First we make sure we have enough data for the 4 byte chunk name + * and the 4 byte chunk length before proceeding with decoding the + * chunk data. To fully decode each of these chunks, we also make + * sure we have enough data in the buffer for the 4 byte CRC at the + * end of every chunk (except IDAT, which is handled separately). + */ + if (!(png_ptr->mode & PNG_HAVE_CHUNK_HEADER)) + { + png_byte chunk_length[4]; + png_byte chunk_tag[4]; + + if (png_ptr->buffer_size < 8) + { + png_push_save_buffer(png_ptr); + return; + } + + png_push_fill_buffer(png_ptr, chunk_length, 4); + png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); + png_reset_crc(png_ptr); + png_crc_read(png_ptr, chunk_tag, 4); + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); + png_check_chunk_name(png_ptr, png_ptr->chunk_name); + png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; + } + + chunk_name = png_ptr->chunk_name; + + if (chunk_name == png_IDAT) + { + if (png_ptr->mode & PNG_AFTER_IDAT) + png_ptr->mode |= PNG_HAVE_CHUNK_AFTER_IDAT; + + /* If we reach an IDAT chunk, this means we have read all of the + * header chunks, and we can start reading the image (or if this + * is called after the image has been read - we have an error). + */ + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + !(png_ptr->mode & PNG_HAVE_PLTE)) + png_error(png_ptr, "Missing PLTE before IDAT"); + + png_ptr->mode |= PNG_HAVE_IDAT; + + if (!(png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) + if (png_ptr->push_length == 0) + return; + + if (png_ptr->mode & PNG_AFTER_IDAT) + png_benign_error(png_ptr, "Too many IDATs found"); + } + + if (chunk_name == png_IHDR) + { + if (png_ptr->push_length != 13) + png_error(png_ptr, "Invalid IHDR length"); + + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_IHDR(png_ptr, info_ptr, png_ptr->push_length); + } + + else if (chunk_name == png_IEND) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_IEND(png_ptr, info_ptr, png_ptr->push_length); + + png_ptr->process_mode = PNG_READ_DONE_MODE; + png_push_have_end(png_ptr, info_ptr); + } + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, keep); + + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + } + +#endif + else if (chunk_name == png_PLTE) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + png_handle_PLTE(png_ptr, info_ptr, png_ptr->push_length); + } + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = png_ptr->push_length; + png_ptr->process_mode = PNG_READ_IDAT_MODE; + png_push_have_info(png_ptr, info_ptr); + png_ptr->zstream.avail_out = + (uInt) PNG_ROWBYTES(png_ptr->pixel_depth, + png_ptr->iwidth) + 1; + png_ptr->zstream.next_out = png_ptr->row_buf; + return; + } + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (png_ptr->chunk_name == png_gAMA) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_gAMA(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sBIT_SUPPORTED + else if (png_ptr->chunk_name == png_sBIT) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_sBIT(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_cHRM_SUPPORTED + else if (png_ptr->chunk_name == png_cHRM) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_cHRM(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_sRGB(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_iCCP_SUPPORTED + else if (png_ptr->chunk_name == png_iCCP) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_iCCP(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_sPLT(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_tRNS(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_bKGD(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_hIST(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_pHYs(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_oFFs(png_ptr, info_ptr, png_ptr->push_length); + } +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_pCAL(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_sCAL(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_tIME(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_tEXt(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_zTXt(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_iTXt(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif + else + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + png_handle_unknown(png_ptr, info_ptr, png_ptr->push_length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } + + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; +} + +void /* PRIVATE */ +png_push_crc_skip(png_structrp png_ptr, png_uint_32 skip) +{ + png_ptr->process_mode = PNG_SKIP_MODE; + png_ptr->skip_length = skip; +} + +void /* PRIVATE */ +png_push_crc_finish(png_structrp png_ptr) +{ + if (png_ptr->skip_length && png_ptr->save_buffer_size) + { + png_size_t save_size = png_ptr->save_buffer_size; + png_uint_32 skip_length = png_ptr->skip_length; + + /* We want the smaller of 'skip_length' and 'save_buffer_size', but + * they are of different types and we don't know which variable has the + * fewest bits. Carefully select the smaller and cast it to the type of + * the larger - this cannot overflow. Do not cast in the following test + * - it will break on either 16 or 64 bit platforms. + */ + if (skip_length < save_size) + save_size = (png_size_t)skip_length; + + else + skip_length = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size); + + png_ptr->skip_length -= skip_length; + png_ptr->buffer_size -= save_size; + png_ptr->save_buffer_size -= save_size; + png_ptr->save_buffer_ptr += save_size; + } + if (png_ptr->skip_length && png_ptr->current_buffer_size) + { + png_size_t save_size = png_ptr->current_buffer_size; + png_uint_32 skip_length = png_ptr->skip_length; + + /* We want the smaller of 'skip_length' and 'current_buffer_size', here, + * the same problem exists as above and the same solution. + */ + if (skip_length < save_size) + save_size = (png_size_t)skip_length; + + else + skip_length = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size); + + png_ptr->skip_length -= skip_length; + png_ptr->buffer_size -= save_size; + png_ptr->current_buffer_size -= save_size; + png_ptr->current_buffer_ptr += save_size; + } + if (!png_ptr->skip_length) + { + if (png_ptr->buffer_size < 4) + { + png_push_save_buffer(png_ptr); + return; + } + + png_crc_finish(png_ptr, 0); + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + } +} + +void PNGCBAPI +png_push_fill_buffer(png_structp png_ptr, png_bytep buffer, png_size_t length) +{ + png_bytep ptr; + + if (png_ptr == NULL) + return; + + ptr = buffer; + if (png_ptr->save_buffer_size) + { + png_size_t save_size; + + if (length < png_ptr->save_buffer_size) + save_size = length; + + else + save_size = png_ptr->save_buffer_size; + + memcpy(ptr, png_ptr->save_buffer_ptr, save_size); + length -= save_size; + ptr += save_size; + png_ptr->buffer_size -= save_size; + png_ptr->save_buffer_size -= save_size; + png_ptr->save_buffer_ptr += save_size; + } + if (length && png_ptr->current_buffer_size) + { + png_size_t save_size; + + if (length < png_ptr->current_buffer_size) + save_size = length; + + else + save_size = png_ptr->current_buffer_size; + + memcpy(ptr, png_ptr->current_buffer_ptr, save_size); + png_ptr->buffer_size -= save_size; + png_ptr->current_buffer_size -= save_size; + png_ptr->current_buffer_ptr += save_size; + } +} + +void /* PRIVATE */ +png_push_save_buffer(png_structrp png_ptr) +{ + if (png_ptr->save_buffer_size) + { + if (png_ptr->save_buffer_ptr != png_ptr->save_buffer) + { + png_size_t i, istop; + png_bytep sp; + png_bytep dp; + + istop = png_ptr->save_buffer_size; + for (i = 0, sp = png_ptr->save_buffer_ptr, dp = png_ptr->save_buffer; + i < istop; i++, sp++, dp++) + { + *dp = *sp; + } + } + } + if (png_ptr->save_buffer_size + png_ptr->current_buffer_size > + png_ptr->save_buffer_max) + { + png_size_t new_max; + png_bytep old_buffer; + + if (png_ptr->save_buffer_size > PNG_SIZE_MAX - + (png_ptr->current_buffer_size + 256)) + { + png_error(png_ptr, "Potential overflow of save_buffer"); + } + + new_max = png_ptr->save_buffer_size + png_ptr->current_buffer_size + 256; + old_buffer = png_ptr->save_buffer; + png_ptr->save_buffer = (png_bytep)png_malloc_warn(png_ptr, + (png_size_t)new_max); + + if (png_ptr->save_buffer == NULL) + { + png_free(png_ptr, old_buffer); + png_error(png_ptr, "Insufficient memory for save_buffer"); + } + + memcpy(png_ptr->save_buffer, old_buffer, png_ptr->save_buffer_size); + png_free(png_ptr, old_buffer); + png_ptr->save_buffer_max = new_max; + } + if (png_ptr->current_buffer_size) + { + memcpy(png_ptr->save_buffer + png_ptr->save_buffer_size, + png_ptr->current_buffer_ptr, png_ptr->current_buffer_size); + png_ptr->save_buffer_size += png_ptr->current_buffer_size; + png_ptr->current_buffer_size = 0; + } + png_ptr->save_buffer_ptr = png_ptr->save_buffer; + png_ptr->buffer_size = 0; +} + +void /* PRIVATE */ +png_push_restore_buffer(png_structrp png_ptr, png_bytep buffer, + png_size_t buffer_length) +{ + png_ptr->current_buffer = buffer; + png_ptr->current_buffer_size = buffer_length; + png_ptr->buffer_size = buffer_length + png_ptr->save_buffer_size; + png_ptr->current_buffer_ptr = png_ptr->current_buffer; +} + +void /* PRIVATE */ +png_push_read_IDAT(png_structrp png_ptr) +{ + if (!(png_ptr->mode & PNG_HAVE_CHUNK_HEADER)) + { + png_byte chunk_length[4]; + png_byte chunk_tag[4]; + + /* TODO: this code can be commoned up with the same code in push_read */ + if (png_ptr->buffer_size < 8) + { + png_push_save_buffer(png_ptr); + return; + } + + png_push_fill_buffer(png_ptr, chunk_length, 4); + png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); + png_reset_crc(png_ptr); + png_crc_read(png_ptr, chunk_tag, 4); + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); + png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; + + if (png_ptr->chunk_name != png_IDAT) + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + + if (!(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + png_error(png_ptr, "Not enough compressed data"); + + return; + } + + png_ptr->idat_size = png_ptr->push_length; + } + + if (png_ptr->idat_size && png_ptr->save_buffer_size) + { + png_size_t save_size = png_ptr->save_buffer_size; + png_uint_32 idat_size = png_ptr->idat_size; + + /* We want the smaller of 'idat_size' and 'current_buffer_size', but they + * are of different types and we don't know which variable has the fewest + * bits. Carefully select the smaller and cast it to the type of the + * larger - this cannot overflow. Do not cast in the following test - it + * will break on either 16 or 64 bit platforms. + */ + if (idat_size < save_size) + save_size = (png_size_t)idat_size; + + else + idat_size = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size); + + png_process_IDAT_data(png_ptr, png_ptr->save_buffer_ptr, save_size); + + png_ptr->idat_size -= idat_size; + png_ptr->buffer_size -= save_size; + png_ptr->save_buffer_size -= save_size; + png_ptr->save_buffer_ptr += save_size; + } + + if (png_ptr->idat_size && png_ptr->current_buffer_size) + { + png_size_t save_size = png_ptr->current_buffer_size; + png_uint_32 idat_size = png_ptr->idat_size; + + /* We want the smaller of 'idat_size' and 'current_buffer_size', but they + * are of different types and we don't know which variable has the fewest + * bits. Carefully select the smaller and cast it to the type of the + * larger - this cannot overflow. + */ + if (idat_size < save_size) + save_size = (png_size_t)idat_size; + + else + idat_size = (png_uint_32)save_size; + + png_calculate_crc(png_ptr, png_ptr->current_buffer_ptr, save_size); + + png_process_IDAT_data(png_ptr, png_ptr->current_buffer_ptr, save_size); + + png_ptr->idat_size -= idat_size; + png_ptr->buffer_size -= save_size; + png_ptr->current_buffer_size -= save_size; + png_ptr->current_buffer_ptr += save_size; + } + if (!png_ptr->idat_size) + { + if (png_ptr->buffer_size < 4) + { + png_push_save_buffer(png_ptr); + return; + } + + png_crc_finish(png_ptr, 0); + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->zowner = 0; + } +} + +void /* PRIVATE */ +png_process_IDAT_data(png_structrp png_ptr, png_bytep buffer, + png_size_t buffer_length) +{ + /* The caller checks for a non-zero buffer length. */ + if (!(buffer_length > 0) || buffer == NULL) + png_error(png_ptr, "No IDAT data (internal error)"); + + /* This routine must process all the data it has been given + * before returning, calling the row callback as required to + * handle the uncompressed results. + */ + png_ptr->zstream.next_in = buffer; + /* TODO: WARNING: TRUNCATION ERROR: DANGER WILL ROBINSON: */ + png_ptr->zstream.avail_in = (uInt)buffer_length; + + /* Keep going until the decompressed data is all processed + * or the stream marked as finished. + */ + while (png_ptr->zstream.avail_in > 0 && + !(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + { + int ret; + + /* We have data for zlib, but we must check that zlib + * has someplace to put the results. It doesn't matter + * if we don't expect any results -- it may be the input + * data is just the LZ end code. + */ + if (!(png_ptr->zstream.avail_out > 0)) + { + /* TODO: WARNING: TRUNCATION ERROR: DANGER WILL ROBINSON: */ + png_ptr->zstream.avail_out = (uInt)(PNG_ROWBYTES(png_ptr->pixel_depth, + png_ptr->iwidth) + 1); + + png_ptr->zstream.next_out = png_ptr->row_buf; + } + + /* Using Z_SYNC_FLUSH here means that an unterminated + * LZ stream (a stream with a missing end code) can still + * be handled, otherwise (Z_NO_FLUSH) a future zlib + * implementation might defer output and therefore + * change the current behavior (see comments in inflate.c + * for why this doesn't happen at present with zlib 1.2.5). + */ + ret = inflate(&png_ptr->zstream, Z_SYNC_FLUSH); + + /* Check for any failure before proceeding. */ + if (ret != Z_OK && ret != Z_STREAM_END) + { + /* Terminate the decompression. */ + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + png_ptr->zowner = 0; + + /* This may be a truncated stream (missing or + * damaged end code). Treat that as a warning. + */ + if (png_ptr->row_number >= png_ptr->num_rows || + png_ptr->pass > 6) + png_warning(png_ptr, "Truncated compressed data in IDAT"); + + else + png_error(png_ptr, "Decompression error in IDAT"); + + /* Skip the check on unprocessed input */ + return; + } + + /* Did inflate output any data? */ + if (png_ptr->zstream.next_out != png_ptr->row_buf) + { + /* Is this unexpected data after the last row? + * If it is, artificially terminate the LZ output + * here. + */ + if (png_ptr->row_number >= png_ptr->num_rows || + png_ptr->pass > 6) + { + /* Extra data. */ + png_warning(png_ptr, "Extra compressed data in IDAT"); + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + png_ptr->zowner = 0; + + /* Do no more processing; skip the unprocessed + * input check below. + */ + return; + } + + /* Do we have a complete row? */ + if (png_ptr->zstream.avail_out == 0) + png_push_process_row(png_ptr); + } + + /* And check for the end of the stream. */ + if (ret == Z_STREAM_END) + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + } + + /* All the data should have been processed, if anything + * is left at this point we have bytes of IDAT data + * after the zlib end code. + */ + if (png_ptr->zstream.avail_in > 0) + png_warning(png_ptr, "Extra compression data in IDAT"); +} + +void /* PRIVATE */ +png_push_process_row(png_structrp png_ptr) +{ + /* 1.5.6: row_info moved out of png_struct to a local here. */ + png_row_info row_info; + + row_info.width = png_ptr->iwidth; /* NOTE: width of current interlaced row */ + row_info.color_type = png_ptr->color_type; + row_info.bit_depth = png_ptr->bit_depth; + row_info.channels = png_ptr->channels; + row_info.pixel_depth = png_ptr->pixel_depth; + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + + if (png_ptr->row_buf[0] > PNG_FILTER_VALUE_NONE) + { + if (png_ptr->row_buf[0] < PNG_FILTER_VALUE_LAST) + png_read_filter_row(png_ptr, &row_info, png_ptr->row_buf + 1, + png_ptr->prev_row + 1, png_ptr->row_buf[0]); + else + png_error(png_ptr, "bad adaptive filter value"); + } + + /* libpng 1.5.6: the following line was copying png_ptr->rowbytes before + * 1.5.6, while the buffer really is this big in current versions of libpng + * it may not be in the future, so this was changed just to copy the + * interlaced row count: + */ + memcpy(png_ptr->prev_row, png_ptr->row_buf, row_info.rowbytes + 1); + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + if (png_ptr->transformations) + png_do_read_transformations(png_ptr, &row_info); +#endif + + /* The transformed pixel depth should match the depth now in row_info. */ + if (png_ptr->transformed_pixel_depth == 0) + { + png_ptr->transformed_pixel_depth = row_info.pixel_depth; + if (row_info.pixel_depth > png_ptr->maximum_pixel_depth) + png_error(png_ptr, "progressive row overflow"); + } + + else if (png_ptr->transformed_pixel_depth != row_info.pixel_depth) + png_error(png_ptr, "internal progressive row size calculation error"); + + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Blow up interlaced rows to full size */ + if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE)) + { + if (png_ptr->pass < 6) + png_do_read_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass, + png_ptr->transformations); + + switch (png_ptr->pass) + { + case 0: + { + int i; + for (i = 0; i < 8 && png_ptr->pass == 0; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); /* Updates png_ptr->pass */ + } + + if (png_ptr->pass == 2) /* Pass 1 might be empty */ + { + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + if (png_ptr->pass == 4 && png_ptr->height <= 4) + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + if (png_ptr->pass == 6 && png_ptr->height <= 4) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + case 1: + { + int i; + for (i = 0; i < 8 && png_ptr->pass == 1; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 2) /* Skip top 4 generated rows */ + { + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 2: + { + int i; + + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + for (i = 0; i < 4 && png_ptr->pass == 2; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 4) /* Pass 3 might be empty */ + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 3: + { + int i; + + for (i = 0; i < 4 && png_ptr->pass == 3; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 4) /* Skip top two generated rows */ + { + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + + break; + } + + case 4: + { + int i; + + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + for (i = 0; i < 2 && png_ptr->pass == 4; i++) + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 6) /* Pass 5 might be empty */ + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + case 5: + { + int i; + + for (i = 0; i < 2 && png_ptr->pass == 5; i++) + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } + + if (png_ptr->pass == 6) /* Skip top generated row */ + { + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + + break; + } + + default: + case 6: + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + + if (png_ptr->pass != 6) + break; + + png_push_have_row(png_ptr, NULL); + png_read_push_finish_row(png_ptr); + } + } + } + else +#endif + { + png_push_have_row(png_ptr, png_ptr->row_buf + 1); + png_read_push_finish_row(png_ptr); + } +} + +void /* PRIVATE */ +png_read_push_finish_row(png_structrp png_ptr) +{ +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static PNG_CONST png_byte png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static PNG_CONST png_byte png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2}; + + /* Height of interlace block. This is not currently used - if you need + * it, uncomment it here and in png.h + static PNG_CONST png_byte png_pass_height[] = {8, 8, 4, 4, 2, 2, 1}; + */ +#endif + + png_ptr->row_number++; + if (png_ptr->row_number < png_ptr->num_rows) + return; + +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced) + { + png_ptr->row_number = 0; + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + do + { + png_ptr->pass++; + if ((png_ptr->pass == 1 && png_ptr->width < 5) || + (png_ptr->pass == 3 && png_ptr->width < 3) || + (png_ptr->pass == 5 && png_ptr->width < 2)) + png_ptr->pass++; + + if (png_ptr->pass > 7) + png_ptr->pass--; + + if (png_ptr->pass >= 7) + break; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + if (png_ptr->transformations & PNG_INTERLACE) + break; + + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + + } while (png_ptr->iwidth == 0 || png_ptr->num_rows == 0); + } +#endif /* PNG_READ_INTERLACING_SUPPORTED */ +} + +void /* PRIVATE */ +png_push_have_info(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr->info_fn != NULL) + (*(png_ptr->info_fn))(png_ptr, info_ptr); +} + +void /* PRIVATE */ +png_push_have_end(png_structrp png_ptr, png_inforp info_ptr) +{ + if (png_ptr->end_fn != NULL) + (*(png_ptr->end_fn))(png_ptr, info_ptr); +} + +void /* PRIVATE */ +png_push_have_row(png_structrp png_ptr, png_bytep row) +{ + if (png_ptr->row_fn != NULL) + (*(png_ptr->row_fn))(png_ptr, row, png_ptr->row_number, + (int)png_ptr->pass); +} + +#ifdef PNG_READ_INTERLACING_SUPPORTED +void PNGAPI +png_progressive_combine_row(png_const_structrp png_ptr, png_bytep old_row, + png_const_bytep new_row) +{ + if (png_ptr == NULL) + return; + + /* new_row is a flag here - if it is NULL then the app callback was called + * from an empty row (see the calls to png_struct::row_fn below), otherwise + * it must be png_ptr->row_buf+1 + */ + if (new_row != NULL) + png_combine_row(png_ptr, old_row, 1/*display*/); +} +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + +void PNGAPI +png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr, + png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn, + png_progressive_end_ptr end_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->info_fn = info_fn; + png_ptr->row_fn = row_fn; + png_ptr->end_fn = end_fn; + + png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer); +} + +png_voidp PNGAPI +png_get_progressive_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return png_ptr->io_ptr; +} +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpriv.h b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpriv.h new file mode 100644 index 0000000..a93a877 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngpriv.h @@ -0,0 +1,1904 @@ + +/* pngpriv.h - private declarations for use inside libpng + * + * For conditions of distribution and use, see copyright notice in png.h + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +/* The symbols declared in this file (including the functions declared + * as extern) are PRIVATE. They are not part of the libpng public + * interface, and are not recommended for use by regular applications. + * Some of them may become public in the future; others may stay private, + * change in an incompatible way, or even disappear. + * Although the libpng users are not forbidden to include this header, + * they should be well aware of the issues that may arise from doing so. + */ + +#ifndef PNGPRIV_H +#define PNGPRIV_H + +/* Feature Test Macros. The following are defined here to ensure that correctly + * implemented libraries reveal the APIs libpng needs to build and hide those + * that are not needed and potentially damaging to the compilation. + * + * Feature Test Macros must be defined before any system header is included (see + * POSIX 1003.1 2.8.2 "POSIX Symbols." + * + * These macros only have an effect if the operating system supports either + * POSIX 1003.1 or C99, or both. On other operating systems (particularly + * Windows/Visual Studio) there is no effect; the OS specific tests below are + * still required (as of 2011-05-02.) + */ +#define _POSIX_SOURCE 1 /* Just the POSIX 1003.1 and C89 APIs */ + +#ifndef PNG_VERSION_INFO_ONLY +/* Standard library headers not required by png.h: */ +# include +# include +#endif + +#define PNGLIB_BUILD /*libpng is being built, not used*/ + +/* If HAVE_CONFIG_H is defined during the build then the build system must + * provide an appropriate "config.h" file on the include path. The header file + * must provide definitions as required below (search for "HAVE_CONFIG_H"); + * see configure.ac for more details of the requirements. The macro + * "PNG_NO_CONFIG_H" is provided for maintainers to test for dependencies on + * 'configure'; define this macro to prevent the configure build including the + * configure generated config.h. Libpng is expected to compile without *any* + * special build system support on a reasonably ANSI-C compliant system. + */ +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include + + /* Pick up the definition of 'restrict' from config.h if it was read: */ +# define PNG_RESTRICT restrict +#endif + +/* To support symbol prefixing it is necessary to know *before* including png.h + * whether the fixed point (and maybe other) APIs are exported, because if they + * are not internal definitions may be required. This is handled below just + * before png.h is included, but load the configuration now if it is available. + */ +#ifndef PNGLCONF_H +# include "pnglibconf.h" +#endif + +/* Local renames may change non-exported API functions from png.h */ +#if defined(PNG_PREFIX) && !defined(PNGPREFIX_H) +# include "pngprefix.h" +#endif + +#ifdef PNG_USER_CONFIG +# include "pngusr.h" + /* These should have been defined in pngusr.h */ +# ifndef PNG_USER_PRIVATEBUILD +# define PNG_USER_PRIVATEBUILD "Custom libpng build" +# endif +# ifndef PNG_USER_DLLFNAME_POSTFIX +# define PNG_USER_DLLFNAME_POSTFIX "Cb" +# endif +#endif + +/* Is this a build of a DLL where compilation of the object modules requires + * different preprocessor settings to those required for a simple library? If + * so PNG_BUILD_DLL must be set. + * + * If libpng is used inside a DLL but that DLL does not export the libpng APIs + * PNG_BUILD_DLL must not be set. To avoid the code below kicking in build a + * static library of libpng then link the DLL against that. + */ +#ifndef PNG_BUILD_DLL +# ifdef DLL_EXPORT + /* This is set by libtool when files are compiled for a DLL; libtool + * always compiles twice, even on systems where it isn't necessary. Set + * PNG_BUILD_DLL in case it is necessary: + */ +# define PNG_BUILD_DLL +# else +# ifdef _WINDLL + /* This is set by the Microsoft Visual Studio IDE in projects that + * build a DLL. It can't easily be removed from those projects (it + * isn't visible in the Visual Studio UI) so it is a fairly reliable + * indication that PNG_IMPEXP needs to be set to the DLL export + * attributes. + */ +# define PNG_BUILD_DLL +# else +# ifdef __DLL__ + /* This is set by the Borland C system when compiling for a DLL + * (as above.) + */ +# define PNG_BUILD_DLL +# else + /* Add additional compiler cases here. */ +# endif +# endif +# endif +#endif /* Setting PNG_BUILD_DLL if required */ + +/* See pngconf.h for more details: the builder of the library may set this on + * the command line to the right thing for the specific compilation system or it + * may be automagically set above (at present we know of no system where it does + * need to be set on the command line.) + * + * PNG_IMPEXP must be set here when building the library to prevent pngconf.h + * setting it to the "import" setting for a DLL build. + */ +#ifndef PNG_IMPEXP +# ifdef PNG_BUILD_DLL +# define PNG_IMPEXP PNG_DLL_EXPORT +# else + /* Not building a DLL, or the DLL doesn't require specific export + * definitions. + */ +# define PNG_IMPEXP +# endif +#endif + +/* No warnings for private or deprecated functions in the build: */ +#ifndef PNG_DEPRECATED +# define PNG_DEPRECATED +#endif +#ifndef PNG_PRIVATE +# define PNG_PRIVATE +#endif + +/* Symbol preprocessing support. + * + * To enable listing global, but internal, symbols the following macros should + * always be used to declare an extern data or function object in this file. + */ +#ifndef PNG_INTERNAL_DATA +# define PNG_INTERNAL_DATA(type, name, array) extern type name array +#endif + +#ifndef PNG_INTERNAL_FUNCTION +# define PNG_INTERNAL_FUNCTION(type, name, args, attributes)\ + extern PNG_FUNCTION(type, name, args, PNG_EMPTY attributes) +#endif + +/* If floating or fixed point APIs are disabled they may still be compiled + * internally. To handle this make sure they are declared as the appropriate + * internal extern function (otherwise the symbol prefixing stuff won't work and + * the functions will be used without definitions.) + * + * NOTE: although all the API functions are declared here they are not all + * actually built! Because the declarations are still made it is necessary to + * fake out types that they depend on. + */ +#ifndef PNG_FP_EXPORT +# ifndef PNG_FLOATING_POINT_SUPPORTED +# define PNG_FP_EXPORT(ordinal, type, name, args)\ + PNG_INTERNAL_FUNCTION(type, name, args, PNG_EMPTY); +# ifndef PNG_VERSION_INFO_ONLY + typedef struct png_incomplete png_double; + typedef png_double* png_doublep; + typedef const png_double* png_const_doublep; + typedef png_double** png_doublepp; +# endif +# endif +#endif +#ifndef PNG_FIXED_EXPORT +# ifndef PNG_FIXED_POINT_SUPPORTED +# define PNG_FIXED_EXPORT(ordinal, type, name, args)\ + PNG_INTERNAL_FUNCTION(type, name, args, PNG_EMPTY); +# endif +#endif + +#include "png.h" + +/* pngconf.h does not set PNG_DLL_EXPORT unless it is required, so: */ +#ifndef PNG_DLL_EXPORT +# define PNG_DLL_EXPORT +#endif + +/* SECURITY and SAFETY: + * + * By default libpng is built without any internal limits on image size, + * individual heap (png_malloc) allocations or the total amount of memory used. + * If PNG_SAFE_LIMITS_SUPPORTED is defined, however, the limits below are used + * (unless individually overridden). These limits are believed to be fairly + * safe, but builders of secure systems should verify the values against the + * real system capabilities. + */ +#ifdef PNG_SAFE_LIMITS_SUPPORTED + /* 'safe' limits */ +# ifndef PNG_USER_WIDTH_MAX +# define PNG_USER_WIDTH_MAX 1000000 +# endif +# ifndef PNG_USER_HEIGHT_MAX +# define PNG_USER_HEIGHT_MAX 1000000 +# endif +# ifndef PNG_USER_CHUNK_CACHE_MAX +# define PNG_USER_CHUNK_CACHE_MAX 128 +# endif +# ifndef PNG_USER_CHUNK_MALLOC_MAX +# define PNG_USER_CHUNK_MALLOC_MAX 8000000 +# endif +#else + /* values for no limits */ +# ifndef PNG_USER_WIDTH_MAX +# define PNG_USER_WIDTH_MAX 0x7fffffff +# endif +# ifndef PNG_USER_HEIGHT_MAX +# define PNG_USER_HEIGHT_MAX 0x7fffffff +# endif +# ifndef PNG_USER_CHUNK_CACHE_MAX +# define PNG_USER_CHUNK_CACHE_MAX 0 +# endif +# ifndef PNG_USER_CHUNK_MALLOC_MAX +# define PNG_USER_CHUNK_MALLOC_MAX 0 +# endif +#endif + +/* Moved to pngpriv.h at libpng-1.5.0 */ +/* NOTE: some of these may have been used in external applications as + * these definitions were exposed in pngconf.h prior to 1.5. + */ + +/* If you are running on a machine where you cannot allocate more + * than 64K of memory at once, uncomment this. While libpng will not + * normally need that much memory in a chunk (unless you load up a very + * large file), zlib needs to know how big of a chunk it can use, and + * libpng thus makes sure to check any memory allocation to verify it + * will fit into memory. + * + * zlib provides 'MAXSEG_64K' which, if defined, indicates the + * same limit and pngconf.h (already included) sets the limit + * if certain operating systems are detected. + */ +#if defined(MAXSEG_64K) && !defined(PNG_MAX_MALLOC_64K) +# define PNG_MAX_MALLOC_64K +#endif + +#ifndef PNG_UNUSED +/* Unused formal parameter warnings are silenced using the following macro + * which is expected to have no bad effects on performance (optimizing + * compilers will probably remove it entirely). Note that if you replace + * it with something other than whitespace, you must include the terminating + * semicolon. + */ +# define PNG_UNUSED(param) (void)param; +#endif + +/* Just a little check that someone hasn't tried to define something + * contradictory. + */ +#if (PNG_ZBUF_SIZE > 65536L) && defined(PNG_MAX_MALLOC_64K) +# undef PNG_ZBUF_SIZE +# define PNG_ZBUF_SIZE 65536L +#endif + +/* If warnings or errors are turned off the code is disabled or redirected here. + * From 1.5.4 functions have been added to allow very limited formatting of + * error and warning messages - this code will also be disabled here. + */ +#ifdef PNG_WARNINGS_SUPPORTED +# define PNG_WARNING_PARAMETERS(p) png_warning_parameters p; +#else +# define png_warning(s1,s2) ((void)(s1)) +# define png_chunk_warning(s1,s2) ((void)(s1)) +# define png_warning_parameter(p,number,string) ((void)0) +# define png_warning_parameter_unsigned(p,number,format,value) ((void)0) +# define png_warning_parameter_signed(p,number,format,value) ((void)0) +# define png_formatted_warning(pp,p,message) ((void)(pp)) +# define PNG_WARNING_PARAMETERS(p) +#endif +#ifndef PNG_ERROR_TEXT_SUPPORTED +# define png_error(s1,s2) png_err(s1) +# define png_chunk_error(s1,s2) png_err(s1) +# define png_fixed_error(s1,s2) png_err(s1) +#endif + +/* C allows up-casts from (void*) to any pointer and (const void*) to any + * pointer to a const object. C++ regards this as a type error and requires an + * explicit, static, cast and provides the static_cast<> rune to ensure that + * const is not cast away. + */ +#ifdef __cplusplus +# define png_voidcast(type, value) static_cast(value) +# define png_constcast(type, value) const_cast(value) +# define png_aligncast(type, value) \ + static_cast(static_cast(value)) +# define png_aligncastconst(type, value) \ + static_cast(static_cast(value)) +#else +# define png_voidcast(type, value) (value) +# define png_constcast(type, value) ((type)(value)) +# define png_aligncast(type, value) ((void*)(value)) +# define png_aligncastconst(type, value) ((const void*)(value)) +#endif /* __cplusplus */ + +/* Some fixed point APIs are still required even if not exported because + * they get used by the corresponding floating point APIs. This magic + * deals with this: + */ +#ifdef PNG_FIXED_POINT_SUPPORTED +# define PNGFAPI PNGAPI +#else +# define PNGFAPI /* PRIVATE */ +#endif + +#ifndef PNG_VERSION_INFO_ONLY +/* Other defines specific to compilers can go here. Try to keep + * them inside an appropriate ifdef/endif pair for portability. + */ +#if defined(PNG_FLOATING_POINT_SUPPORTED) ||\ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) + /* png.c requires the following ANSI-C constants if the conversion of + * floating point to ASCII is implemented therein: + * + * DBL_DIG Maximum number of decimal digits (can be set to any constant) + * DBL_MIN Smallest normalized fp number (can be set to an arbitrary value) + * DBL_MAX Maximum floating point number (can be set to an arbitrary value) + */ +# include + +# if (defined(__MWERKS__) && defined(macintosh)) || defined(applec) || \ + defined(THINK_C) || defined(__SC__) || defined(TARGET_OS_MAC) + /* We need to check that hasn't already been included earlier + * as it seems it doesn't agree with , yet we should really use + * if possible. + */ +# if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__) +# include +# endif +# else +# include +# endif +# if defined(_AMIGA) && defined(__SASC) && defined(_M68881) + /* Amiga SAS/C: We must include builtin FPU functions when compiling using + * MATH=68881 + */ +# include +# endif +#endif + +/* This provides the non-ANSI (far) memory allocation routines. */ +#if defined(__TURBOC__) && defined(__MSDOS__) +# include +# include +#endif + +#if defined(WIN32) || defined(_Windows) || defined(_WINDOWS) || \ + defined(_WIN32) || defined(__WIN32__) +# include /* defines _WINDOWS_ macro */ +#endif +#endif /* PNG_VERSION_INFO_ONLY */ + +/* Moved here around 1.5.0beta36 from pngconf.h */ +/* Users may want to use these so they are not private. Any library + * functions that are passed far data must be model-independent. + */ + +/* Memory model/platform independent fns */ +#ifndef PNG_ABORT +# ifdef _WINDOWS_ +# define PNG_ABORT() ExitProcess(0) +# else +# define PNG_ABORT() abort() +# endif +#endif + +/* These macros may need to be architecture dependent. */ +#define PNG_ALIGN_NONE 0 /* do not use data alignment */ +#define PNG_ALIGN_ALWAYS 1 /* assume unaligned accesses are OK */ +#ifdef offsetof +# define PNG_ALIGN_OFFSET 2 /* use offsetof to determine alignment */ +#else +# define PNG_ALIGN_OFFSET -1 /* prevent the use of this */ +#endif +#define PNG_ALIGN_SIZE 3 /* use sizeof to determine alignment */ + +#ifndef PNG_ALIGN_TYPE + /* Default to using aligned access optimizations and requiring alignment to a + * multiple of the data type size. Override in a compiler specific fashion + * if necessary by inserting tests here: + */ +# define PNG_ALIGN_TYPE PNG_ALIGN_SIZE +#endif + +#if PNG_ALIGN_TYPE == PNG_ALIGN_SIZE + /* This is used because in some compiler implementations non-aligned + * structure members are supported, so the offsetof approach below fails. + * Set PNG_ALIGN_SIZE=0 for compiler combinations where unaligned access + * is good for performance. Do not do this unless you have tested the result + * and understand it. + */ +# define png_alignof(type) (sizeof (type)) +#else +# if PNG_ALIGN_TYPE == PNG_ALIGN_OFFSET +# define png_alignof(type) offsetof(struct{char c; type t;}, t) +# else +# if PNG_ALIGN_TYPE == PNG_ALIGN_ALWAYS +# define png_alignof(type) (1) +# endif + /* Else leave png_alignof undefined to prevent use thereof */ +# endif +#endif + +/* This implicitly assumes alignment is always to a power of 2. */ +#ifdef png_alignof +# define png_isaligned(ptr, type)\ + ((((const char*)ptr-(const char*)0) & (png_alignof(type)-1)) == 0) +#else +# define png_isaligned(ptr, type) 0 +#endif + +/* End of memory model/platform independent support */ +/* End of 1.5.0beta36 move from pngconf.h */ + +/* CONSTANTS and UTILITY MACROS + * These are used internally by libpng and not exposed in the API + */ + +/* Various modes of operation. Note that after an init, mode is set to + * zero automatically when the structure is created. Three of these + * are defined in png.h because they need to be visible to applications + * that call png_set_unknown_chunk(). + */ +/* #define PNG_HAVE_IHDR 0x01 (defined in png.h) */ +/* #define PNG_HAVE_PLTE 0x02 (defined in png.h) */ +#define PNG_HAVE_IDAT 0x04 +/* #define PNG_AFTER_IDAT 0x08 (defined in png.h) */ +#define PNG_HAVE_IEND 0x10 + /* 0x20 (unused) */ + /* 0x40 (unused) */ + /* 0x80 (unused) */ +#define PNG_HAVE_CHUNK_HEADER 0x100 +#define PNG_WROTE_tIME 0x200 +#define PNG_WROTE_INFO_BEFORE_PLTE 0x400 +#define PNG_BACKGROUND_IS_GRAY 0x800 +#define PNG_HAVE_PNG_SIGNATURE 0x1000 +#define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000 /* Have another chunk after IDAT */ + /* 0x4000 (unused) */ +#define PNG_IS_READ_STRUCT 0x8000 /* Else is a write struct */ + +/* Flags for the transformations the PNG library does on the image data */ +#define PNG_BGR 0x0001 +#define PNG_INTERLACE 0x0002 +#define PNG_PACK 0x0004 +#define PNG_SHIFT 0x0008 +#define PNG_SWAP_BYTES 0x0010 +#define PNG_INVERT_MONO 0x0020 +#define PNG_QUANTIZE 0x0040 +#define PNG_COMPOSE 0x0080 /* Was PNG_BACKGROUND */ +#define PNG_BACKGROUND_EXPAND 0x0100 +#define PNG_EXPAND_16 0x0200 /* Added to libpng 1.5.2 */ +#define PNG_16_TO_8 0x0400 /* Becomes 'chop' in 1.5.4 */ +#define PNG_RGBA 0x0800 +#define PNG_EXPAND 0x1000 +#define PNG_GAMMA 0x2000 +#define PNG_GRAY_TO_RGB 0x4000 +#define PNG_FILLER 0x8000 +#define PNG_PACKSWAP 0x10000 +#define PNG_SWAP_ALPHA 0x20000 +#define PNG_STRIP_ALPHA 0x40000 +#define PNG_INVERT_ALPHA 0x80000 +#define PNG_USER_TRANSFORM 0x100000 +#define PNG_RGB_TO_GRAY_ERR 0x200000 +#define PNG_RGB_TO_GRAY_WARN 0x400000 +#define PNG_RGB_TO_GRAY 0x600000 /* two bits, RGB_TO_GRAY_ERR|WARN */ +#define PNG_ENCODE_ALPHA 0x800000 /* Added to libpng-1.5.4 */ +#define PNG_ADD_ALPHA 0x1000000 /* Added to libpng-1.2.7 */ +#define PNG_EXPAND_tRNS 0x2000000 /* Added to libpng-1.2.9 */ +#define PNG_SCALE_16_TO_8 0x4000000 /* Added to libpng-1.5.4 */ + /* 0x8000000 unused */ + /* 0x10000000 unused */ + /* 0x20000000 unused */ + /* 0x40000000 unused */ +/* Flags for png_create_struct */ +#define PNG_STRUCT_PNG 0x0001 +#define PNG_STRUCT_INFO 0x0002 + +/* Scaling factor for filter heuristic weighting calculations */ +#define PNG_WEIGHT_FACTOR (1<<(PNG_WEIGHT_SHIFT)) +#define PNG_COST_FACTOR (1<<(PNG_COST_SHIFT)) + +/* Flags for the png_ptr->flags rather than declaring a byte for each one */ +#define PNG_FLAG_ZLIB_CUSTOM_STRATEGY 0x0001 +#define PNG_FLAG_ZSTREAM_INITIALIZED 0x0002 /* Added to libpng-1.6.0 */ + /* 0x0004 unused */ +#define PNG_FLAG_ZSTREAM_ENDED 0x0008 /* Added to libpng-1.6.0 */ + /* 0x0010 unused */ + /* 0x0020 unused */ +#define PNG_FLAG_ROW_INIT 0x0040 +#define PNG_FLAG_FILLER_AFTER 0x0080 +#define PNG_FLAG_CRC_ANCILLARY_USE 0x0100 +#define PNG_FLAG_CRC_ANCILLARY_NOWARN 0x0200 +#define PNG_FLAG_CRC_CRITICAL_USE 0x0400 +#define PNG_FLAG_CRC_CRITICAL_IGNORE 0x0800 +#define PNG_FLAG_ASSUME_sRGB 0x1000 /* Added to libpng-1.5.4 */ +#define PNG_FLAG_OPTIMIZE_ALPHA 0x2000 /* Added to libpng-1.5.4 */ +#define PNG_FLAG_DETECT_UNINITIALIZED 0x4000 /* Added to libpng-1.5.4 */ +/* #define PNG_FLAG_KEEP_UNKNOWN_CHUNKS 0x8000 */ +/* #define PNG_FLAG_KEEP_UNSAFE_CHUNKS 0x10000 */ +#define PNG_FLAG_LIBRARY_MISMATCH 0x20000 +#define PNG_FLAG_STRIP_ERROR_NUMBERS 0x40000 +#define PNG_FLAG_STRIP_ERROR_TEXT 0x80000 +#define PNG_FLAG_BENIGN_ERRORS_WARN 0x100000 /* Added to libpng-1.4.0 */ +#define PNG_FLAG_APP_WARNINGS_WARN 0x200000 /* Added to libpng-1.6.0 */ +#define PNG_FLAG_APP_ERRORS_WARN 0x400000 /* Added to libpng-1.6.0 */ + /* 0x800000 unused */ + /* 0x1000000 unused */ + /* 0x2000000 unused */ + /* 0x4000000 unused */ + /* 0x8000000 unused */ + /* 0x10000000 unused */ + /* 0x20000000 unused */ + /* 0x40000000 unused */ + +#define PNG_FLAG_CRC_ANCILLARY_MASK (PNG_FLAG_CRC_ANCILLARY_USE | \ + PNG_FLAG_CRC_ANCILLARY_NOWARN) + +#define PNG_FLAG_CRC_CRITICAL_MASK (PNG_FLAG_CRC_CRITICAL_USE | \ + PNG_FLAG_CRC_CRITICAL_IGNORE) + +#define PNG_FLAG_CRC_MASK (PNG_FLAG_CRC_ANCILLARY_MASK | \ + PNG_FLAG_CRC_CRITICAL_MASK) + +/* Save typing and make code easier to understand */ + +#define PNG_COLOR_DIST(c1, c2) (abs((int)((c1).red) - (int)((c2).red)) + \ + abs((int)((c1).green) - (int)((c2).green)) + \ + abs((int)((c1).blue) - (int)((c2).blue))) + +/* Added to libpng-1.6.0: scale a 16-bit value in the range 0..65535 to 0..255 + * by dividing by 257 *with rounding*. This macro is exact for the given range. + * See the discourse in pngrtran.c png_do_scale_16_to_8. The values in the + * macro were established by experiment (modifying the added value). The macro + * has a second variant that takes a value already scaled by 255 and divides by + * 65535 - this has a maximum error of .502. Over the range 0..65535*65535 it + * only gives off-by-one errors and only for 0.5% (1 in 200) of the values. + */ +#define PNG_DIV65535(v24) (((v24) + 32895) >> 16) +#define PNG_DIV257(v16) PNG_DIV65535((png_uint_32)(v16) * 255) + +/* Added to libpng-1.2.6 JB */ +#define PNG_ROWBYTES(pixel_bits, width) \ + ((pixel_bits) >= 8 ? \ + ((png_size_t)(width) * (((png_size_t)(pixel_bits)) >> 3)) : \ + (( ((png_size_t)(width) * ((png_size_t)(pixel_bits))) + 7) >> 3) ) + +/* PNG_OUT_OF_RANGE returns true if value is outside the range + * ideal-delta..ideal+delta. Each argument is evaluated twice. + * "ideal" and "delta" should be constants, normally simple + * integers, "value" a variable. Added to libpng-1.2.6 JB + */ +#define PNG_OUT_OF_RANGE(value, ideal, delta) \ + ( (value) < (ideal)-(delta) || (value) > (ideal)+(delta) ) + +/* Conversions between fixed and floating point, only defined if + * required (to make sure the code doesn't accidentally use float + * when it is supposedly disabled.) + */ +#ifdef PNG_FLOATING_POINT_SUPPORTED +/* The floating point conversion can't overflow, though it can and + * does lose accuracy relative to the original fixed point value. + * In practice this doesn't matter because png_fixed_point only + * stores numbers with very low precision. The png_ptr and s + * arguments are unused by default but are there in case error + * checking becomes a requirement. + */ +#define png_float(png_ptr, fixed, s) (.00001 * (fixed)) + +/* The fixed point conversion performs range checking and evaluates + * its argument multiple times, so must be used with care. The + * range checking uses the PNG specification values for a signed + * 32 bit fixed point value except that the values are deliberately + * rounded-to-zero to an integral value - 21474 (21474.83 is roughly + * (2^31-1) * 100000). 's' is a string that describes the value being + * converted. + * + * NOTE: this macro will raise a png_error if the range check fails, + * therefore it is normally only appropriate to use this on values + * that come from API calls or other sources where an out of range + * error indicates a programming error, not a data error! + * + * NOTE: by default this is off - the macro is not used - because the + * function call saves a lot of code. + */ +#ifdef PNG_FIXED_POINT_MACRO_SUPPORTED +#define png_fixed(png_ptr, fp, s) ((fp) <= 21474 && (fp) >= -21474 ?\ + ((png_fixed_point)(100000 * (fp))) : (png_fixed_error(png_ptr, s),0)) +#endif +/* else the corresponding function is defined below, inside the scope of the + * cplusplus test. + */ +#endif + +/* Constants for known chunk types. If you need to add a chunk, define the name + * here. For historical reasons these constants have the form png_; i.e. + * the prefix is lower case. Please use decimal values as the parameters to + * match the ISO PNG specification and to avoid relying on the C locale + * interpretation of character values. + * + * Prior to 1.5.6 these constants were strings, as of 1.5.6 png_uint_32 values + * are computed and a new macro (PNG_STRING_FROM_CHUNK) added to allow a string + * to be generated if required. + * + * PNG_32b correctly produces a value shifted by up to 24 bits, even on + * architectures where (int) is only 16 bits. + */ +#define PNG_32b(b,s) ((png_uint_32)(b) << (s)) +#define PNG_CHUNK(b1,b2,b3,b4) \ + (PNG_32b(b1,24) | PNG_32b(b2,16) | PNG_32b(b3,8) | PNG_32b(b4,0)) + +#define png_IHDR PNG_CHUNK( 73, 72, 68, 82) +#define png_IDAT PNG_CHUNK( 73, 68, 65, 84) +#define png_IEND PNG_CHUNK( 73, 69, 78, 68) +#define png_PLTE PNG_CHUNK( 80, 76, 84, 69) +#define png_bKGD PNG_CHUNK( 98, 75, 71, 68) +#define png_cHRM PNG_CHUNK( 99, 72, 82, 77) +#define png_gAMA PNG_CHUNK(103, 65, 77, 65) +#define png_hIST PNG_CHUNK(104, 73, 83, 84) +#define png_iCCP PNG_CHUNK(105, 67, 67, 80) +#define png_iTXt PNG_CHUNK(105, 84, 88, 116) +#define png_oFFs PNG_CHUNK(111, 70, 70, 115) +#define png_pCAL PNG_CHUNK(112, 67, 65, 76) +#define png_sCAL PNG_CHUNK(115, 67, 65, 76) +#define png_pHYs PNG_CHUNK(112, 72, 89, 115) +#define png_sBIT PNG_CHUNK(115, 66, 73, 84) +#define png_sPLT PNG_CHUNK(115, 80, 76, 84) +#define png_sRGB PNG_CHUNK(115, 82, 71, 66) +#define png_sTER PNG_CHUNK(115, 84, 69, 82) +#define png_tEXt PNG_CHUNK(116, 69, 88, 116) +#define png_tIME PNG_CHUNK(116, 73, 77, 69) +#define png_tRNS PNG_CHUNK(116, 82, 78, 83) +#define png_zTXt PNG_CHUNK(122, 84, 88, 116) + +/* The following will work on (signed char*) strings, whereas the get_uint_32 + * macro will fail on top-bit-set values because of the sign extension. + */ +#define PNG_CHUNK_FROM_STRING(s)\ + PNG_CHUNK(0xff&(s)[0], 0xff&(s)[1], 0xff&(s)[2], 0xff&(s)[3]) + +/* This uses (char), not (png_byte) to avoid warnings on systems where (char) is + * signed and the argument is a (char[]) This macro will fail miserably on + * systems where (char) is more than 8 bits. + */ +#define PNG_STRING_FROM_CHUNK(s,c)\ + (void)(((char*)(s))[0]=(char)((c)>>24), ((char*)(s))[1]=(char)((c)>>16),\ + ((char*)(s))[2]=(char)((c)>>8), ((char*)(s))[3]=(char)((c))) + +/* Do the same but terminate with a null character. */ +#define PNG_CSTRING_FROM_CHUNK(s,c)\ + (void)(PNG_STRING_FROM_CHUNK(s,c), ((char*)(s))[4] = 0) + +/* Test on flag values as defined in the spec (section 5.4): */ +#define PNG_CHUNK_ANCILLIARY(c) (1 & ((c) >> 29)) +#define PNG_CHUNK_CRITICAL(c) (!PNG_CHUNK_ANCILLIARY(c)) +#define PNG_CHUNK_PRIVATE(c) (1 & ((c) >> 21)) +#define PNG_CHUNK_RESERVED(c) (1 & ((c) >> 13)) +#define PNG_CHUNK_SAFE_TO_COPY(c) (1 & ((c) >> 5)) + +/* Gamma values (new at libpng-1.5.4): */ +#define PNG_GAMMA_MAC_OLD 151724 /* Assume '1.8' is really 2.2/1.45! */ +#define PNG_GAMMA_MAC_INVERSE 65909 +#define PNG_GAMMA_sRGB_INVERSE 45455 + +/* Almost everything below is C specific; the #defines above can be used in + * non-C code (so long as it is C-preprocessed) the rest of this stuff cannot. + */ +#ifndef PNG_VERSION_INFO_ONLY + +#include "pngstruct.h" +#include "pnginfo.h" + +/* This is used for 16 bit gamma tables -- only the top level pointers are + * const; this could be changed: + */ +typedef const png_uint_16p * png_const_uint_16pp; + +/* Added to libpng-1.5.7: sRGB conversion tables */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +PNG_INTERNAL_DATA(const png_uint_16, png_sRGB_table, [256]); + /* Convert from an sRGB encoded value 0..255 to a 16-bit linear value, + * 0..65535. This table gives the closest 16-bit answers (no errors). + */ +#endif + +PNG_INTERNAL_DATA(const png_uint_16, png_sRGB_base, [512]); +PNG_INTERNAL_DATA(const png_byte, png_sRGB_delta, [512]); + +#define PNG_sRGB_FROM_LINEAR(linear) ((png_byte)((png_sRGB_base[(linear)>>15] +\ + ((((linear)&0x7fff)*png_sRGB_delta[(linear)>>15])>>12)) >> 8)) + /* Given a value 'linear' in the range 0..255*65535 calculate the 8-bit sRGB + * encoded value with maximum error 0.646365. Note that the input is not a + * 16-bit value; it has been multiplied by 255! */ +#endif /* PNG_SIMPLIFIED_READ/WRITE */ + + +/* Internal functions; these are not exported from a DLL however because they + * are used within several of the C source files they have to be C extern. + * + * All of these functions must be declared with PNG_INTERNAL_FUNCTION. + */ + +/* Zlib support */ +#define PNG_UNEXPECTED_ZLIB_RETURN (-7) +PNG_INTERNAL_FUNCTION(void, png_zstream_error,(png_structrp png_ptr, int ret), + PNG_EMPTY); + /* Used by the zlib handling functions to ensure that z_stream::msg is always + * set before they return. + */ + +#ifdef PNG_WRITE_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_free_buffer_list,(png_structrp png_ptr, + png_compression_bufferp *list),PNG_EMPTY); + /* Free the buffer list used by the compressed write code. */ +#endif + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && \ + !defined(PNG_FIXED_POINT_MACRO_SUPPORTED) && \ + (defined(PNG_gAMA_SUPPORTED) || defined(PNG_cHRM_SUPPORTED) || \ + defined(PNG_sCAL_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED)) || \ + (defined(PNG_sCAL_SUPPORTED) && \ + defined(PNG_FLOATING_ARITHMETIC_SUPPORTED)) +PNG_INTERNAL_FUNCTION(png_fixed_point,png_fixed,(png_const_structrp png_ptr, + double fp, png_const_charp text),PNG_EMPTY); +#endif + +/* Check the user version string for compatibility, returns false if the version + * numbers aren't compatible. + */ +PNG_INTERNAL_FUNCTION(int,png_user_version_check,(png_structrp png_ptr, + png_const_charp user_png_ver),PNG_EMPTY); + +/* Internal base allocator - no messages, NULL on failure to allocate. This + * does, however, call the application provided allocator and that could call + * png_error (although that would be a bug in the application implementation.) + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_malloc_base,(png_const_structrp png_ptr, + png_alloc_size_t size),PNG_ALLOCATED); + +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_sPLT_SUPPORTED) ||\ + defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) +/* Internal array allocator, outputs no error or warning messages on failure, + * just returns NULL. + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_malloc_array,(png_const_structrp png_ptr, + int nelements, size_t element_size),PNG_ALLOCATED); + +/* The same but an existing array is extended by add_elements. This function + * also memsets the new elements to 0 and copies the old elements. The old + * array is not freed or altered. + */ +PNG_INTERNAL_FUNCTION(png_voidp,png_realloc_array,(png_const_structrp png_ptr, + png_const_voidp array, int old_elements, int add_elements, + size_t element_size),PNG_ALLOCATED); +#endif /* text, sPLT or unknown chunks */ + +/* Magic to create a struct when there is no struct to call the user supplied + * memory allocators. Because error handling has not been set up the memory + * handlers can't safely call png_error, but this is an obscure and undocumented + * restriction so libpng has to assume that the 'free' handler, at least, might + * call png_error. + */ +PNG_INTERNAL_FUNCTION(png_structp,png_create_png_struct, + (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn, + png_error_ptr warn_fn, png_voidp mem_ptr, png_malloc_ptr malloc_fn, + png_free_ptr free_fn),PNG_ALLOCATED); + +/* Free memory from internal libpng struct */ +PNG_INTERNAL_FUNCTION(void,png_destroy_png_struct,(png_structrp png_ptr), + PNG_EMPTY); + +/* Free an allocated jmp_buf (always succeeds) */ +PNG_INTERNAL_FUNCTION(void,png_free_jmpbuf,(png_structrp png_ptr),PNG_EMPTY); + +/* Function to allocate memory for zlib. PNGAPI is disallowed. */ +PNG_INTERNAL_FUNCTION(voidpf,png_zalloc,(voidpf png_ptr, uInt items, uInt size), + PNG_ALLOCATED); + +/* Function to free memory for zlib. PNGAPI is disallowed. */ +PNG_INTERNAL_FUNCTION(void,png_zfree,(voidpf png_ptr, voidpf ptr),PNG_EMPTY); + +/* Next four functions are used internally as callbacks. PNGCBAPI is required + * but not PNG_EXPORT. PNGAPI added at libpng version 1.2.3, changed to + * PNGCBAPI at 1.5.0 + */ + +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_read_data,(png_structp png_ptr, + png_bytep data, png_size_t length),PNG_EMPTY); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_push_fill_buffer,(png_structp png_ptr, + png_bytep buffer, png_size_t length),PNG_EMPTY); +#endif + +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_write_data,(png_structp png_ptr, + png_bytep data, png_size_t length),PNG_EMPTY); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_STDIO_SUPPORTED +PNG_INTERNAL_FUNCTION(void PNGCBAPI,png_default_flush,(png_structp png_ptr), + PNG_EMPTY); +# endif +#endif + +/* Reset the CRC variable */ +PNG_INTERNAL_FUNCTION(void,png_reset_crc,(png_structrp png_ptr),PNG_EMPTY); + +/* Write the "data" buffer to whatever output you are using */ +PNG_INTERNAL_FUNCTION(void,png_write_data,(png_structrp png_ptr, + png_const_bytep data, png_size_t length),PNG_EMPTY); + +/* Read and check the PNG file signature */ +PNG_INTERNAL_FUNCTION(void,png_read_sig,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); + +/* Read the chunk header (length + type name) */ +PNG_INTERNAL_FUNCTION(png_uint_32,png_read_chunk_header,(png_structrp png_ptr), + PNG_EMPTY); + +/* Read data from whatever input you are using into the "data" buffer */ +PNG_INTERNAL_FUNCTION(void,png_read_data,(png_structrp png_ptr, png_bytep data, + png_size_t length),PNG_EMPTY); + +/* Read bytes into buf, and update png_ptr->crc */ +PNG_INTERNAL_FUNCTION(void,png_crc_read,(png_structrp png_ptr, png_bytep buf, + png_uint_32 length),PNG_EMPTY); + +/* Read "skip" bytes, read the file crc, and (optionally) verify png_ptr->crc */ +PNG_INTERNAL_FUNCTION(int,png_crc_finish,(png_structrp png_ptr, + png_uint_32 skip),PNG_EMPTY); + +/* Read the CRC from the file and compare it to the libpng calculated CRC */ +PNG_INTERNAL_FUNCTION(int,png_crc_error,(png_structrp png_ptr),PNG_EMPTY); + +/* Calculate the CRC over a section of data. Note that we are only + * passing a maximum of 64K on systems that have this as a memory limit, + * since this is the maximum buffer size we can specify. + */ +PNG_INTERNAL_FUNCTION(void,png_calculate_crc,(png_structrp png_ptr, + png_const_bytep ptr, png_size_t length),PNG_EMPTY); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_flush,(png_structrp png_ptr),PNG_EMPTY); +#endif + +/* Write various chunks */ + +/* Write the IHDR chunk, and update the png_struct with the necessary + * information. + */ +PNG_INTERNAL_FUNCTION(void,png_write_IHDR,(png_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, int color_type, + int compression_method, int filter_method, int interlace_method),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_write_PLTE,(png_structrp png_ptr, + png_const_colorp palette, png_uint_32 num_pal),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_compress_IDAT,(png_structrp png_ptr, + png_const_bytep row_data, png_alloc_size_t row_data_length, int flush), + PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_write_IEND,(png_structrp png_ptr),PNG_EMPTY); + +#ifdef PNG_WRITE_gAMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_gAMA_fixed,(png_structrp png_ptr, + png_fixed_point file_gamma),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_sBIT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sBIT,(png_structrp png_ptr, + png_const_color_8p sbit, int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_cHRM_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_cHRM_fixed,(png_structrp png_ptr, + const png_xy *xy), PNG_EMPTY); + /* The xy value must have been previously validated */ +#endif + +#ifdef PNG_WRITE_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sRGB,(png_structrp png_ptr, + int intent),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_iCCP,(png_structrp png_ptr, + png_const_charp name, png_const_bytep profile), PNG_EMPTY); + /* The profile must have been previously validated for correctness, the + * length comes from the first four bytes. Only the base, deflate, + * compression is supported. + */ +#endif + +#ifdef PNG_WRITE_sPLT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sPLT,(png_structrp png_ptr, + png_const_sPLT_tp palette),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_tRNS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tRNS,(png_structrp png_ptr, + png_const_bytep trans, png_const_color_16p values, int number, + int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_bKGD_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_bKGD,(png_structrp png_ptr, + png_const_color_16p values, int color_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_hIST,(png_structrp png_ptr, + png_const_uint_16p hist, int num_hist),PNG_EMPTY); +#endif + +/* Chunks that have keywords */ +#ifdef PNG_WRITE_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tEXt,(png_structrp png_ptr, + png_const_charp key, png_const_charp text, png_size_t text_len),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_zTXt,(png_structrp png_ptr, png_const_charp + key, png_const_charp text, png_size_t text_len, int compression),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_iTXt,(png_structrp png_ptr, + int compression, png_const_charp key, png_const_charp lang, + png_const_charp lang_key, png_const_charp text),PNG_EMPTY); +#endif + +#ifdef PNG_TEXT_SUPPORTED /* Added at version 1.0.14 and 1.2.4 */ +PNG_INTERNAL_FUNCTION(int,png_set_text_2,(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_textp text_ptr, int num_text),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_oFFs,(png_structrp png_ptr, + png_int_32 x_offset, png_int_32 y_offset, int unit_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_pCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_pCAL,(png_structrp png_ptr, + png_charp purpose, png_int_32 X0, png_int_32 X1, int type, int nparams, + png_const_charp units, png_charpp params),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_pHYs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_pHYs,(png_structrp png_ptr, + png_uint_32 x_pixels_per_unit, png_uint_32 y_pixels_per_unit, + int unit_type),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_tIME_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_tIME,(png_structrp png_ptr, + png_const_timep mod_time),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_sCAL_s,(png_structrp png_ptr, + int unit, png_const_charp width, png_const_charp height),PNG_EMPTY); +#endif + +/* Called when finished processing a row of data */ +PNG_INTERNAL_FUNCTION(void,png_write_finish_row,(png_structrp png_ptr), + PNG_EMPTY); + +/* Internal use only. Called before first row of data */ +PNG_INTERNAL_FUNCTION(void,png_write_start_row,(png_structrp png_ptr), + PNG_EMPTY); + +/* Combine a row of data, dealing with alpha, etc. if requested. 'row' is an + * array of png_ptr->width pixels. If the image is not interlaced or this + * is the final pass this just does a memcpy, otherwise the "display" flag + * is used to determine whether to copy pixels that are not in the current pass. + * + * Because 'png_do_read_interlace' (below) replicates pixels this allows this + * function to achieve the documented 'blocky' appearance during interlaced read + * if display is 1 and the 'sparkle' appearance, where existing pixels in 'row' + * are not changed if they are not in the current pass, when display is 0. + * + * 'display' must be 0 or 1, otherwise the memcpy will be done regardless. + * + * The API always reads from the png_struct row buffer and always assumes that + * it is full width (png_do_read_interlace has already been called.) + * + * This function is only ever used to write to row buffers provided by the + * caller of the relevant libpng API and the row must have already been + * transformed by the read transformations. + * + * The PNG_USE_COMPILE_TIME_MASKS option causes generation of pre-computed + * bitmasks for use within the code, otherwise runtime generated masks are used. + * The default is compile time masks. + */ +#ifndef PNG_USE_COMPILE_TIME_MASKS +# define PNG_USE_COMPILE_TIME_MASKS 1 +#endif +PNG_INTERNAL_FUNCTION(void,png_combine_row,(png_const_structrp png_ptr, + png_bytep row, int display),PNG_EMPTY); + +#ifdef PNG_READ_INTERLACING_SUPPORTED +/* Expand an interlaced row: the 'row_info' describes the pass data that has + * been read in and must correspond to the pixels in 'row', the pixels are + * expanded (moved apart) in 'row' to match the final layout, when doing this + * the pixels are *replicated* to the intervening space. This is essential for + * the correct operation of png_combine_row, above. + */ +PNG_INTERNAL_FUNCTION(void,png_do_read_interlace,(png_row_infop row_info, + png_bytep row, int pass, png_uint_32 transformations),PNG_EMPTY); +#endif + +/* GRR TO DO (2.0 or whenever): simplify other internal calling interfaces */ + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED +/* Grab pixels out of a row for an interlaced pass */ +PNG_INTERNAL_FUNCTION(void,png_do_write_interlace,(png_row_infop row_info, + png_bytep row, int pass),PNG_EMPTY); +#endif + +/* Unfilter a row: check the filter value before calling this, there is no point + * calling it for PNG_FILTER_VALUE_NONE. + */ +PNG_INTERNAL_FUNCTION(void,png_read_filter_row,(png_structrp pp, png_row_infop + row_info, png_bytep row, png_const_bytep prev_row, int filter),PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_up_neon,(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_sub4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_avg4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth3_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_filter_row_paeth4_neon,(png_row_infop + row_info, png_bytep row, png_const_bytep prev_row),PNG_EMPTY); + +/* Choose the best filter to use and filter the row data */ +PNG_INTERNAL_FUNCTION(void,png_write_find_filter,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_read_IDAT_data,(png_structrp png_ptr, + png_bytep output, png_alloc_size_t avail_out),PNG_EMPTY); + /* Read 'avail_out' bytes of data from the IDAT stream. If the output buffer + * is NULL the function checks, instead, for the end of the stream. In this + * case a benign error will be issued if the stream end is not found or if + * extra data has to be consumed. + */ +PNG_INTERNAL_FUNCTION(void,png_read_finish_IDAT,(png_structrp png_ptr), + PNG_EMPTY); + /* This cleans up when the IDAT LZ stream does not end when the last image + * byte is read; there is still some pending input. + */ + +PNG_INTERNAL_FUNCTION(void,png_read_finish_row,(png_structrp png_ptr), + PNG_EMPTY); + /* Finish a row while reading, dealing with interlacing passes, etc. */ +#endif + +/* Initialize the row buffers, etc. */ +PNG_INTERNAL_FUNCTION(void,png_read_start_row,(png_structrp png_ptr),PNG_EMPTY); + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +/* Optional call to update the users info structure */ +PNG_INTERNAL_FUNCTION(void,png_read_transform_info,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +#endif + +/* These are the functions that do the transformations */ +#ifdef PNG_READ_FILLER_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_filler,(png_row_infop row_info, + png_bytep row, png_uint_32 filler, png_uint_32 flags),PNG_EMPTY); +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_swap_alpha,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_write_swap_alpha,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_invert_alpha,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_write_invert_alpha,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#if defined(PNG_WRITE_FILLER_SUPPORTED) || \ + defined(PNG_READ_STRIP_ALPHA_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_strip_channel,(png_row_infop row_info, + png_bytep row, int at_start),PNG_EMPTY); +#endif + +#ifdef PNG_16BIT_SUPPORTED +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_swap,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED) || \ + defined(PNG_WRITE_PACKSWAP_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_packswap,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_do_rgb_to_gray,(png_structrp png_ptr, + png_row_infop row_info, png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_gray_to_rgb,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_PACK_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_unpack,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_unshift,(png_row_infop row_info, + png_bytep row, png_const_color_8p sig_bits),PNG_EMPTY); +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_invert,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_scale_16_to_8,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_chop,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_quantize,(png_row_infop row_info, + png_bytep row, png_const_bytep palette_lookup, + png_const_bytep quantize_lookup),PNG_EMPTY); + +# ifdef PNG_CORRECT_PALETTE_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_correct_palette,(png_structrp png_ptr, + png_colorp palette, int num_palette),PNG_EMPTY); +# endif +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_bgr,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_PACK_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_pack,(png_row_infop row_info, + png_bytep row, png_uint_32 bit_depth),PNG_EMPTY); +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_shift,(png_row_infop row_info, + png_bytep row, png_const_color_8p bit_depth),PNG_EMPTY); +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_compose,(png_row_infop row_info, + png_bytep row, png_structrp png_ptr),PNG_EMPTY); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_gamma,(png_row_infop row_info, + png_bytep row, png_structrp png_ptr),PNG_EMPTY); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_encode_alpha,(png_row_infop row_info, + png_bytep row, png_structrp png_ptr),PNG_EMPTY); +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_expand_palette,(png_row_infop row_info, + png_bytep row, png_const_colorp palette, png_const_bytep trans, + int num_trans),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_do_expand,(png_row_infop row_info, + png_bytep row, png_const_color_16p trans_color),PNG_EMPTY); +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_expand_16,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +/* The following decodes the appropriate chunks, and does error correction, + * then calls the appropriate callback for the chunk if it is valid. + */ + +/* Decode the IHDR chunk */ +PNG_INTERNAL_FUNCTION(void,png_handle_IHDR,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_PLTE,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_IEND,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); + +#ifdef PNG_READ_bKGD_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_bKGD,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_cHRM,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_gAMA,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_hIST,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_iCCP,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif /* PNG_READ_iCCP_SUPPORTED */ + +#ifdef PNG_READ_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_iTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_oFFs,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_pCAL,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_pHYs,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sBIT,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sCAL,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sPLT,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif /* PNG_READ_sPLT_SUPPORTED */ + +#ifdef PNG_READ_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_sRGB,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tEXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tIME,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_tRNS,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_zTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +#endif + +PNG_INTERNAL_FUNCTION(void,png_check_chunk_name,(png_structrp png_ptr, + png_uint_32 chunk_name),PNG_EMPTY); + +#ifdef PNG_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_unknown,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length, int keep),PNG_EMPTY); + /* This is the function that gets called for unknown chunks. The 'keep' + * argument is either non-zero for a known chunk that has been set to be + * handled as unknown or zero for an unknown chunk. By default the function + * just skips the chunk or errors out if it is critical. + */ + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_chunk_unknown_handling, + (png_const_structrp png_ptr, png_uint_32 chunk_name),PNG_EMPTY); + /* Exactly as the API png_handle_as_unknown() except that the argument is a + * 32-bit chunk name, not a string. + */ +#endif +#endif /* PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED */ + +/* Handle the transformations for reading and writing */ +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_transformations,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); +#endif +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_write_transformations,(png_structrp png_ptr, + png_row_infop row_info),PNG_EMPTY); +#endif + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_init_read_transformations,(png_structrp png_ptr), + PNG_EMPTY); +#endif + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_read_chunk,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_sig,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_check_crc,(png_structrp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_crc_skip,(png_structrp png_ptr, + png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_crc_finish,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_save_buffer,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_restore_buffer,(png_structrp png_ptr, + png_bytep buffer, png_size_t buffer_length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_IDAT,(png_structrp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_process_IDAT_data,(png_structrp png_ptr, + png_bytep buffer, png_size_t buffer_length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_process_row,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_handle_unknown,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_info,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_end,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_have_row,(png_structrp png_ptr, + png_bytep row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_end,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_process_some_data,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_push_finish_row,(png_structrp png_ptr), + PNG_EMPTY); +# ifdef PNG_READ_tEXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_tEXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_tEXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif +# ifdef PNG_READ_zTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_zTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_zTXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif +# ifdef PNG_READ_iTXt_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_push_handle_iTXt,(png_structrp png_ptr, + png_inforp info_ptr, png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_push_read_iTXt,(png_structrp png_ptr, + png_inforp info_ptr),PNG_EMPTY); +# endif + +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ + +#ifdef PNG_MNG_FEATURES_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_do_read_intrapixel,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_do_write_intrapixel,(png_row_infop row_info, + png_bytep row),PNG_EMPTY); +#endif + +/* Added at libpng version 1.6.0 */ +#ifdef PNG_GAMMA_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_colorspace_set_gamma,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_fixed_point gAMA), PNG_EMPTY); + /* Set the colorspace gamma with a value provided by the application or by + * the gAMA chunk on read. The value will override anything set by an ICC + * profile. + */ + +PNG_INTERNAL_FUNCTION(void,png_colorspace_sync_info,(png_const_structrp png_ptr, + png_inforp info_ptr), PNG_EMPTY); + /* Synchronize the info 'valid' flags with the colorspace */ + +PNG_INTERNAL_FUNCTION(void,png_colorspace_sync,(png_const_structrp png_ptr, + png_inforp info_ptr), PNG_EMPTY); + /* Copy the png_struct colorspace to the info_struct and call the above to + * synchronize the flags. Checks for NULL info_ptr and does nothing. + */ +#endif + +/* Added at libpng version 1.4.0 */ +#ifdef PNG_COLORSPACE_SUPPORTED +/* These internal functions are for maintaining the colorspace structure within + * a png_info or png_struct (or, indeed, both). + */ +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_chromaticities, + (png_const_structrp png_ptr, png_colorspacerp colorspace, const png_xy *xy, + int preferred), PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_endpoints, + (png_const_structrp png_ptr, png_colorspacerp colorspace, const png_XYZ *XYZ, + int preferred), PNG_EMPTY); + +#ifdef PNG_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_sRGB,(png_const_structrp png_ptr, + png_colorspacerp colorspace, int intent), PNG_EMPTY); + /* This does set the colorspace gAMA and cHRM values too, but doesn't set the + * flags to write them, if it returns false there was a problem and an error + * message has already been output (but the colorspace may still need to be + * synced to record the invalid flag). + */ +#endif /* sRGB */ + +#ifdef PNG_iCCP_SUPPORTED +PNG_INTERNAL_FUNCTION(int,png_colorspace_set_ICC,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, png_const_bytep profile, int color_type), + PNG_EMPTY); + /* The 'name' is used for information only */ + +/* Routines for checking parts of an ICC profile. */ +PNG_INTERNAL_FUNCTION(int,png_icc_check_length,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length), PNG_EMPTY); +PNG_INTERNAL_FUNCTION(int,png_icc_check_header,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, + png_const_bytep profile /* first 132 bytes only */, int color_type), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(int,png_icc_check_tag_table,(png_const_structrp png_ptr, + png_colorspacerp colorspace, png_const_charp name, + png_uint_32 profile_length, + png_const_bytep profile /* header plus whole tag table */), PNG_EMPTY); +#ifdef PNG_sRGB_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_icc_set_sRGB,( + png_const_structrp png_ptr, png_colorspacerp colorspace, + png_const_bytep profile, uLong adler), PNG_EMPTY); + /* 'adler' is the Adler32 checksum of the uncompressed profile data. It may + * be zero to indicate that it is not available. It is used, if provided, + * as a fast check on the profile when checking to see if it is sRGB. + */ +#endif +#endif /* iCCP */ + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_colorspace_set_rgb_coefficients, + (png_structrp png_ptr), PNG_EMPTY); + /* Set the rgb_to_gray coefficients from the colorspace Y values */ +#endif /* READ_RGB_TO_GRAY */ +#endif /* COLORSPACE */ + +/* Added at libpng version 1.4.0 */ +PNG_INTERNAL_FUNCTION(void,png_check_IHDR,(png_const_structrp png_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type),PNG_EMPTY); + +/* Added at libpng version 1.5.10 */ +#if defined(PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED) || \ + defined(PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_do_check_palette_indexes, + (png_structrp png_ptr, png_row_infop row_info),PNG_EMPTY); +#endif + +#if defined(PNG_FLOATING_POINT_SUPPORTED) && defined(PNG_ERROR_TEXT_SUPPORTED) +PNG_INTERNAL_FUNCTION(void,png_fixed_error,(png_const_structrp png_ptr, + png_const_charp name),PNG_NORETURN); +#endif + +/* Puts 'string' into 'buffer' at buffer[pos], taking care never to overwrite + * the end. Always leaves the buffer nul terminated. Never errors out (and + * there is no error code.) + */ +PNG_INTERNAL_FUNCTION(size_t,png_safecat,(png_charp buffer, size_t bufsize, + size_t pos, png_const_charp string),PNG_EMPTY); + +/* Various internal functions to handle formatted warning messages, currently + * only implemented for warnings. + */ +#if defined(PNG_WARNINGS_SUPPORTED) || defined(PNG_TIME_RFC1123_SUPPORTED) +/* Utility to dump an unsigned value into a buffer, given a start pointer and + * and end pointer (which should point just *beyond* the end of the buffer!) + * Returns the pointer to the start of the formatted string. This utility only + * does unsigned values. + */ +PNG_INTERNAL_FUNCTION(png_charp,png_format_number,(png_const_charp start, + png_charp end, int format, png_alloc_size_t number),PNG_EMPTY); + +/* Convenience macro that takes an array: */ +#define PNG_FORMAT_NUMBER(buffer,format,number) \ + png_format_number(buffer, buffer + (sizeof buffer), format, number) + +/* Suggested size for a number buffer (enough for 64 bits and a sign!) */ +#define PNG_NUMBER_BUFFER_SIZE 24 + +/* These are the integer formats currently supported, the name is formed from + * the standard printf(3) format string. + */ +#define PNG_NUMBER_FORMAT_u 1 /* chose unsigned API! */ +#define PNG_NUMBER_FORMAT_02u 2 +#define PNG_NUMBER_FORMAT_d 1 /* chose signed API! */ +#define PNG_NUMBER_FORMAT_02d 2 +#define PNG_NUMBER_FORMAT_x 3 +#define PNG_NUMBER_FORMAT_02x 4 +#define PNG_NUMBER_FORMAT_fixed 5 /* choose the signed API */ +#endif + +#ifdef PNG_WARNINGS_SUPPORTED +/* New defines and members adding in libpng-1.5.4 */ +# define PNG_WARNING_PARAMETER_SIZE 32 +# define PNG_WARNING_PARAMETER_COUNT 8 /* Maximum 9; see pngerror.c */ + +/* An l-value of this type has to be passed to the APIs below to cache the + * values of the parameters to a formatted warning message. + */ +typedef char png_warning_parameters[PNG_WARNING_PARAMETER_COUNT][ + PNG_WARNING_PARAMETER_SIZE]; + +PNG_INTERNAL_FUNCTION(void,png_warning_parameter,(png_warning_parameters p, + int number, png_const_charp string),PNG_EMPTY); + /* Parameters are limited in size to PNG_WARNING_PARAMETER_SIZE characters, + * including the trailing '\0'. + */ +PNG_INTERNAL_FUNCTION(void,png_warning_parameter_unsigned, + (png_warning_parameters p, int number, int format, png_alloc_size_t value), + PNG_EMPTY); + /* Use png_alloc_size_t because it is an unsigned type as big as any we + * need to output. Use the following for a signed value. + */ +PNG_INTERNAL_FUNCTION(void,png_warning_parameter_signed, + (png_warning_parameters p, int number, int format, png_int_32 value), + PNG_EMPTY); + +PNG_INTERNAL_FUNCTION(void,png_formatted_warning,(png_const_structrp png_ptr, + png_warning_parameters p, png_const_charp message),PNG_EMPTY); + /* 'message' follows the X/Open approach of using @1, @2 to insert + * parameters previously supplied using the above functions. Errors in + * specifying the parameters will simply result in garbage substitutions. + */ +#endif + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +/* Application errors (new in 1.6); use these functions (declared below) for + * errors in the parameters or order of API function calls on read. The + * 'warning' should be used for an error that can be handled completely; the + * 'error' for one which can be handled safely but which may lose application + * information or settings. + * + * By default these both result in a png_error call prior to release, while in a + * released version the 'warning' is just a warning. However if the application + * explicitly disables benign errors (explicitly permitting the code to lose + * information) they both turn into warnings. + * + * If benign errors aren't supported they end up as the corresponding base call + * (png_warning or png_error.) + */ +PNG_INTERNAL_FUNCTION(void,png_app_warning,(png_const_structrp png_ptr, + png_const_charp message),PNG_EMPTY); + /* The application provided invalid parameters to an API function or called + * an API function at the wrong time, libpng can completely recover. + */ + +PNG_INTERNAL_FUNCTION(void,png_app_error,(png_const_structrp png_ptr, + png_const_charp message),PNG_EMPTY); + /* As above but libpng will ignore the call, or attempt some other partial + * recovery from the error. + */ +#else +# define png_app_warning(pp,s) png_warning(pp,s) +# define png_app_error(pp,s) png_error(pp,s) +#endif + +PNG_INTERNAL_FUNCTION(void,png_chunk_report,(png_const_structrp png_ptr, + png_const_charp message, int error),PNG_EMPTY); + /* Report a recoverable issue in chunk data. On read this is used to report + * a problem found while reading a particular chunk and the + * png_chunk_benign_error or png_chunk_warning function is used as + * appropriate. On write this is used to report an error that comes from + * data set via an application call to a png_set_ API and png_app_error or + * png_app_warning is used as appropriate. + * + * The 'error' parameter must have one of the following values: + */ +#define PNG_CHUNK_WARNING 0 /* never an error */ +#define PNG_CHUNK_WRITE_ERROR 1 /* an error only on write */ +#define PNG_CHUNK_ERROR 2 /* always an error */ + +/* ASCII to FP interfaces, currently only implemented if sCAL + * support is required. + */ +#if defined(PNG_sCAL_SUPPORTED) +/* MAX_DIGITS is actually the maximum number of characters in an sCAL + * width or height, derived from the precision (number of significant + * digits - a build time settable option) and assumptions about the + * maximum ridiculous exponent. + */ +#define PNG_sCAL_MAX_DIGITS (PNG_sCAL_PRECISION+1/*.*/+1/*E*/+10/*exponent*/) + +#ifdef PNG_FLOATING_POINT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_ascii_from_fp,(png_const_structrp png_ptr, + png_charp ascii, png_size_t size, double fp, unsigned int precision), + PNG_EMPTY); +#endif /* FLOATING_POINT */ + +#ifdef PNG_FIXED_POINT_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_ascii_from_fixed,(png_const_structrp png_ptr, + png_charp ascii, png_size_t size, png_fixed_point fp),PNG_EMPTY); +#endif /* FIXED_POINT */ +#endif /* sCAL */ + +#if defined(PNG_sCAL_SUPPORTED) || defined(PNG_pCAL_SUPPORTED) +/* An internal API to validate the format of a floating point number. + * The result is the index of the next character. If the number is + * not valid it will be the index of a character in the supposed number. + * + * The format of a number is defined in the PNG extensions specification + * and this API is strictly conformant to that spec, not anyone elses! + * + * The format as a regular expression is: + * + * [+-]?[0-9]+.?([Ee][+-]?[0-9]+)? + * + * or: + * + * [+-]?.[0-9]+(.[0-9]+)?([Ee][+-]?[0-9]+)? + * + * The complexity is that either integer or fraction must be present and the + * fraction is permitted to have no digits only if the integer is present. + * + * NOTE: The dangling E problem. + * There is a PNG valid floating point number in the following: + * + * PNG floating point numbers are not greedy. + * + * Working this out requires *TWO* character lookahead (because of the + * sign), the parser does not do this - it will fail at the 'r' - this + * doesn't matter for PNG sCAL chunk values, but it requires more care + * if the value were ever to be embedded in something more complex. Use + * ANSI-C strtod if you need the lookahead. + */ +/* State table for the parser. */ +#define PNG_FP_INTEGER 0 /* before or in integer */ +#define PNG_FP_FRACTION 1 /* before or in fraction */ +#define PNG_FP_EXPONENT 2 /* before or in exponent */ +#define PNG_FP_STATE 3 /* mask for the above */ +#define PNG_FP_SAW_SIGN 4 /* Saw +/- in current state */ +#define PNG_FP_SAW_DIGIT 8 /* Saw a digit in current state */ +#define PNG_FP_SAW_DOT 16 /* Saw a dot in current state */ +#define PNG_FP_SAW_E 32 /* Saw an E (or e) in current state */ +#define PNG_FP_SAW_ANY 60 /* Saw any of the above 4 */ + +/* These three values don't affect the parser. They are set but not used. + */ +#define PNG_FP_WAS_VALID 64 /* Preceding substring is a valid fp number */ +#define PNG_FP_NEGATIVE 128 /* A negative number, including "-0" */ +#define PNG_FP_NONZERO 256 /* A non-zero value */ +#define PNG_FP_STICKY 448 /* The above three flags */ + +/* This is available for the caller to store in 'state' if required. Do not + * call the parser after setting it (the parser sometimes clears it.) + */ +#define PNG_FP_INVALID 512 /* Available for callers as a distinct value */ + +/* Result codes for the parser (boolean - true meants ok, false means + * not ok yet.) + */ +#define PNG_FP_MAYBE 0 /* The number may be valid in the future */ +#define PNG_FP_OK 1 /* The number is valid */ + +/* Tests on the sticky non-zero and negative flags. To pass these checks + * the state must also indicate that the whole number is valid - this is + * achieved by testing PNG_FP_SAW_DIGIT (see the implementation for why this + * is equivalent to PNG_FP_OK above.) + */ +#define PNG_FP_NZ_MASK (PNG_FP_SAW_DIGIT | PNG_FP_NEGATIVE | PNG_FP_NONZERO) + /* NZ_MASK: the string is valid and a non-zero negative value */ +#define PNG_FP_Z_MASK (PNG_FP_SAW_DIGIT | PNG_FP_NONZERO) + /* Z MASK: the string is valid and a non-zero value. */ + /* PNG_FP_SAW_DIGIT: the string is valid. */ +#define PNG_FP_IS_ZERO(state) (((state) & PNG_FP_Z_MASK) == PNG_FP_SAW_DIGIT) +#define PNG_FP_IS_POSITIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_Z_MASK) +#define PNG_FP_IS_NEGATIVE(state) (((state) & PNG_FP_NZ_MASK) == PNG_FP_NZ_MASK) + +/* The actual parser. This can be called repeatedly. It updates + * the index into the string and the state variable (which must + * be initialized to 0). It returns a result code, as above. There + * is no point calling the parser any more if it fails to advance to + * the end of the string - it is stuck on an invalid character (or + * terminated by '\0'). + * + * Note that the pointer will consume an E or even an E+ and then leave + * a 'maybe' state even though a preceding integer.fraction is valid. + * The PNG_FP_WAS_VALID flag indicates that a preceding substring was + * a valid number. It's possible to recover from this by calling + * the parser again (from the start, with state 0) but with a string + * that omits the last character (i.e. set the size to the index of + * the problem character.) This has not been tested within libpng. + */ +PNG_INTERNAL_FUNCTION(int,png_check_fp_number,(png_const_charp string, + png_size_t size, int *statep, png_size_tp whereami),PNG_EMPTY); + +/* This is the same but it checks a complete string and returns true + * only if it just contains a floating point number. As of 1.5.4 this + * function also returns the state at the end of parsing the number if + * it was valid (otherwise it returns 0.) This can be used for testing + * for negative or zero values using the sticky flag. + */ +PNG_INTERNAL_FUNCTION(int,png_check_fp_string,(png_const_charp string, + png_size_t size),PNG_EMPTY); +#endif /* pCAL || sCAL */ + +#if defined(PNG_READ_GAMMA_SUPPORTED) ||\ + defined(PNG_INCH_CONVERSIONS_SUPPORTED) || defined(PNG_READ_pHYs_SUPPORTED) +/* Added at libpng version 1.5.0 */ +/* This is a utility to provide a*times/div (rounded) and indicate + * if there is an overflow. The result is a boolean - false (0) + * for overflow, true (1) if no overflow, in which case *res + * holds the result. + */ +PNG_INTERNAL_FUNCTION(int,png_muldiv,(png_fixed_point_p res, png_fixed_point a, + png_int_32 multiplied_by, png_int_32 divided_by),PNG_EMPTY); +#endif + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_INCH_CONVERSIONS_SUPPORTED) +/* Same deal, but issue a warning on overflow and return 0. */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_muldiv_warn, + (png_const_structrp png_ptr, png_fixed_point a, png_int_32 multiplied_by, + png_int_32 divided_by),PNG_EMPTY); +#endif + +#ifdef PNG_GAMMA_SUPPORTED +/* Calculate a reciprocal - used for gamma values. This returns + * 0 if the argument is 0 in order to maintain an undefined value; + * there are no warnings. + */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_reciprocal,(png_fixed_point a), + PNG_EMPTY); + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* The same but gives a reciprocal of the product of two fixed point + * values. Accuracy is suitable for gamma calculations but this is + * not exact - use png_muldiv for that. Only required at present on read. + */ +PNG_INTERNAL_FUNCTION(png_fixed_point,png_reciprocal2,(png_fixed_point a, + png_fixed_point b),PNG_EMPTY); +#endif + +/* Return true if the gamma value is significantly different from 1.0 */ +PNG_INTERNAL_FUNCTION(int,png_gamma_significant,(png_fixed_point gamma_value), + PNG_EMPTY); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* Internal fixed point gamma correction. These APIs are called as + * required to convert single values - they don't need to be fast, + * they are not used when processing image pixel values. + * + * While the input is an 'unsigned' value it must actually be the + * correct bit value - 0..255 or 0..65535 as required. + */ +PNG_INTERNAL_FUNCTION(png_uint_16,png_gamma_correct,(png_structrp png_ptr, + unsigned int value, png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(png_uint_16,png_gamma_16bit_correct,(unsigned int value, + png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(png_byte,png_gamma_8bit_correct,(unsigned int value, + png_fixed_point gamma_value),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_destroy_gamma_table,(png_structrp png_ptr), + PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_build_gamma_table,(png_structrp png_ptr, + int bit_depth),PNG_EMPTY); +#endif + +/* SIMPLIFIED READ/WRITE SUPPORT */ +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) ||\ + defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) +/* The internal structure that png_image::opaque points to. */ +typedef struct png_control +{ + png_structp png_ptr; + png_infop info_ptr; + png_voidp error_buf; /* Always a jmp_buf at present. */ + + png_const_bytep memory; /* Memory buffer. */ + png_size_t size; /* Size of the memory buffer. */ + + unsigned int for_write :1; /* Otherwise it is a read structure */ + unsigned int owned_file :1; /* We own the file in io_ptr */ +} png_control; + +/* Return the pointer to the jmp_buf from a png_control: necessary because C + * does not reveal the type of the elements of jmp_buf. + */ +#ifdef __cplusplus +# define png_control_jmp_buf(pc) (((jmp_buf*)((pc)->error_buf))[0]) +#else +# define png_control_jmp_buf(pc) ((pc)->error_buf) +#endif + +/* Utility to safely execute a piece of libpng code catching and logging any + * errors that might occur. Returns true on success, false on failure (either + * of the function or as a result of a png_error.) + */ +PNG_INTERNAL_FUNCTION(void,png_safe_error,(png_structp png_ptr, + png_const_charp error_message),PNG_NORETURN); + +#ifdef PNG_WARNINGS_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_safe_warning,(png_structp png_ptr, + png_const_charp warning_message),PNG_EMPTY); +#else +# define png_safe_warning 0/*dummy argument*/ +#endif + +PNG_INTERNAL_FUNCTION(int,png_safe_execute,(png_imagep image, + int (*function)(png_voidp), png_voidp arg),PNG_EMPTY); + +/* Utility to log an error; this also cleans up the png_image; the function + * always returns 0 (false). + */ +PNG_INTERNAL_FUNCTION(int,png_image_error,(png_imagep image, + png_const_charp error_message),PNG_EMPTY); + +#ifndef PNG_SIMPLIFIED_READ_SUPPORTED +/* png_image_free is used by the write code but not exported */ +PNG_INTERNAL_FUNCTION(void, png_image_free, (png_imagep image), PNG_EMPTY); +#endif /* !SIMPLIFIED_READ */ + +#endif /* SIMPLIFIED READ/WRITE */ + +#ifdef PNG_FILTER_OPTIMIZATIONS +PNG_INTERNAL_FUNCTION(void, PNG_FILTER_OPTIMIZATIONS, (png_structp png_ptr, + unsigned int bpp), PNG_EMPTY); + /* This is the initialization function for hardware specific optimizations, + * one implementation (for ARM NEON machines) is contained in + * arm/filter_neon.c. It need not be defined - the generic code will be used + * if not. + */ +#endif + +/* Maintainer: Put new private prototypes here ^ */ + +//#include "pngdebug.h" + +#endif /* PNG_VERSION_INFO_ONLY */ +#endif /* PNGPRIV_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngread.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngread.c new file mode 100644 index 0000000..f7bde96 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngread.c @@ -0,0 +1,4000 @@ + +/* pngread.c - read a PNG file + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains routines that an application calls directly to + * read a PNG file or stream. + */ + +#include "pngpriv.h" +#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) && defined(PNG_STDIO_SUPPORTED) +# include +#endif + +#ifdef PNG_READ_SUPPORTED + +/* Create a PNG structure for reading, and allocate any memory needed. */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_read_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn),PNG_ALLOCATED) +{ +#ifndef PNG_USER_MEM_SUPPORTED + png_structp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, NULL, NULL, NULL); +#else + return png_create_read_struct_2(user_png_ver, error_ptr, error_fn, + warn_fn, NULL, NULL, NULL); +} + +/* Alternate create PNG structure for reading, and allocate any memory + * needed. + */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_read_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn),PNG_ALLOCATED) +{ + png_structp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, mem_ptr, malloc_fn, free_fn); +#endif /* PNG_USER_MEM_SUPPORTED */ + + if (png_ptr != NULL) + { + png_ptr->mode = PNG_IS_READ_STRUCT; + + /* Added in libpng-1.6.0; this can be used to detect a read structure if + * required (it will be zero in a write structure.) + */ +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + png_ptr->IDAT_read_size = PNG_IDAT_READ_SIZE; +# endif + +# ifdef PNG_BENIGN_READ_ERRORS_SUPPORTED + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN; + + /* In stable builds only warn if an application error can be completely + * handled. + */ +# if PNG_LIBPNG_BUILD_BASE_TYPE >= PNG_LIBPNG_BUILD_RC + png_ptr->flags |= PNG_FLAG_APP_WARNINGS_WARN; +# endif +# endif + + /* TODO: delay this, it can be done in png_init_io (if the app doesn't + * do it itself) avoiding setting the default function if it is not + * required. + */ + png_set_read_fn(png_ptr, NULL, NULL); + } + + return png_ptr; +} + + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the information before the actual image data. This has been + * changed in v0.90 to allow reading a file that already has the magic + * bytes read from the stream. You can tell libpng how many bytes have + * been read from the beginning of the stream (up to the maximum of 8) + * via png_set_sig_bytes(), and we will only check the remaining bytes + * here. The application can then have access to the signature bytes we + * read if it is determined that this isn't a valid PNG file. + */ +void PNGAPI +png_read_info(png_structrp png_ptr, png_inforp info_ptr) +{ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + + png_debug(1, "in png_read_info"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Read and check the PNG file signature. */ + png_read_sig(png_ptr, info_ptr); + + for (;;) + { + png_uint_32 length = png_read_chunk_header(png_ptr); + png_uint_32 chunk_name = png_ptr->chunk_name; + + /* IDAT logic needs to happen here to simplify getting the two flags + * right. + */ + if (chunk_name == png_IDAT) + { + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "Missing IHDR before IDAT"); + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + !(png_ptr->mode & PNG_HAVE_PLTE)) + png_chunk_error(png_ptr, "Missing PLTE before IDAT"); + + else if (png_ptr->mode & PNG_AFTER_IDAT) + png_chunk_benign_error(png_ptr, "Too many IDATs found"); + + png_ptr->mode |= PNG_HAVE_IDAT; + } + + else if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + + /* This should be a binary subdivision search or a hash for + * matching the chunk name rather than a linear search. + */ + if (chunk_name == png_IHDR) + png_handle_IHDR(png_ptr, info_ptr, length); + + else if (chunk_name == png_IEND) + png_handle_IEND(png_ptr, info_ptr, length); + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + png_handle_unknown(png_ptr, info_ptr, length, keep); + + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = 0; /* It has been consumed */ + break; + } + } +#endif + else if (chunk_name == png_PLTE) + png_handle_PLTE(png_ptr, info_ptr, length); + + else if (chunk_name == png_IDAT) + { + png_ptr->idat_size = length; + break; + } + +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + png_handle_bKGD(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED + else if (chunk_name == png_cHRM) + png_handle_cHRM(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (chunk_name == png_gAMA) + png_handle_gAMA(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + png_handle_hIST(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + png_handle_oFFs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + png_handle_pCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + png_handle_sCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + png_handle_pHYs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED + else if (chunk_name == png_sBIT) + png_handle_sBIT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + png_handle_sRGB(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED + else if (chunk_name == png_iCCP) + png_handle_iCCP(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + png_handle_sPLT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + png_handle_tEXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + png_handle_tIME(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + png_handle_tRNS(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + png_handle_zTXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + png_handle_iTXt(png_ptr, info_ptr, length); +#endif + + else + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +/* Optional call to update the users info_ptr structure */ +void PNGAPI +png_read_update_info(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_read_update_info"); + + if (png_ptr != NULL) + { + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + { + png_read_start_row(png_ptr); + +# ifdef PNG_READ_TRANSFORMS_SUPPORTED + png_read_transform_info(png_ptr, info_ptr); +# else + PNG_UNUSED(info_ptr) +# endif + } + + /* New in 1.6.0 this avoids the bug of doing the initializations twice */ + else + png_app_error(png_ptr, + "png_read_update_info/png_start_read_image: duplicate call"); + } +} + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Initialize palette, background, etc, after transformations + * are set, but before any reading takes place. This allows + * the user to obtain a gamma-corrected palette, for example. + * If the user doesn't call this, we will do it ourselves. + */ +void PNGAPI +png_start_read_image(png_structrp png_ptr) +{ + png_debug(1, "in png_start_read_image"); + + if (png_ptr != NULL) + { + if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0) + png_read_start_row(png_ptr); + + /* New in 1.6.0 this avoids the bug of doing the initializations twice */ + else + png_app_error(png_ptr, + "png_start_read_image/png_read_update_info: duplicate call"); + } +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +void PNGAPI +png_read_row(png_structrp png_ptr, png_bytep row, png_bytep dsp_row) +{ + png_row_info row_info; + + if (png_ptr == NULL) + return; + + png_debug2(1, "in png_read_row (row %lu, pass %d)", + (unsigned long)png_ptr->row_number, png_ptr->pass); + + /* png_read_start_row sets the information (in particular iwidth) for this + * interlace pass. + */ + if (!(png_ptr->flags & PNG_FLAG_ROW_INIT)) + png_read_start_row(png_ptr); + + /* 1.5.6: row_info moved out of png_struct to a local here. */ + row_info.width = png_ptr->iwidth; /* NOTE: width of current interlaced row */ + row_info.color_type = png_ptr->color_type; + row_info.bit_depth = png_ptr->bit_depth; + row_info.channels = png_ptr->channels; + row_info.pixel_depth = png_ptr->pixel_depth; + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + + if (png_ptr->row_number == 0 && png_ptr->pass == 0) + { + /* Check for transforms that have been set but were defined out */ +#if defined(PNG_WRITE_INVERT_SUPPORTED) && !defined(PNG_READ_INVERT_SUPPORTED) + if (png_ptr->transformations & PNG_INVERT_MONO) + png_warning(png_ptr, "PNG_READ_INVERT_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_FILLER_SUPPORTED) && !defined(PNG_READ_FILLER_SUPPORTED) + if (png_ptr->transformations & PNG_FILLER) + png_warning(png_ptr, "PNG_READ_FILLER_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \ + !defined(PNG_READ_PACKSWAP_SUPPORTED) + if (png_ptr->transformations & PNG_PACKSWAP) + png_warning(png_ptr, "PNG_READ_PACKSWAP_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_PACK_SUPPORTED) && !defined(PNG_READ_PACK_SUPPORTED) + if (png_ptr->transformations & PNG_PACK) + png_warning(png_ptr, "PNG_READ_PACK_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_SHIFT_SUPPORTED) && !defined(PNG_READ_SHIFT_SUPPORTED) + if (png_ptr->transformations & PNG_SHIFT) + png_warning(png_ptr, "PNG_READ_SHIFT_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_BGR_SUPPORTED) && !defined(PNG_READ_BGR_SUPPORTED) + if (png_ptr->transformations & PNG_BGR) + png_warning(png_ptr, "PNG_READ_BGR_SUPPORTED is not defined"); +#endif + +#if defined(PNG_WRITE_SWAP_SUPPORTED) && !defined(PNG_READ_SWAP_SUPPORTED) + if (png_ptr->transformations & PNG_SWAP_BYTES) + png_warning(png_ptr, "PNG_READ_SWAP_SUPPORTED is not defined"); +#endif + } + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* If interlaced and we do not need a new row, combine row and return. + * Notice that the pixels we have from previous rows have been transformed + * already; we can only combine like with like (transformed or + * untransformed) and, because of the libpng API for interlaced images, this + * means we must transform before de-interlacing. + */ + if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE)) + { + switch (png_ptr->pass) + { + case 0: + if (png_ptr->row_number & 0x07) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + png_read_finish_row(png_ptr); + return; + } + break; + + case 1: + if ((png_ptr->row_number & 0x07) || png_ptr->width < 5) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 2: + if ((png_ptr->row_number & 0x07) != 4) + { + if (dsp_row != NULL && (png_ptr->row_number & 4)) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 3: + if ((png_ptr->row_number & 3) || png_ptr->width < 3) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 4: + if ((png_ptr->row_number & 3) != 2) + { + if (dsp_row != NULL && (png_ptr->row_number & 2)) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + case 5: + if ((png_ptr->row_number & 1) || png_ptr->width < 2) + { + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + png_read_finish_row(png_ptr); + return; + } + break; + + default: + case 6: + if (!(png_ptr->row_number & 1)) + { + png_read_finish_row(png_ptr); + return; + } + break; + } + } +#endif + + if (!(png_ptr->mode & PNG_HAVE_IDAT)) + png_error(png_ptr, "Invalid attempt to read row data"); + + /* Fill the row with IDAT data: */ + png_read_IDAT_data(png_ptr, png_ptr->row_buf, row_info.rowbytes + 1); + + if (png_ptr->row_buf[0] > PNG_FILTER_VALUE_NONE) + { + if (png_ptr->row_buf[0] < PNG_FILTER_VALUE_LAST) + png_read_filter_row(png_ptr, &row_info, png_ptr->row_buf + 1, + png_ptr->prev_row + 1, png_ptr->row_buf[0]); + else + png_error(png_ptr, "bad adaptive filter value"); + } + + /* libpng 1.5.6: the following line was copying png_ptr->rowbytes before + * 1.5.6, while the buffer really is this big in current versions of libpng + * it may not be in the future, so this was changed just to copy the + * interlaced count: + */ + memcpy(png_ptr->prev_row, png_ptr->row_buf, row_info.rowbytes + 1); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) && + (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING)) + { + /* Intrapixel differencing */ + png_do_read_intrapixel(&row_info, png_ptr->row_buf + 1); + } +#endif + + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + if (png_ptr->transformations) + png_do_read_transformations(png_ptr, &row_info); +#endif + + /* The transformed pixel depth should match the depth now in row_info. */ + if (png_ptr->transformed_pixel_depth == 0) + { + png_ptr->transformed_pixel_depth = row_info.pixel_depth; + if (row_info.pixel_depth > png_ptr->maximum_pixel_depth) + png_error(png_ptr, "sequential row overflow"); + } + + else if (png_ptr->transformed_pixel_depth != row_info.pixel_depth) + png_error(png_ptr, "internal sequential row size calculation error"); + +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Blow up interlaced rows to full size */ + if (png_ptr->interlaced && + (png_ptr->transformations & PNG_INTERLACE)) + { + if (png_ptr->pass < 6) + png_do_read_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass, + png_ptr->transformations); + + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, 1/*display*/); + + if (row != NULL) + png_combine_row(png_ptr, row, 0/*row*/); + } + + else +#endif + { + if (row != NULL) + png_combine_row(png_ptr, row, -1/*ignored*/); + + if (dsp_row != NULL) + png_combine_row(png_ptr, dsp_row, -1/*ignored*/); + } + png_read_finish_row(png_ptr); + + if (png_ptr->read_row_fn != NULL) + (*(png_ptr->read_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass); + +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read one or more rows of image data. If the image is interlaced, + * and png_set_interlace_handling() has been called, the rows need to + * contain the contents of the rows from the previous pass. If the + * image has alpha or transparency, and png_handle_alpha()[*] has been + * called, the rows contents must be initialized to the contents of the + * screen. + * + * "row" holds the actual image, and pixels are placed in it + * as they arrive. If the image is displayed after each pass, it will + * appear to "sparkle" in. "display_row" can be used to display a + * "chunky" progressive image, with finer detail added as it becomes + * available. If you do not want this "chunky" display, you may pass + * NULL for display_row. If you do not want the sparkle display, and + * you have not called png_handle_alpha(), you may pass NULL for rows. + * If you have called png_handle_alpha(), and the image has either an + * alpha channel or a transparency chunk, you must provide a buffer for + * rows. In this case, you do not have to provide a display_row buffer + * also, but you may. If the image is not interlaced, or if you have + * not called png_set_interlace_handling(), the display_row buffer will + * be ignored, so pass NULL to it. + * + * [*] png_handle_alpha() does not exist yet, as of this version of libpng + */ + +void PNGAPI +png_read_rows(png_structrp png_ptr, png_bytepp row, + png_bytepp display_row, png_uint_32 num_rows) +{ + png_uint_32 i; + png_bytepp rp; + png_bytepp dp; + + png_debug(1, "in png_read_rows"); + + if (png_ptr == NULL) + return; + + rp = row; + dp = display_row; + if (rp != NULL && dp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep rptr = *rp++; + png_bytep dptr = *dp++; + + png_read_row(png_ptr, rptr, dptr); + } + + else if (rp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep rptr = *rp; + png_read_row(png_ptr, rptr, NULL); + rp++; + } + + else if (dp != NULL) + for (i = 0; i < num_rows; i++) + { + png_bytep dptr = *dp; + png_read_row(png_ptr, NULL, dptr); + dp++; + } +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the entire image. If the image has an alpha channel or a tRNS + * chunk, and you have called png_handle_alpha()[*], you will need to + * initialize the image to the current image that PNG will be overlaying. + * We set the num_rows again here, in case it was incorrectly set in + * png_read_start_row() by a call to png_read_update_info() or + * png_start_read_image() if png_set_interlace_handling() wasn't called + * prior to either of these functions like it should have been. You can + * only call this function once. If you desire to have an image for + * each pass of a interlaced image, use png_read_rows() instead. + * + * [*] png_handle_alpha() does not exist yet, as of this version of libpng + */ +void PNGAPI +png_read_image(png_structrp png_ptr, png_bytepp image) +{ + png_uint_32 i, image_height; + int pass, j; + png_bytepp rp; + + png_debug(1, "in png_read_image"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (!(png_ptr->flags & PNG_FLAG_ROW_INIT)) + { + pass = png_set_interlace_handling(png_ptr); + /* And make sure transforms are initialized. */ + png_start_read_image(png_ptr); + } + else + { + if (png_ptr->interlaced && !(png_ptr->transformations & PNG_INTERLACE)) + { + /* Caller called png_start_read_image or png_read_update_info without + * first turning on the PNG_INTERLACE transform. We can fix this here, + * but the caller should do it! + */ + png_warning(png_ptr, "Interlace handling should be turned on when " + "using png_read_image"); + /* Make sure this is set correctly */ + png_ptr->num_rows = png_ptr->height; + } + + /* Obtain the pass number, which also turns on the PNG_INTERLACE flag in + * the above error case. + */ + pass = png_set_interlace_handling(png_ptr); + } +#else + if (png_ptr->interlaced) + png_error(png_ptr, + "Cannot read interlaced image -- interlace handler disabled"); + + pass = 1; +#endif + + image_height=png_ptr->height; + + for (j = 0; j < pass; j++) + { + rp = image; + for (i = 0; i < image_height; i++) + { + png_read_row(png_ptr, *rp, NULL); + rp++; + } + } +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +/* Read the end of the PNG file. Will not read past the end of the + * file, will verify the end is accurate, and will read any comments + * or time information at the end of the file, if info is not NULL. + */ +void PNGAPI +png_read_end(png_structrp png_ptr, png_inforp info_ptr) +{ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + int keep; +#endif + + png_debug(1, "in png_read_end"); + + if (png_ptr == NULL) + return; + + /* If png_read_end is called in the middle of reading the rows there may + * still be pending IDAT data and an owned zstream. Deal with this here. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + if (!png_chunk_unknown_handling(png_ptr, png_IDAT)) +#endif + png_read_finish_IDAT(png_ptr); + +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Report invalid palette index; added at libng-1.5.10 */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max > png_ptr->num_palette) + png_benign_error(png_ptr, "Read palette index exceeding num_palette"); +#endif + + do + { + png_uint_32 length = png_read_chunk_header(png_ptr); + png_uint_32 chunk_name = png_ptr->chunk_name; + + if (chunk_name == png_IHDR) + png_handle_IHDR(png_ptr, info_ptr, length); + + else if (chunk_name == png_IEND) + png_handle_IEND(png_ptr, info_ptr, length); + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED + else if ((keep = png_chunk_unknown_handling(png_ptr, chunk_name)) != 0) + { + if (chunk_name == png_IDAT) + { + if ((length > 0) || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) + png_benign_error(png_ptr, "Too many IDATs found"); + } + png_handle_unknown(png_ptr, info_ptr, length, keep); + if (chunk_name == png_PLTE) + png_ptr->mode |= PNG_HAVE_PLTE; + } +#endif + + else if (chunk_name == png_IDAT) + { + /* Zero length IDATs are legal after the last IDAT has been + * read, but not after other chunks have been read. + */ + if ((length > 0) || (png_ptr->mode & PNG_HAVE_CHUNK_AFTER_IDAT)) + png_benign_error(png_ptr, "Too many IDATs found"); + + png_crc_finish(png_ptr, length); + } + else if (chunk_name == png_PLTE) + png_handle_PLTE(png_ptr, info_ptr, length); + +#ifdef PNG_READ_bKGD_SUPPORTED + else if (chunk_name == png_bKGD) + png_handle_bKGD(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED + else if (chunk_name == png_cHRM) + png_handle_cHRM(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_gAMA_SUPPORTED + else if (chunk_name == png_gAMA) + png_handle_gAMA(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + else if (chunk_name == png_hIST) + png_handle_hIST(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED + else if (chunk_name == png_oFFs) + png_handle_oFFs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED + else if (chunk_name == png_pCAL) + png_handle_pCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED + else if (chunk_name == png_sCAL) + png_handle_sCAL(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED + else if (chunk_name == png_pHYs) + png_handle_pHYs(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED + else if (chunk_name == png_sBIT) + png_handle_sBIT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED + else if (chunk_name == png_sRGB) + png_handle_sRGB(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iCCP_SUPPORTED + else if (chunk_name == png_iCCP) + png_handle_iCCP(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_sPLT_SUPPORTED + else if (chunk_name == png_sPLT) + png_handle_sPLT(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED + else if (chunk_name == png_tEXt) + png_handle_tEXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tIME_SUPPORTED + else if (chunk_name == png_tIME) + png_handle_tIME(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_tRNS_SUPPORTED + else if (chunk_name == png_tRNS) + png_handle_tRNS(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED + else if (chunk_name == png_zTXt) + png_handle_zTXt(png_ptr, info_ptr, length); +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED + else if (chunk_name == png_iTXt) + png_handle_iTXt(png_ptr, info_ptr, length); +#endif + + else + png_handle_unknown(png_ptr, info_ptr, length, + PNG_HANDLE_CHUNK_AS_DEFAULT); + } while (!(png_ptr->mode & PNG_HAVE_IEND)); +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +/* Free all memory used in the read struct */ +static void +png_read_destroy(png_structrp png_ptr) +{ + png_debug(1, "in png_read_destroy"); + +#ifdef PNG_READ_GAMMA_SUPPORTED + png_destroy_gamma_table(png_ptr); +#endif + + png_free(png_ptr, png_ptr->big_row_buf); + png_free(png_ptr, png_ptr->big_prev_row); + png_free(png_ptr, png_ptr->read_buffer); + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + png_free(png_ptr, png_ptr->palette_lookup); + png_free(png_ptr, png_ptr->quantize_index); +#endif + + if (png_ptr->free_me & PNG_FREE_PLTE) + png_zfree(png_ptr, png_ptr->palette); + png_ptr->free_me &= ~PNG_FREE_PLTE; + +#if defined(PNG_tRNS_SUPPORTED) || \ + defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + if (png_ptr->free_me & PNG_FREE_TRNS) + png_free(png_ptr, png_ptr->trans_alpha); + png_ptr->free_me &= ~PNG_FREE_TRNS; +#endif + + inflateEnd(&png_ptr->zstream); + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + png_free(png_ptr, png_ptr->save_buffer); +#endif + +#if defined(PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED) &&\ + defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) + png_free(png_ptr, png_ptr->unknown_chunk.data); +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + png_free(png_ptr, png_ptr->chunk_list); +#endif + + /* NOTE: the 'setjmp' buffer may still be allocated and the memory and error + * callbacks are still set at this point. They are required to complete the + * destruction of the png_struct itself. + */ +} + +/* Free all memory used by the read */ +void PNGAPI +png_destroy_read_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr, + png_infopp end_info_ptr_ptr) +{ + png_structrp png_ptr = NULL; + + png_debug(1, "in png_destroy_read_struct"); + + if (png_ptr_ptr != NULL) + png_ptr = *png_ptr_ptr; + + if (png_ptr == NULL) + return; + + /* libpng 1.6.0: use the API to destroy info structs to ensure consistent + * behavior. Prior to 1.6.0 libpng did extra 'info' destruction in this API. + * The extra was, apparently, unnecessary yet this hides memory leak bugs. + */ + png_destroy_info_struct(png_ptr, end_info_ptr_ptr); + png_destroy_info_struct(png_ptr, info_ptr_ptr); + + *png_ptr_ptr = NULL; + png_read_destroy(png_ptr); + png_destroy_png_struct(png_ptr); +} + +void PNGAPI +png_set_read_status_fn(png_structrp png_ptr, png_read_status_ptr read_row_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->read_row_fn = read_row_fn; +} + + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_read_png(png_structrp png_ptr, png_inforp info_ptr, + int transforms, + voidp params) +{ + int row; + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* png_read_info() gives us all of the information from the + * PNG file before the first IDAT (image data chunk). + */ + png_read_info(png_ptr, info_ptr); + if (info_ptr->height > PNG_UINT_32_MAX/(sizeof (png_bytep))) + png_error(png_ptr, "Image is too high to process with png_read_png()"); + + /* -------------- image transformations start here ------------------- */ + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + /* Tell libpng to strip 16-bit/color files down to 8 bits per color. + */ + if (transforms & PNG_TRANSFORM_SCALE_16) + { + /* Added at libpng-1.5.4. "strip_16" produces the same result that it + * did in earlier versions, while "scale_16" is now more accurate. + */ + png_set_scale_16(png_ptr); + } +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + /* If both SCALE and STRIP are required pngrtran will effectively cancel the + * latter by doing SCALE first. This is ok and allows apps not to check for + * which is supported to get the right answer. + */ + if (transforms & PNG_TRANSFORM_STRIP_16) + png_set_strip_16(png_ptr); +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + /* Strip alpha bytes from the input data without combining with + * the background (not recommended). + */ + if (transforms & PNG_TRANSFORM_STRIP_ALPHA) + png_set_strip_alpha(png_ptr); +#endif + +#if defined(PNG_READ_PACK_SUPPORTED) && !defined(PNG_READ_EXPAND_SUPPORTED) + /* Extract multiple pixels with bit depths of 1, 2, or 4 from a single + * byte into separate bytes (useful for paletted and grayscale images). + */ + if (transforms & PNG_TRANSFORM_PACKING) + png_set_packing(png_ptr); +#endif + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + /* Change the order of packed pixels to least significant bit first + * (not useful if you are using png_set_packing). + */ + if (transforms & PNG_TRANSFORM_PACKSWAP) + png_set_packswap(png_ptr); +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED + /* Expand paletted colors into true RGB triplets + * Expand grayscale images to full 8 bits from 1, 2, or 4 bits/pixel + * Expand paletted or RGB images with transparency to full alpha + * channels so the data will be available as RGBA quartets. + */ + if (transforms & PNG_TRANSFORM_EXPAND) + if ((png_ptr->bit_depth < 8) || + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) || + (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) + png_set_expand(png_ptr); +#endif + + /* We don't handle background color or gamma transformation or quantizing. + */ + +#ifdef PNG_READ_INVERT_SUPPORTED + /* Invert monochrome files to have 0 as white and 1 as black + */ + if (transforms & PNG_TRANSFORM_INVERT_MONO) + png_set_invert_mono(png_ptr); +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED + /* If you want to shift the pixel values from the range [0,255] or + * [0,65535] to the original [0,7] or [0,31], or whatever range the + * colors were originally in: + */ + if ((transforms & PNG_TRANSFORM_SHIFT) + && png_get_valid(png_ptr, info_ptr, PNG_INFO_sBIT)) + { + png_color_8p sig_bit = 0; + + png_get_sBIT(png_ptr, info_ptr, &sig_bit); + png_set_shift(png_ptr, sig_bit); + } +#endif + +#ifdef PNG_READ_BGR_SUPPORTED + /* Flip the RGB pixels to BGR (or RGBA to BGRA) */ + if (transforms & PNG_TRANSFORM_BGR) + png_set_bgr(png_ptr); +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED + /* Swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */ + if (transforms & PNG_TRANSFORM_SWAP_ALPHA) + png_set_swap_alpha(png_ptr); +#endif + +#ifdef PNG_READ_SWAP_SUPPORTED + /* Swap bytes of 16-bit files to least significant byte first */ + if (transforms & PNG_TRANSFORM_SWAP_ENDIAN) + png_set_swap(png_ptr); +#endif + +/* Added at libpng-1.2.41 */ +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + /* Invert the alpha channel from opacity to transparency */ + if (transforms & PNG_TRANSFORM_INVERT_ALPHA) + png_set_invert_alpha(png_ptr); +#endif + +/* Added at libpng-1.2.41 */ +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /* Expand grayscale image to RGB */ + if (transforms & PNG_TRANSFORM_GRAY_TO_RGB) + png_set_gray_to_rgb(png_ptr); +#endif + +/* Added at libpng-1.5.4 */ +#ifdef PNG_READ_EXPAND_16_SUPPORTED + if (transforms & PNG_TRANSFORM_EXPAND_16) + png_set_expand_16(png_ptr); +#endif + + /* We don't handle adding filler bytes */ + + /* We use png_read_image and rely on that for interlace handling, but we also + * call png_read_update_info therefore must turn on interlace handling now: + */ + (void)png_set_interlace_handling(png_ptr); + + /* Optional call to gamma correct and add the background to the palette + * and update info structure. REQUIRED if you are expecting libpng to + * update the palette for you (i.e., you selected such a transform above). + */ + png_read_update_info(png_ptr, info_ptr); + + /* -------------- image transformations end here ------------------- */ + + png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); + if (info_ptr->row_pointers == NULL) + { + png_uint_32 iptr; + + info_ptr->row_pointers = (png_bytepp)png_malloc(png_ptr, + info_ptr->height * (sizeof (png_bytep))); + for (iptr=0; iptrheight; iptr++) + info_ptr->row_pointers[iptr] = NULL; + + info_ptr->free_me |= PNG_FREE_ROWS; + + for (row = 0; row < (int)info_ptr->height; row++) + info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr, + png_get_rowbytes(png_ptr, info_ptr)); + } + + png_read_image(png_ptr, info_ptr->row_pointers); + info_ptr->valid |= PNG_INFO_IDAT; + + /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */ + png_read_end(png_ptr, info_ptr); + + PNG_UNUSED(transforms) /* Quiet compiler warnings */ + PNG_UNUSED(params) + +} +#endif /* PNG_INFO_IMAGE_SUPPORTED */ +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +#ifdef PNG_SIMPLIFIED_READ_SUPPORTED +/* SIMPLIFIED READ + * + * This code currently relies on the sequential reader, though it could easily + * be made to work with the progressive one. + */ +/* Arguments to png_image_finish_read: */ + +/* Encoding of PNG data (used by the color-map code) */ +/* TODO: change these, dang, ANSI-C reserves the 'E' namespace. */ +# define E_NOTSET 0 /* File encoding not yet known */ +# define E_sRGB 1 /* 8-bit encoded to sRGB gamma */ +# define E_LINEAR 2 /* 16-bit linear: not encoded, NOT pre-multiplied! */ +# define E_FILE 3 /* 8-bit encoded to file gamma, not sRGB or linear */ +# define E_LINEAR8 4 /* 8-bit linear: only from a file value */ + +/* Color-map processing: after libpng has run on the PNG image further + * processing may be needed to conver the data to color-map indicies. + */ +#define PNG_CMAP_NONE 0 +#define PNG_CMAP_GA 1 /* Process GA data to a color-map with alpha */ +#define PNG_CMAP_TRANS 2 /* Process GA data to a background index */ +#define PNG_CMAP_RGB 3 /* Process RGB data */ +#define PNG_CMAP_RGB_ALPHA 4 /* Process RGBA data */ + +/* The following document where the background is for each processing case. */ +#define PNG_CMAP_NONE_BACKGROUND 256 +#define PNG_CMAP_GA_BACKGROUND 231 +#define PNG_CMAP_TRANS_BACKGROUND 254 +#define PNG_CMAP_RGB_BACKGROUND 256 +#define PNG_CMAP_RGB_ALPHA_BACKGROUND 216 + +typedef struct +{ + /* Arguments: */ + png_imagep image; + png_voidp buffer; + png_int_32 row_stride; + png_voidp colormap; + png_const_colorp background; + /* Local variables: */ + png_voidp local_row; + png_voidp first_row; + ptrdiff_t row_bytes; /* step between rows */ + int file_encoding; /* E_ values above */ + png_fixed_point gamma_to_linear; /* For E_FILE, reciprocal of gamma */ + int colormap_processing; /* PNG_CMAP_ values above */ +} png_image_read_control; + +/* Do all the *safe* initialization - 'safe' means that png_error won't be + * called, so setting up the jmp_buf is not required. This means that anything + * called from here must *not* call png_malloc - it has to call png_malloc_warn + * instead so that control is returned safely back to this routine. + */ +static int +png_image_read_init(png_imagep image) +{ + if (image->opaque == NULL) + { + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, image, + png_safe_error, png_safe_warning); + + /* And set the rest of the structure to NULL to ensure that the various + * fields are consistent. + */ + memset(image, 0, (sizeof *image)); + image->version = PNG_IMAGE_VERSION; + + if (png_ptr != NULL) + { + png_infop info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr != NULL) + { + png_controlp control = png_voidcast(png_controlp, + png_malloc_warn(png_ptr, (sizeof *control))); + + if (control != NULL) + { + memset(control, 0, (sizeof *control)); + + control->png_ptr = png_ptr; + control->info_ptr = info_ptr; + control->for_write = 0; + + image->opaque = control; + return 1; + } + + /* Error clean up */ + png_destroy_info_struct(png_ptr, &info_ptr); + } + + png_destroy_read_struct(&png_ptr, NULL, NULL); + } + + return png_image_error(image, "png_image_read: out of memory"); + } + + return png_image_error(image, "png_image_read: opaque pointer not NULL"); +} + +/* Utility to find the base format of a PNG file from a png_struct. */ +static png_uint_32 +png_image_format(png_structrp png_ptr) +{ + png_uint_32 format = 0; + + if (png_ptr->color_type & PNG_COLOR_MASK_COLOR) + format |= PNG_FORMAT_FLAG_COLOR; + + if (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) + format |= PNG_FORMAT_FLAG_ALPHA; + + /* Use png_ptr here, not info_ptr, because by examination png_handle_tRNS + * sets the png_struct fields; that's all we are interested in here. The + * precise interaction with an app call to png_set_tRNS and PNG file reading + * is unclear. + */ + else if (png_ptr->num_trans > 0) + format |= PNG_FORMAT_FLAG_ALPHA; + + if (png_ptr->bit_depth == 16) + format |= PNG_FORMAT_FLAG_LINEAR; + + if (png_ptr->color_type & PNG_COLOR_MASK_PALETTE) + format |= PNG_FORMAT_FLAG_COLORMAP; + + return format; +} + +/* Is the given gamma significantly different from sRGB? The test is the same + * one used in pngrtran.c when deciding whether to do gamma correction. The + * arithmetic optimizes the division by using the fact that the inverse of the + * file sRGB gamma is 2.2 + */ +static int +png_gamma_not_sRGB(png_fixed_point g) +{ + if (g < PNG_FP_1) + { + /* An uninitialized gamma is assumed to be sRGB for the simplified API. */ + if (g == 0) + return 0; + + return png_gamma_significant((g * 11 + 2)/5 /* i.e. *2.2, rounded */); + } + + return 1; +} + +/* Do the main body of a 'png_image_begin_read' function; read the PNG file + * header and fill in all the information. This is executed in a safe context, + * unlike the init routine above. + */ +static int +png_image_read_header(png_voidp argument) +{ + png_imagep image = png_voidcast(png_imagep, argument); + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + + png_set_benign_errors(png_ptr, 1/*warn*/); + png_read_info(png_ptr, info_ptr); + + /* Do this the fast way; just read directly out of png_struct. */ + image->width = png_ptr->width; + image->height = png_ptr->height; + + { + png_uint_32 format = png_image_format(png_ptr); + + image->format = format; + +#ifdef PNG_COLORSPACE_SUPPORTED + /* Does the colorspace match sRGB? If there is no color endpoint + * (colorant) information assume yes, otherwise require the + * 'ENDPOINTS_MATCHE_sRGB' colorspace flag to have been set. If the + * colorspace has been determined to be invalid ignore it. + */ + if ((format & PNG_FORMAT_FLAG_COLOR) != 0 && ((png_ptr->colorspace.flags + & (PNG_COLORSPACE_HAVE_ENDPOINTS|PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB| + PNG_COLORSPACE_INVALID)) == PNG_COLORSPACE_HAVE_ENDPOINTS)) + image->flags |= PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB; +#endif + } + + /* We need the maximum number of entries regardless of the format the + * application sets here. + */ + { + png_uint_32 cmap_entries; + + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + cmap_entries = 1U << png_ptr->bit_depth; + break; + + case PNG_COLOR_TYPE_PALETTE: + cmap_entries = png_ptr->num_palette; + break; + + default: + cmap_entries = 256; + break; + } + + if (cmap_entries > 256) + cmap_entries = 256; + + image->colormap_entries = cmap_entries; + } + + return 1; +} + +#ifdef PNG_STDIO_SUPPORTED +int PNGAPI +png_image_begin_read_from_stdio(png_imagep image, FILE* file) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file != NULL) + { + if (png_image_read_init(image)) + { + /* This is slightly evil, but png_init_io doesn't do anything other + * than this and we haven't changed the standard IO functions so + * this saves a 'safe' function. + */ + image->opaque->png_ptr->io_ptr = file; + return png_safe_execute(image, png_image_read_header, image); + } + } + + else + return png_image_error(image, + "png_image_begin_read_from_stdio: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_stdio: incorrect PNG_IMAGE_VERSION"); + + return 0; +} + +int PNGAPI +png_image_begin_read_from_file(png_imagep image, const char *file_name) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file_name != NULL) + { + FILE *fp = fopen(file_name, "rb"); + + if (fp != NULL) + { + if (png_image_read_init(image)) + { + image->opaque->png_ptr->io_ptr = fp; + image->opaque->owned_file = 1; + return png_safe_execute(image, png_image_read_header, image); + } + + /* Clean up: just the opened file. */ + (void)fclose(fp); + } + + else + return png_image_error(image, strerror(errno)); + } + + else + return png_image_error(image, + "png_image_begin_read_from_file: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_file: incorrect PNG_IMAGE_VERSION"); + + return 0; +} +#endif /* PNG_STDIO_SUPPORTED */ + +static void PNGCBAPI +png_image_memory_read(png_structp png_ptr, png_bytep out, png_size_t need) +{ + if (png_ptr != NULL) + { + png_imagep image = png_voidcast(png_imagep, png_ptr->io_ptr); + if (image != NULL) + { + png_controlp cp = image->opaque; + if (cp != NULL) + { + png_const_bytep memory = cp->memory; + png_size_t size = cp->size; + + if (memory != NULL && size >= need) + { + memcpy(out, memory, need); + cp->memory = memory + need; + cp->size = size - need; + return; + } + + png_error(png_ptr, "read beyond end of data"); + } + } + + png_error(png_ptr, "invalid memory read"); + } +} + +int PNGAPI png_image_begin_read_from_memory(png_imagep image, + png_const_voidp memory, png_size_t size) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (memory != NULL && size > 0) + { + if (png_image_read_init(image)) + { + /* Now set the IO functions to read from the memory buffer and + * store it into io_ptr. Again do this in-place to avoid calling a + * libpng function that requires error handling. + */ + image->opaque->memory = png_voidcast(png_const_bytep, memory); + image->opaque->size = size; + image->opaque->png_ptr->io_ptr = image; + image->opaque->png_ptr->read_data_fn = png_image_memory_read; + + return png_safe_execute(image, png_image_read_header, image); + } + } + + else + return png_image_error(image, + "png_image_begin_read_from_memory: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_begin_read_from_memory: incorrect PNG_IMAGE_VERSION"); + + return 0; +} + +/* Utility function to skip chunks that are not used by the simplified image + * read functions and an appropriate macro to call it. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static void +png_image_skip_unused_chunks(png_structrp png_ptr) +{ + /* Prepare the reader to ignore all recognized chunks whose data will not + * be used, i.e., all chunks recognized by libpng except for those + * involved in basic image reading: + * + * IHDR, PLTE, IDAT, IEND + * + * Or image data handling: + * + * tRNS, bKGD, gAMA, cHRM, sRGB, iCCP and sBIT. + * + * This provides a small performance improvement and eliminates any + * potential vulnerability to security problems in the unused chunks. + */ + { + static PNG_CONST png_byte chunks_to_process[] = { + 98, 75, 71, 68, '\0', /* bKGD */ + 99, 72, 82, 77, '\0', /* cHRM */ + 103, 65, 77, 65, '\0', /* gAMA */ + 105, 67, 67, 80, '\0', /* iCCP */ + 115, 66, 73, 84, '\0', /* sBIT */ + 115, 82, 71, 66, '\0', /* sRGB */ + }; + + /* Ignore unknown chunks and all other chunks except for the + * IHDR, PLTE, tRNS, IDAT, and IEND chunks. + */ + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, + NULL, -1); + + /* But do not ignore image data handling chunks */ + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_AS_DEFAULT, + chunks_to_process, (sizeof chunks_to_process)/5); + } +} + +# define PNG_SKIP_CHUNKS(p) png_image_skip_unused_chunks(p) +#else +# define PNG_SKIP_CHUNKS(p) ((void)0) +#endif /* PNG_HANDLE_AS_UNKNOWN_SUPPORTED */ + +/* The following macro gives the exact rounded answer for all values in the + * range 0..255 (it actually divides by 51.2, but the rounding still generates + * the correct numbers 0..5 + */ +#define PNG_DIV51(v8) (((v8) * 5 + 130) >> 8) + +/* Utility functions to make particular color-maps */ +static void +set_file_encoding(png_image_read_control *display) +{ + png_fixed_point g = display->image->opaque->png_ptr->colorspace.gamma; + if (png_gamma_significant(g)) + { + if (png_gamma_not_sRGB(g)) + { + display->file_encoding = E_FILE; + display->gamma_to_linear = png_reciprocal(g); + } + + else + display->file_encoding = E_sRGB; + } + + else + display->file_encoding = E_LINEAR8; +} + +static unsigned int +decode_gamma(png_image_read_control *display, png_uint_32 value, int encoding) +{ + if (encoding == E_FILE) /* double check */ + encoding = display->file_encoding; + + if (encoding == E_NOTSET) /* must be the file encoding */ + { + set_file_encoding(display); + encoding = display->file_encoding; + } + + switch (encoding) + { + case E_FILE: + value = png_gamma_16bit_correct(value*257, display->gamma_to_linear); + break; + + case E_sRGB: + value = png_sRGB_table[value]; + break; + + case E_LINEAR: + break; + + case E_LINEAR8: + value *= 257; + break; + + default: + png_error(display->image->opaque->png_ptr, + "unexpected encoding (internal error)"); + break; + } + + return value; +} + +static png_uint_32 +png_colormap_compose(png_image_read_control *display, + png_uint_32 foreground, int foreground_encoding, png_uint_32 alpha, + png_uint_32 background, int encoding) +{ + /* The file value is composed on the background, the background has the given + * encoding and so does the result, the file is encoded with E_FILE and the + * file and alpha are 8-bit values. The (output) encoding will always be + * E_LINEAR or E_sRGB. + */ + png_uint_32 f = decode_gamma(display, foreground, foreground_encoding); + png_uint_32 b = decode_gamma(display, background, encoding); + + /* The alpha is always an 8-bit value (it comes from the palette), the value + * scaled by 255 is what PNG_sRGB_FROM_LINEAR requires. + */ + f = f * alpha + b * (255-alpha); + + if (encoding == E_LINEAR) + { + /* Scale to 65535; divide by 255, approximately (in fact this is extremely + * accurate, it divides by 255.00000005937181414556, with no overflow.) + */ + f *= 257; /* Now scaled by 65535 */ + f += f >> 16; + f = (f+32768) >> 16; + } + + else /* E_sRGB */ + f = PNG_sRGB_FROM_LINEAR(f); + + return f; +} + +/* NOTE: E_LINEAR values to this routine must be 16-bit, but E_FILE values must + * be 8-bit. + */ +static void +png_create_colormap_entry(png_image_read_control *display, + png_uint_32 ip, png_uint_32 red, png_uint_32 green, png_uint_32 blue, + png_uint_32 alpha, int encoding) +{ + png_imagep image = display->image; + const int output_encoding = (image->format & PNG_FORMAT_FLAG_LINEAR) ? + E_LINEAR : E_sRGB; + const int convert_to_Y = (image->format & PNG_FORMAT_FLAG_COLOR) == 0 && + (red != green || green != blue); + + if (ip > 255) + png_error(image->opaque->png_ptr, "color-map index out of range"); + + /* Update the cache with whether the file gamma is significantly different + * from sRGB. + */ + if (encoding == E_FILE) + { + if (display->file_encoding == E_NOTSET) + set_file_encoding(display); + + /* Note that the cached value may be E_FILE too, but if it is then the + * gamma_to_linear member has been set. + */ + encoding = display->file_encoding; + } + + if (encoding == E_FILE) + { + png_fixed_point g = display->gamma_to_linear; + + red = png_gamma_16bit_correct(red*257, g); + green = png_gamma_16bit_correct(green*257, g); + blue = png_gamma_16bit_correct(blue*257, g); + + if (convert_to_Y || output_encoding == E_LINEAR) + { + alpha *= 257; + encoding = E_LINEAR; + } + + else + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + encoding = E_sRGB; + } + } + + else if (encoding == E_LINEAR8) + { + /* This encoding occurs quite frequently in test cases because PngSuite + * includes a gAMA 1.0 chunk with most images. + */ + red *= 257; + green *= 257; + blue *= 257; + alpha *= 257; + encoding = E_LINEAR; + } + + else if (encoding == E_sRGB && (convert_to_Y || output_encoding == E_LINEAR)) + { + /* The values are 8-bit sRGB values, but must be converted to 16-bit + * linear. + */ + red = png_sRGB_table[red]; + green = png_sRGB_table[green]; + blue = png_sRGB_table[blue]; + alpha *= 257; + encoding = E_LINEAR; + } + + /* This is set if the color isn't gray but the output is. */ + if (encoding == E_LINEAR) + { + if (convert_to_Y) + { + /* NOTE: these values are copied from png_do_rgb_to_gray */ + png_uint_32 y = (png_uint_32)6968 * red + (png_uint_32)23434 * green + + (png_uint_32)2366 * blue; + + if (output_encoding == E_LINEAR) + y = (y + 16384) >> 15; + + else + { + /* y is scaled by 32768, we need it scaled by 255: */ + y = (y + 128) >> 8; + y *= 255; + y = PNG_sRGB_FROM_LINEAR((y + 64) >> 7); + encoding = E_sRGB; + } + + blue = red = green = y; + } + + else if (output_encoding == E_sRGB) + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + alpha = PNG_DIV257(alpha); + encoding = E_sRGB; + } + } + + if (encoding != output_encoding) + png_error(image->opaque->png_ptr, "bad encoding (internal error)"); + + /* Store the value. */ + { +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int afirst = (image->format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (image->format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int bgr = (image->format & PNG_FORMAT_FLAG_BGR) ? 2 : 0; +# else +# define bgr 0 +# endif + + if (output_encoding == E_LINEAR) + { + png_uint_16p entry = png_voidcast(png_uint_16p, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + /* The linear 16-bit values must be pre-multiplied by the alpha channel + * value, if less than 65535 (this is, effectively, composite on black + * if the alpha channel is removed.) + */ + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_uint_16)alpha; + /* FALL THROUGH */ + + case 3: + if (alpha < 65535) + { + if (alpha > 0) + { + blue = (blue * alpha + 32767U)/65535U; + green = (green * alpha + 32767U)/65535U; + red = (red * alpha + 32767U)/65535U; + } + + else + red = green = blue = 0; + } + entry[afirst + (2 ^ bgr)] = (png_uint_16)blue; + entry[afirst + 1] = (png_uint_16)green; + entry[afirst + bgr] = (png_uint_16)red; + break; + + case 2: + entry[1 ^ afirst] = (png_uint_16)alpha; + /* FALL THROUGH */ + + case 1: + if (alpha < 65535) + { + if (alpha > 0) + green = (green * alpha + 32767U)/65535U; + + else + green = 0; + } + entry[afirst] = (png_uint_16)green; + break; + + default: + break; + } + } + + else /* output encoding is E_sRGB */ + { + png_bytep entry = png_voidcast(png_bytep, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_byte)alpha; + case 3: + entry[afirst + (2 ^ bgr)] = (png_byte)blue; + entry[afirst + 1] = (png_byte)green; + entry[afirst + bgr] = (png_byte)red; + break; + + case 2: + entry[1 ^ afirst] = (png_byte)alpha; + case 1: + entry[afirst] = (png_byte)green; + break; + + default: + break; + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + } +} + +static int +make_gray_file_colormap(png_image_read_control *display) +{ + unsigned int i; + + for (i=0; i<256; ++i) + png_create_colormap_entry(display, i, i, i, i, 255, E_FILE); + + return i; +} + +static int +make_gray_colormap(png_image_read_control *display) +{ + unsigned int i; + + for (i=0; i<256; ++i) + png_create_colormap_entry(display, i, i, i, i, 255, E_sRGB); + + return i; +} +#define PNG_GRAY_COLORMAP_ENTRIES 256 + +static int +make_ga_colormap(png_image_read_control *display) +{ + unsigned int i, a; + + /* Alpha is retained, the output will be a color-map with entries + * selected by six levels of alpha. One transparent entry, 6 gray + * levels for all the intermediate alpha values, leaving 230 entries + * for the opaque grays. The color-map entries are the six values + * [0..5]*51, the GA processing uses PNG_DIV51(value) to find the + * relevant entry. + * + * if (alpha > 229) // opaque + * { + * // The 231 entries are selected to make the math below work: + * base = 0; + * entry = (231 * gray + 128) >> 8; + * } + * else if (alpha < 26) // transparent + * { + * base = 231; + * entry = 0; + * } + * else // partially opaque + * { + * base = 226 + 6 * PNG_DIV51(alpha); + * entry = PNG_DIV51(gray); + * } + */ + i = 0; + while (i < 231) + { + unsigned int gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, 255, E_sRGB); + } + + /* 255 is used here for the component values for consistency with the code + * that undoes premultiplication in pngwrite.c. + */ + png_create_colormap_entry(display, i++, 255, 255, 255, 0, E_sRGB); + + for (a=1; a<5; ++a) + { + unsigned int g; + + for (g=0; g<6; ++g) + png_create_colormap_entry(display, i++, g*51, g*51, g*51, a*51, + E_sRGB); + } + + return i; +} + +#define PNG_GA_COLORMAP_ENTRIES 256 + +static int +make_rgb_colormap(png_image_read_control *display) +{ + unsigned int i, r; + + /* Build a 6x6x6 opaque RGB cube */ + for (i=r=0; r<6; ++r) + { + unsigned int g; + + for (g=0; g<6; ++g) + { + unsigned int b; + + for (b=0; b<6; ++b) + png_create_colormap_entry(display, i++, r*51, g*51, b*51, 255, + E_sRGB); + } + } + + return i; +} + +#define PNG_RGB_COLORMAP_ENTRIES 216 + +/* Return a palette index to the above palette given three 8-bit sRGB values. */ +#define PNG_RGB_INDEX(r,g,b) \ + ((png_byte)(6 * (6 * PNG_DIV51(r) + PNG_DIV51(g)) + PNG_DIV51(b))) + +static int +png_image_read_colormap(png_voidp argument) +{ + png_image_read_control *display = + png_voidcast(png_image_read_control*, argument); + const png_imagep image = display->image; + + const png_structrp png_ptr = image->opaque->png_ptr; + const png_uint_32 output_format = image->format; + const int output_encoding = (output_format & PNG_FORMAT_FLAG_LINEAR) ? + E_LINEAR : E_sRGB; + + unsigned int cmap_entries; + unsigned int output_processing; /* Output processing option */ + unsigned int data_encoding = E_NOTSET; /* Encoding libpng must produce */ + + /* Background information; the background color and the index of this color + * in the color-map if it exists (else 256). + */ + unsigned int background_index = 256; + png_uint_32 back_r, back_g, back_b; + + /* Flags to accumulate things that need to be done to the input. */ + int expand_tRNS = 0; + + /* Exclude the NYI feature of compositing onto a color-mapped buffer; it is + * very difficult to do, the results look awful, and it is difficult to see + * what possible use it is because the application can't control the + * color-map. + */ + if (((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0 || + png_ptr->num_trans > 0) /* alpha in input */ && + ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) /* no alpha in output */) + { + if (output_encoding == E_LINEAR) /* compose on black */ + back_b = back_g = back_r = 0; + + else if (display->background == NULL /* no way to remove it */) + png_error(png_ptr, + "a background color must be supplied to remove alpha/transparency"); + + /* Get a copy of the background color (this avoids repeating the checks + * below.) The encoding is 8-bit sRGB or 16-bit linear, depending on the + * output format. + */ + else + { + back_g = display->background->green; + if (output_format & PNG_FORMAT_FLAG_COLOR) + { + back_r = display->background->red; + back_b = display->background->blue; + } + else + back_b = back_r = back_g; + } + } + + else if (output_encoding == E_LINEAR) + back_b = back_r = back_g = 65535; + + else + back_b = back_r = back_g = 255; + + /* Default the input file gamma if required - this is necessary because + * libpng assumes that if no gamma information is present the data is in the + * output format, but the simplified API deduces the gamma from the input + * format. + */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_GAMMA) == 0) + { + /* Do this directly, not using the png_colorspace functions, to ensure + * that it happens even if the colorspace is invalid (though probably if + * it is the setting will be ignored) Note that the same thing can be + * achieved at the application interface with png_set_gAMA. + */ + if (png_ptr->bit_depth == 16 && + (image->flags & PNG_IMAGE_FLAG_16BIT_sRGB) == 0) + png_ptr->colorspace.gamma = PNG_GAMMA_LINEAR; + + else + png_ptr->colorspace.gamma = PNG_GAMMA_sRGB_INVERSE; + + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + } + + /* Decide what to do based on the PNG color type of the input data. The + * utility function png_create_colormap_entry deals with most aspects of the + * output transformations; this code works out how to produce bytes of + * color-map entries from the original format. + */ + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + if (png_ptr->bit_depth <= 8) + { + /* There at most 256 colors in the output, regardless of + * transparency. + */ + unsigned int step, i, val, trans = 256/*ignore*/, back_alpha = 0; + + cmap_entries = 1U << png_ptr->bit_depth; + if (cmap_entries > image->colormap_entries) + png_error(png_ptr, "gray[8] color-map: too few entries"); + + step = 255 / (cmap_entries - 1); + output_processing = PNG_CMAP_NONE; + + /* If there is a tRNS chunk then this either selects a transparent + * value or, if the output has no alpha, the background color. + */ + if (png_ptr->num_trans > 0) + { + trans = png_ptr->trans_color.gray; + + if ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) + back_alpha = output_encoding == E_LINEAR ? 65535 : 255; + } + + /* png_create_colormap_entry just takes an RGBA and writes the + * corresponding color-map entry using the format from 'image', + * including the required conversion to sRGB or linear as + * appropriate. The input values are always either sRGB (if the + * gamma correction flag is 0) or 0..255 scaled file encoded values + * (if the function must gamma correct them). + */ + for (i=val=0; ibit_depth < 8) + png_set_packing(png_ptr); + } + + else /* bit depth is 16 */ + { + /* The 16-bit input values can be converted directly to 8-bit gamma + * encoded values; however, if a tRNS chunk is present 257 color-map + * entries are required. This means that the extra entry requires + * special processing; add an alpha channel, sacrifice gray level + * 254 and convert transparent (alpha==0) entries to that. + * + * Use libpng to chop the data to 8 bits. Convert it to sRGB at the + * same time to minimize quality loss. If a tRNS chunk is present + * this means libpng must handle it too; otherwise it is impossible + * to do the exact match on the 16-bit value. + * + * If the output has no alpha channel *and* the background color is + * gray then it is possible to let libpng handle the substitution by + * ensuring that the corresponding gray level matches the background + * color exactly. + */ + data_encoding = E_sRGB; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray[16] color-map: too few entries"); + + cmap_entries = make_gray_colormap(display); + + if (png_ptr->num_trans > 0) + { + unsigned int back_alpha; + + if (output_format & PNG_FORMAT_FLAG_ALPHA) + back_alpha = 0; + + else + { + if (back_r == back_g && back_g == back_b) + { + /* Background is gray; no special processing will be + * required. + */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry + * matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, E_LINEAR); + } + + /* The background passed to libpng, however, must be the + * sRGB value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: does this work without expanding tRNS to alpha? + * It should be the color->gray case below apparently + * doesn't. + */ + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + break; + } + + back_alpha = output_encoding == E_LINEAR ? 65535 : 255; + } + + /* output_processing means that the libpng-processed row will be + * 8-bit GA and it has to be processing to single byte color-map + * values. Entry 254 is replaced by either a completely + * transparent entry or by the background color at full + * precision (and the background color is not a simple gray leve + * in this case.) + */ + expand_tRNS = 1; + output_processing = PNG_CMAP_TRANS; + background_index = 254; + + /* And set (overwrite) color-map entry 254 to the actual + * background color at full precision. + */ + png_create_colormap_entry(display, 254, back_r, back_g, back_b, + back_alpha, output_encoding); + } + + else + output_processing = PNG_CMAP_NONE; + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + /* 8-bit or 16-bit PNG with two channels - gray and alpha. A minimum + * of 65536 combinations. If, however, the alpha channel is to be + * removed there are only 256 possibilities if the background is gray. + * (Otherwise there is a subset of the 65536 possibilities defined by + * the triangle between black, white and the background color.) + * + * Reduce 16-bit files to 8-bit and sRGB encode the result. No need to + * worry about tRNS matching - tRNS is ignored if there is an alpha + * channel. + */ + data_encoding = E_sRGB; + + if (output_format & PNG_FORMAT_FLAG_ALPHA) + { + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray+alpha color-map: too few entries"); + + cmap_entries = make_ga_colormap(display); + + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else /* alpha is removed */ + { + /* Alpha must be removed as the PNG data is processed when the + * background is a color because the G and A channels are + * independent and the vector addition (non-parallel vectors) is a + * 2-D problem. + * + * This can be reduced to the same algorithm as above by making a + * colormap containing gray levels (for the opaque grays), a + * background entry (for a transparent pixel) and a set of four six + * level color values, one set for each intermediate alpha value. + * See the comments in make_ga_colormap for how this works in the + * per-pixel processing. + * + * If the background is gray, however, we only need a 256 entry gray + * level color map. It is sufficient to make the entry generated + * for the background color be exactly the color specified. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0 || + (back_r == back_g && back_g == back_b)) + { + /* Background is gray; no special processing will be required. */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray-alpha color-map: too few entries"); + + cmap_entries = make_gray_colormap(display); + + if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, E_LINEAR); + } + + /* The background passed to libpng, however, must be the sRGB + * value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + } + + else + { + png_uint_32 i, a; + + /* This is the same as png_make_ga_colormap, above, except that + * the entries are all opaque. + */ + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "ga-alpha color-map: too few entries"); + + i = 0; + while (i < 231) + { + png_uint_32 gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, + 255, E_sRGB); + } + + /* NOTE: this preserves the full precision of the application + * background color. + */ + background_index = i; + png_create_colormap_entry(display, i++, back_r, back_g, back_b, + output_encoding == E_LINEAR ? 65535U : 255U, output_encoding); + + /* For non-opaque input composite on the sRGB background - this + * requires inverting the encoding for each component. The input + * is still converted to the sRGB encoding because this is a + * reasonable approximate to the logarithmic curve of human + * visual sensitivity, at least over the narrow range which PNG + * represents. Consequently 'G' is always sRGB encoded, while + * 'A' is linear. We need the linear background colors. + */ + if (output_encoding == E_sRGB) /* else already linear */ + { + /* This may produce a value not exactly matching the + * background, but that's ok because these numbers are only + * used when alpha != 0 + */ + back_r = png_sRGB_table[back_r]; + back_g = png_sRGB_table[back_g]; + back_b = png_sRGB_table[back_b]; + } + + for (a=1; a<5; ++a) + { + unsigned int g; + + /* PNG_sRGB_FROM_LINEAR expects a 16-bit linear value scaled + * by an 8-bit alpha value (0..255). + */ + png_uint_32 alpha = 51 * a; + png_uint_32 back_rx = (255-alpha) * back_r; + png_uint_32 back_gx = (255-alpha) * back_g; + png_uint_32 back_bx = (255-alpha) * back_b; + + for (g=0; g<6; ++g) + { + png_uint_32 gray = png_sRGB_table[g*51] * alpha; + + png_create_colormap_entry(display, i++, + PNG_sRGB_FROM_LINEAR(gray + back_rx), + PNG_sRGB_FROM_LINEAR(gray + back_gx), + PNG_sRGB_FROM_LINEAR(gray + back_bx), 255, E_sRGB); + } + } + + cmap_entries = i; + output_processing = PNG_CMAP_GA; + } + } + break; + + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + /* Exclude the case where the output is gray; we can always handle this + * with the cases above. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0) + { + /* The color-map will be grayscale, so we may as well convert the + * input RGB values to a simple grayscale and use the grayscale + * code above. + * + * NOTE: calling this apparently damages the recognition of the + * transparent color in background color handling; call + * png_set_tRNS_to_alpha before png_set_background_fixed. + */ + png_set_rgb_to_gray_fixed(png_ptr, PNG_ERROR_ACTION_NONE, -1, + -1); + data_encoding = E_sRGB; + + /* The output will now be one or two 8-bit gray or gray+alpha + * channels. The more complex case arises when the input has alpha. + */ + if ((png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) && + (output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* Both input and output have an alpha channel, so no background + * processing is required; just map the GA bytes to the right + * color-map entry. + */ + expand_tRNS = 1; + + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[ga] color-map: too few entries"); + + cmap_entries = make_ga_colormap(display); + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else + { + /* Either the input or the output has no alpha channel, so there + * will be no non-opaque pixels in the color-map; it will just be + * grayscale. + */ + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[gray] color-map: too few entries"); + + /* Ideally this code would use libpng to do the gamma correction, + * but if an input alpha channel is to be removed we will hit the + * libpng bug in gamma+compose+rgb-to-gray (the double gamma + * correction bug). Fix this by dropping the gamma correction in + * this case and doing it in the palette; this will result in + * duplicate palette entries, but that's better than the + * alternative of double gamma correction. + */ + if ((png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) && + png_gamma_not_sRGB(png_ptr->colorspace.gamma)) + { + cmap_entries = make_gray_file_colormap(display); + data_encoding = E_FILE; + } + + else + cmap_entries = make_gray_colormap(display); + + /* But if the input has alpha or transparency it must be removed + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + png_color_16 c; + png_uint_32 gray = back_g; + + /* We need to ensure that the application background exists in + * the colormap and that completely transparent pixels map to + * it. Achieve this simply by ensuring that the entry + * selected for the background really is the background color. + */ + if (data_encoding == E_FILE) /* from the fixup above */ + { + /* The app supplied a gray which is in output_encoding, we + * need to convert it to a value of the input (E_FILE) + * encoding then set this palette entry to the required + * output encoding. + */ + if (output_encoding == E_sRGB) + gray = png_sRGB_table[gray]; /* now E_LINEAR */ + + gray = PNG_DIV257(png_gamma_16bit_correct(gray, + png_ptr->colorspace.gamma)); /* now E_FILE */ + + /* And make sure the corresponding palette entry contains + * exactly the required sRGB value. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 0/*unused*/, output_encoding); + } + + else if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 0/*unused*/, E_LINEAR); + } + + /* The background passed to libpng, however, must be the + * output (normally sRGB) value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: the following is apparently a bug in libpng. Without + * it the transparent color recognition in + * png_set_background_fixed seems to go wrong. + */ + expand_tRNS = 1; + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + } + + output_processing = PNG_CMAP_NONE; + } + } + + else /* output is color */ + { + /* We could use png_quantize here so long as there is no transparent + * color or alpha; png_quantize ignores alpha. Easier overall just + * to do it once and using PNG_DIV51 on the 6x6x6 reduced RGB cube. + * Consequently we always want libpng to produce sRGB data. + */ + data_encoding = E_sRGB; + + /* Is there any transparency or alpha? */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + /* Is there alpha in the output too? If so all four channels are + * processed into a special RGB cube with alpha support. + */ + if (output_format & PNG_FORMAT_FLAG_ALPHA) + { + png_uint_32 r; + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb+alpha color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + + /* Add a transparent entry. */ + png_create_colormap_entry(display, cmap_entries, 255, 255, + 255, 0, E_sRGB); + + /* This is stored as the background index for the processing + * algorithm. + */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with alpha 0.5. */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + png_uint_32 g; + + for (g=0; g<256; g = (g << 1) | 0x7f) + { + png_uint_32 b; + + /* This generates components with the values 0, 127 and + * 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + r, g, b, 128, E_sRGB); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else + { + /* Alpha/transparency must be removed. The background must + * exist in the color map (achieved by setting adding it after + * the 666 color-map). If the standard processing code will + * pick up this entry automatically that's all that is + * required; libpng can be called to do the background + * processing. + */ + unsigned int sample_size = + PNG_IMAGE_SAMPLE_SIZE(output_format); + png_uint_32 r, g, b; /* sRGB background */ + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb-alpha color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + + png_create_colormap_entry(display, cmap_entries, back_r, + back_g, back_b, 0/*unused*/, output_encoding); + + if (output_encoding == E_LINEAR) + { + r = PNG_sRGB_FROM_LINEAR(back_r * 255); + g = PNG_sRGB_FROM_LINEAR(back_g * 255); + b = PNG_sRGB_FROM_LINEAR(back_b * 255); + } + + else + { + r = back_r; + g = back_g; + b = back_g; + } + + /* Compare the newly-created color-map entry with the one the + * PNG_CMAP_RGB algorithm will use. If the two entries don't + * match, add the new one and set this as the background + * index. + */ + if (memcmp((png_const_bytep)display->colormap + + sample_size * cmap_entries, + (png_const_bytep)display->colormap + + sample_size * PNG_RGB_INDEX(r,g,b), + sample_size) != 0) + { + /* The background color must be added. */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with created by composing with + * the background at alpha 0.5. + */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + for (g=0; g<256; g = (g << 1) | 0x7f) + { + /* This generates components with the values 0, 127 + * and 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + png_colormap_compose(display, r, E_sRGB, 128, + back_r, output_encoding), + png_colormap_compose(display, g, E_sRGB, 128, + back_g, output_encoding), + png_colormap_compose(display, b, E_sRGB, 128, + back_b, output_encoding), + 0/*unused*/, output_encoding); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else /* background color is in the standard color-map */ + { + png_color_16 c; + + c.index = 0; /*unused*/ + c.red = (png_uint_16)back_r; + c.gray = c.green = (png_uint_16)back_g; + c.blue = (png_uint_16)back_b; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_RGB; + } + } + } + + else /* no alpha or transparency in the input */ + { + /* Alpha in the output is irrelevant, simply map the opaque input + * pixels to the 6x6x6 color-map. + */ + if (PNG_RGB_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + output_processing = PNG_CMAP_RGB; + } + } + break; + + case PNG_COLOR_TYPE_PALETTE: + /* It's already got a color-map. It may be necessary to eliminate the + * tRNS entries though. + */ + { + unsigned int num_trans = png_ptr->num_trans; + png_const_bytep trans = num_trans > 0 ? png_ptr->trans_alpha : NULL; + png_const_colorp colormap = png_ptr->palette; + const int do_background = trans != NULL && + (output_format & PNG_FORMAT_FLAG_ALPHA) == 0; + unsigned int i; + + /* Just in case: */ + if (trans == NULL) + num_trans = 0; + + output_processing = PNG_CMAP_NONE; + data_encoding = E_FILE; /* Don't change from color-map indicies */ + cmap_entries = png_ptr->num_palette; + if (cmap_entries > 256) + cmap_entries = 256; + + if (cmap_entries > image->colormap_entries) + png_error(png_ptr, "palette color-map: too few entries"); + + for (i=0; i < cmap_entries; ++i) + { + if (do_background && i < num_trans && trans[i] < 255) + { + if (trans[i] == 0) + png_create_colormap_entry(display, i, back_r, back_g, + back_b, 0, output_encoding); + + else + { + /* Must compose the PNG file color in the color-map entry + * on the sRGB color in 'back'. + */ + png_create_colormap_entry(display, i, + png_colormap_compose(display, colormap[i].red, E_FILE, + trans[i], back_r, output_encoding), + png_colormap_compose(display, colormap[i].green, E_FILE, + trans[i], back_g, output_encoding), + png_colormap_compose(display, colormap[i].blue, E_FILE, + trans[i], back_b, output_encoding), + output_encoding == E_LINEAR ? trans[i] * 257U : + trans[i], + output_encoding); + } + } + + else + png_create_colormap_entry(display, i, colormap[i].red, + colormap[i].green, colormap[i].blue, + i < num_trans ? trans[i] : 255U, E_FILE/*8-bit*/); + } + + /* The PNG data may have indicies packed in fewer than 8 bits, it + * must be expanded if so. + */ + if (png_ptr->bit_depth < 8) + png_set_packing(png_ptr); + } + break; + + default: + png_error(png_ptr, "invalid PNG color type"); + /*NOT REACHED*/ + break; + } + + /* Now deal with the output processing */ + if (expand_tRNS && png_ptr->num_trans > 0 && + (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) == 0) + png_set_tRNS_to_alpha(png_ptr); + + switch (data_encoding) + { + default: + png_error(png_ptr, "bad data option (internal error)"); + break; + + case E_sRGB: + /* Change to 8-bit sRGB */ + png_set_alpha_mode_fixed(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_sRGB); + /* FALL THROUGH */ + + case E_FILE: + if (png_ptr->bit_depth > 8) + png_set_scale_16(png_ptr); + break; + } + + if (cmap_entries > 256 || cmap_entries > image->colormap_entries) + png_error(png_ptr, "color map overflow (BAD internal error)"); + + image->colormap_entries = cmap_entries; + + /* Double check using the recorded background index */ + switch (output_processing) + { + case PNG_CMAP_NONE: + if (background_index != PNG_CMAP_NONE_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_GA: + if (background_index != PNG_CMAP_GA_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_TRANS: + if (background_index >= cmap_entries || + background_index != PNG_CMAP_TRANS_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB: + if (background_index != PNG_CMAP_RGB_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB_ALPHA: + if (background_index != PNG_CMAP_RGB_ALPHA_BACKGROUND) + goto bad_background; + break; + + default: + png_error(png_ptr, "bad processing option (internal error)"); + + bad_background: + png_error(png_ptr, "bad background index (internal error)"); + } + + display->colormap_processing = output_processing; + + return 1/*ok*/; +} + +/* The final part of the color-map read called from png_image_finish_read. */ +static int +png_image_read_and_map(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + int passes; + + /* Called when the libpng data must be transformed into the color-mapped + * form. There is a local row buffer in display->local and this routine must + * do the interlace handling. + */ + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + passes = 0; + png_error(png_ptr, "unknown interlace type"); + } + + { + png_uint_32 height = image->height; + png_uint_32 width = image->width; + int proc = display->colormap_processing; + png_bytep first_row = png_voidcast(png_bytep, display->first_row); + ptrdiff_t step_row = display->row_bytes; + int pass; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass); + stepx = PNG_PASS_COL_OFFSET(pass); + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = stepy = 1; + } + + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read read the libpng data into the temporary buffer. */ + png_read_row(png_ptr, inrow, NULL); + + /* Now process the row according to the processing option, note + * that the caller verifies that the format of the libpng output + * data is as required. + */ + outrow += startx; + switch (proc) + { + case PNG_CMAP_GA: + for (; outrow < end_row; outrow += stepx) + { + /* The data is always in the PNG order */ + unsigned int gray = *inrow++; + unsigned int alpha = *inrow++; + unsigned int entry; + + /* NOTE: this code is copied as a comment in + * make_ga_colormap above. Please update the + * comment if you change this code! + */ + if (alpha > 229) /* opaque */ + { + entry = (231 * gray + 128) >> 8; + } + else if (alpha < 26) /* transparent */ + { + entry = 231; + } + else /* partially opaque */ + { + entry = 226 + 6 * PNG_DIV51(alpha) + PNG_DIV51(gray); + } + + *outrow = (png_byte)entry; + } + break; + + case PNG_CMAP_TRANS: + for (; outrow < end_row; outrow += stepx) + { + png_byte gray = *inrow++; + png_byte alpha = *inrow++; + + if (alpha == 0) + *outrow = PNG_CMAP_TRANS_BACKGROUND; + + else if (gray != PNG_CMAP_TRANS_BACKGROUND) + *outrow = gray; + + else + *outrow = (png_byte)(PNG_CMAP_TRANS_BACKGROUND+1); + } + break; + + case PNG_CMAP_RGB: + for (; outrow < end_row; outrow += stepx) + { + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], inrow[2]); + inrow += 3; + } + break; + + case PNG_CMAP_RGB_ALPHA: + for (; outrow < end_row; outrow += stepx) + { + unsigned int alpha = inrow[3]; + + /* Because the alpha entries only hold alpha==0.5 values + * split the processing at alpha==0.25 (64) and 0.75 + * (196). + */ + + if (alpha >= 196) + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], + inrow[2]); + + else if (alpha < 64) + *outrow = PNG_CMAP_RGB_ALPHA_BACKGROUND; + + else + { + /* Likewise there are three entries for each of r, g + * and b. We could select the entry by popcount on + * the top two bits on those architectures that + * support it, this is what the code below does, + * crudely. + */ + unsigned int back_i = PNG_CMAP_RGB_ALPHA_BACKGROUND+1; + + /* Here are how the values map: + * + * 0x00 .. 0x3f -> 0 + * 0x40 .. 0xbf -> 1 + * 0xc0 .. 0xff -> 2 + * + * So, as above with the explicit alpha checks, the + * breakpoints are at 64 and 196. + */ + if (inrow[0] & 0x80) back_i += 9; /* red */ + if (inrow[0] & 0x40) back_i += 9; + if (inrow[0] & 0x80) back_i += 3; /* green */ + if (inrow[0] & 0x40) back_i += 3; + if (inrow[0] & 0x80) back_i += 1; /* blue */ + if (inrow[0] & 0x40) back_i += 1; + + *outrow = (png_byte)back_i; + } + + inrow += 4; + } + break; + + default: + break; + } + } + } + } + + return 1; +} + +static int +png_image_read_colormapped(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_controlp control = image->opaque; + png_structrp png_ptr = control->png_ptr; + png_inforp info_ptr = control->info_ptr; + + int passes = 0; /* As a flag */ + + PNG_SKIP_CHUNKS(png_ptr); + + /* Update the 'info' structure and make sure the result is as required; first + * make sure to turn on the interlace handling if it will be required + * (because it can't be turned on *after* the call to png_read_update_info!) + */ + if (display->colormap_processing == PNG_CMAP_NONE) + passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + /* The expected output can be deduced from the colormap_processing option. */ + switch (display->colormap_processing) + { + case PNG_CMAP_NONE: + /* Output must be one channel and one byte per pixel, the output + * encoding can be anything. + */ + if ((info_ptr->color_type == PNG_COLOR_TYPE_PALETTE || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY) && + info_ptr->bit_depth == 8) + break; + + goto bad_output; + + case PNG_CMAP_TRANS: + case PNG_CMAP_GA: + /* Output must be two channels and the 'G' one must be sRGB, the latter + * can be checked with an exact number because it should have been set + * to this number above! + */ + if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 256) + break; + + goto bad_output; + + case PNG_CMAP_RGB: + /* Output must be 8-bit sRGB encoded RGB */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 216) + break; + + goto bad_output; + + case PNG_CMAP_RGB_ALPHA: + /* Output must be 8-bit sRGB encoded RGBA */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 244 /* 216 + 1 + 27 */) + break; + + /* goto bad_output; */ + /* FALL THROUGH */ + + default: + bad_output: + png_error(png_ptr, "bad color-map processing (internal error)"); + } + + /* Now read the rows. Do this here if it is possible to read directly into + * the output buffer, otherwise allocate a local row buffer of the maximum + * size libpng requires and call the relevant processing routine safely. + */ + { + png_voidp first_row = display->buffer; + ptrdiff_t row_bytes = display->row_stride; + + /* The following expression is designed to work correctly whether it gives + * a signed or an unsigned result. + */ + if (row_bytes < 0) + { + char *ptr = png_voidcast(char*, first_row); + ptr += (image->height-1) * (-row_bytes); + first_row = png_voidcast(png_voidp, ptr); + } + + display->first_row = first_row; + display->row_bytes = row_bytes; + } + + if (passes == 0) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_and_map, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else + { + png_alloc_size_t row_bytes = display->row_bytes; + + while (--passes >= 0) + { + png_uint_32 y = image->height; + png_bytep row = png_voidcast(png_bytep, display->first_row); + + while (y-- > 0) + { + png_read_row(png_ptr, row, NULL); + row += row_bytes; + } + } + + return 1; + } +} + +/* Just the row reading part of png_image_read. */ +static int +png_image_read_composite(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + int passes; + + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + passes = 0; + png_error(png_ptr, "unknown interlace type"); + } + + { + png_uint_32 height = image->height; + png_uint_32 width = image->width; + ptrdiff_t step_row = display->row_bytes; + unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1; + int pass; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass) * channels; + stepx = PNG_PASS_COL_OFFSET(pass) * channels; + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = channels; + stepy = 1; + } + + for (; ylocal_row); + png_bytep outrow; + png_const_bytep end_row; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + outrow = png_voidcast(png_bytep, display->first_row); + outrow += y * step_row; + end_row = outrow + width * channels; + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[channels]; + + if (alpha > 0) /* else no change to the output */ + { + unsigned int c; + + for (c=0; cimage; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + png_uint_32 height = image->height; + png_uint_32 width = image->width; + int pass, passes; + + /* Double check the convoluted logic below. We expect to get here with + * libpng doing rgb to gray and gamma correction but background processing + * left to the png_image_read_background function. The rows libpng produce + * might be 8 or 16-bit but should always have two channels; gray plus alpha. + */ + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == 0) + png_error(png_ptr, "lost rgb to gray"); + + if ((png_ptr->transformations & PNG_COMPOSE) != 0) + png_error(png_ptr, "unexpected compose"); + + if (png_get_channels(png_ptr, info_ptr) != 2) + png_error(png_ptr, "lost/gained channels"); + + /* Expect the 8-bit case to always remove the alpha channel */ + if ((image->format & PNG_FORMAT_FLAG_LINEAR) == 0 && + (image->format & PNG_FORMAT_FLAG_ALPHA) != 0) + png_error(png_ptr, "unexpected 8-bit transformation"); + + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + passes = 0; + png_error(png_ptr, "unknown interlace type"); + } + + switch (png_get_bit_depth(png_ptr, info_ptr)) + { + default: + png_error(png_ptr, "unexpected bit depth"); + break; + + case 8: + /* 8-bit sRGB gray values with an alpha channel; the alpha channel is + * to be removed by composing on a backgroundi: either the row if + * display->background is NULL or display->background->green if not. + * Unlike the code above ALPHA_OPTIMIZED has *not* been done. + */ + { + png_bytep first_row = png_voidcast(png_bytep, display->first_row); + ptrdiff_t step_row = display->row_bytes; + + for (pass = 0; pass < passes; ++pass) + { + png_bytep row = png_voidcast(png_bytep, + display->first_row); + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass); + stepx = PNG_PASS_COL_OFFSET(pass); + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = stepy = 1; + } + + if (display->background == NULL) + { + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[1]; + + if (alpha > 0) /* else no change to the output */ + { + png_uint_32 component = inrow[0]; + + if (alpha < 255) /* else just use component */ + { + /* Since PNG_OPTIMIZED_ALPHA was not set it is + * necessary to invert the sRGB transfer + * function and multiply the alpha out. + */ + component = png_sRGB_table[component] * alpha; + component += png_sRGB_table[outrow[0]] * + (255-alpha); + component = PNG_sRGB_FROM_LINEAR(component); + } + + outrow[0] = (png_byte)component; + } + + inrow += 2; /* gray and alpha channel */ + } + } + } + + else /* constant background value */ + { + png_byte background8 = display->background->green; + png_uint_16 background = png_sRGB_table[background8]; + + for (; ylocal_row); + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[1]; + + if (alpha > 0) /* else use background */ + { + png_uint_32 component = inrow[0]; + + if (alpha < 255) /* else just use component */ + { + component = png_sRGB_table[component] * alpha; + component += background * (255-alpha); + component = PNG_sRGB_FROM_LINEAR(component); + } + + outrow[0] = (png_byte)component; + } + + else + outrow[0] = background8; + + inrow += 2; /* gray and alpha channel */ + } + + row += display->row_bytes; + } + } + } + } + break; + + case 16: + /* 16-bit linear with pre-multiplied alpha; the pre-multiplication must + * still be done and, maybe, the alpha channel removed. This code also + * handles the alpha-first option. + */ + { + png_uint_16p first_row = png_voidcast(png_uint_16p, + display->first_row); + /* The division by two is safe because the caller passed in a + * stride which was multiplied by 2 (below) to get row_bytes. + */ + ptrdiff_t step_row = display->row_bytes / 2; + int preserve_alpha = (image->format & PNG_FORMAT_FLAG_ALPHA) != 0; + unsigned int outchannels = 1+preserve_alpha; + int swap_alpha = 0; + + if (preserve_alpha && (image->format & PNG_FORMAT_FLAG_AFIRST)) + swap_alpha = 1; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + /* The 'x' start and step are adjusted to output components here. + */ + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass) * outchannels; + stepx = PNG_PASS_COL_OFFSET(pass) * outchannels; + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = outchannels; + stepy = 1; + } + + for (; ylocal_row), NULL); + inrow = png_voidcast(png_const_uint_16p, display->local_row); + + /* Now do the pre-multiplication on each pixel in this row. + */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_uint_32 component = inrow[0]; + png_uint_16 alpha = inrow[1]; + + if (alpha > 0) /* else 0 */ + { + if (alpha < 65535) /* else just use component */ + { + component *= alpha; + component += 32767; + component /= 65535; + } + } + + else + component = 0; + + outrow[swap_alpha] = (png_uint_16)component; + if (preserve_alpha) + outrow[1 ^ swap_alpha] = alpha; + + inrow += 2; /* components and alpha channel */ + } + } + } + } + break; + } + + return 1; +} + +/* The guts of png_image_finish_read as a png_safe_execute callback. */ +static int +png_image_read_direct(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + + png_uint_32 format = image->format; + int linear = (format & PNG_FORMAT_FLAG_LINEAR) != 0; + int do_local_compose = 0; + int do_local_background = 0; /* to avoid double gamma correction bug */ + int passes = 0; + + /* Add transforms to ensure the correct output format is produced then check + * that the required implementation support is there. Always expand; always + * need 8 bits minimum, no palette and expanded tRNS. + */ + png_set_expand(png_ptr); + + /* Now check the format to see if it was modified. */ + { + png_uint_32 base_format = png_image_format(png_ptr) & + ~PNG_FORMAT_FLAG_COLORMAP /* removed by png_set_expand */; + png_uint_32 change = format ^ base_format; + png_fixed_point output_gamma; + int mode; /* alpha mode */ + + /* Do this first so that we have a record if rgb to gray is happening. */ + if (change & PNG_FORMAT_FLAG_COLOR) + { + /* gray<->color transformation required. */ + if (format & PNG_FORMAT_FLAG_COLOR) + png_set_gray_to_rgb(png_ptr); + + else + { + /* libpng can't do both rgb to gray and + * background/pre-multiplication if there is also significant gamma + * correction, because both operations require linear colors and + * the code only supports one transform doing the gamma correction. + * Handle this by doing the pre-multiplication or background + * operation in this code, if necessary. + * + * TODO: fix this by rewriting pngrtran.c (!) + * + * For the moment (given that fixing this in pngrtran.c is an + * enormous change) 'do_local_background' is used to indicate that + * the problem exists. + */ + if (base_format & PNG_FORMAT_FLAG_ALPHA) + do_local_background = 1/*maybe*/; + + png_set_rgb_to_gray_fixed(png_ptr, PNG_ERROR_ACTION_NONE, + PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT); + } + + change &= ~PNG_FORMAT_FLAG_COLOR; + } + + /* Set the gamma appropriately, linear for 16-bit input, sRGB otherwise. + */ + { + png_fixed_point input_gamma_default; + + if ((base_format & PNG_FORMAT_FLAG_LINEAR) && + (image->flags & PNG_IMAGE_FLAG_16BIT_sRGB) == 0) + input_gamma_default = PNG_GAMMA_LINEAR; + else + input_gamma_default = PNG_DEFAULT_sRGB; + + /* Call png_set_alpha_mode to set the default for the input gamma; the + * output gamma is set by a second call below. + */ + png_set_alpha_mode_fixed(png_ptr, PNG_ALPHA_PNG, input_gamma_default); + } + + if (linear) + { + /* If there *is* an alpha channel in the input it must be multiplied + * out; use PNG_ALPHA_STANDARD, otherwise just use PNG_ALPHA_PNG. + */ + if (base_format & PNG_FORMAT_FLAG_ALPHA) + mode = PNG_ALPHA_STANDARD; /* associated alpha */ + + else + mode = PNG_ALPHA_PNG; + + output_gamma = PNG_GAMMA_LINEAR; + } + + else + { + mode = PNG_ALPHA_PNG; + output_gamma = PNG_DEFAULT_sRGB; + } + + /* If 'do_local_background' is set check for the presence of gamma + * correction; this is part of the work-round for the libpng bug + * described above. + * + * TODO: fix libpng and remove this. + */ + if (do_local_background) + { + png_fixed_point gtest; + + /* This is 'png_gamma_threshold' from pngrtran.c; the test used for + * gamma correction, the screen gamma hasn't been set on png_struct + * yet; it's set below. png_struct::gamma, however, is set to the + * final value. + */ + if (png_muldiv(>est, output_gamma, png_ptr->colorspace.gamma, + PNG_FP_1) && !png_gamma_significant(gtest)) + do_local_background = 0; + + else if (mode == PNG_ALPHA_STANDARD) + { + do_local_background = 2/*required*/; + mode = PNG_ALPHA_PNG; /* prevent libpng doing it */ + } + + /* else leave as 1 for the checks below */ + } + + /* If the bit-depth changes then handle that here. */ + if (change & PNG_FORMAT_FLAG_LINEAR) + { + if (linear /*16-bit output*/) + png_set_expand_16(png_ptr); + + else /* 8-bit output */ + png_set_scale_16(png_ptr); + + change &= ~PNG_FORMAT_FLAG_LINEAR; + } + + /* Now the background/alpha channel changes. */ + if (change & PNG_FORMAT_FLAG_ALPHA) + { + /* Removing an alpha channel requires composition for the 8-bit + * formats; for the 16-bit it is already done, above, by the + * pre-multiplication and the channel just needs to be stripped. + */ + if (base_format & PNG_FORMAT_FLAG_ALPHA) + { + /* If RGB->gray is happening the alpha channel must be left and the + * operation completed locally. + * + * TODO: fix libpng and remove this. + */ + if (do_local_background) + do_local_background = 2/*required*/; + + /* 16-bit output: just remove the channel */ + else if (linear) /* compose on black (well, pre-multiply) */ + png_set_strip_alpha(png_ptr); + + /* 8-bit output: do an appropriate compose */ + else if (display->background != NULL) + { + png_color_16 c; + + c.index = 0; /*unused*/ + c.red = display->background->red; + c.green = display->background->green; + c.blue = display->background->blue; + c.gray = display->background->green; + + /* This is always an 8-bit sRGB value, using the 'green' channel + * for gray is much better than calculating the luminance here; + * we can get off-by-one errors in that calculation relative to + * the app expectations and that will show up in transparent + * pixels. + */ + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + } + + else /* compose on row: implemented below. */ + { + do_local_compose = 1; + /* This leaves the alpha channel in the output, so it has to be + * removed by the code below. Set the encoding to the 'OPTIMIZE' + * one so the code only has to hack on the pixels that require + * composition. + */ + mode = PNG_ALPHA_OPTIMIZED; + } + } + + else /* output needs an alpha channel */ + { + /* This is tricky because it happens before the swap operation has + * been accomplished; however, the swap does *not* swap the added + * alpha channel (weird API), so it must be added in the correct + * place. + */ + png_uint_32 filler; /* opaque filler */ + int where; + + if (linear) + filler = 65535; + + else + filler = 255; + +# ifdef PNG_FORMAT_AFIRST_SUPPORTED + if (format & PNG_FORMAT_FLAG_AFIRST) + { + where = PNG_FILLER_BEFORE; + change &= ~PNG_FORMAT_FLAG_AFIRST; + } + + else +# endif + where = PNG_FILLER_AFTER; + + png_set_add_alpha(png_ptr, filler, where); + } + + /* This stops the (irrelevant) call to swap_alpha below. */ + change &= ~PNG_FORMAT_FLAG_ALPHA; + } + + /* Now set the alpha mode correctly; this is always done, even if there is + * no alpha channel in either the input or the output because it correctly + * sets the output gamma. + */ + png_set_alpha_mode_fixed(png_ptr, mode, output_gamma); + +# ifdef PNG_FORMAT_BGR_SUPPORTED + if (change & PNG_FORMAT_FLAG_BGR) + { + /* Check only the output format; PNG is never BGR; don't do this if + * the output is gray, but fix up the 'format' value in that case. + */ + if (format & PNG_FORMAT_FLAG_COLOR) + png_set_bgr(png_ptr); + + else + format &= ~PNG_FORMAT_FLAG_BGR; + + change &= ~PNG_FORMAT_FLAG_BGR; + } +# endif + +# ifdef PNG_FORMAT_AFIRST_SUPPORTED + if (change & PNG_FORMAT_FLAG_AFIRST) + { + /* Only relevant if there is an alpha channel - it's particularly + * important to handle this correctly because do_local_compose may + * be set above and then libpng will keep the alpha channel for this + * code to remove. + */ + if (format & PNG_FORMAT_FLAG_ALPHA) + { + /* Disable this if doing a local background, + * TODO: remove this when local background is no longer required. + */ + if (do_local_background != 2) + png_set_swap_alpha(png_ptr); + } + + else + format &= ~PNG_FORMAT_FLAG_AFIRST; + + change &= ~PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* If the *output* is 16-bit then we need to check for a byte-swap on this + * architecture. + */ + if (linear) + { + PNG_CONST png_uint_16 le = 0x0001; + + if (*(png_const_bytep)&le) + png_set_swap(png_ptr); + } + + /* If change is not now 0 some transformation is missing - error out. */ + if (change) + png_error(png_ptr, "png_read_image: unsupported transformation"); + } + + PNG_SKIP_CHUNKS(png_ptr); + + /* Update the 'info' structure and make sure the result is as required; first + * make sure to turn on the interlace handling if it will be required + * (because it can't be turned on *after* the call to png_read_update_info!) + * + * TODO: remove the do_local_background fixup below. + */ + if (!do_local_compose && do_local_background != 2) + passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + { + png_uint_32 info_format = 0; + + if (info_ptr->color_type & PNG_COLOR_MASK_COLOR) + info_format |= PNG_FORMAT_FLAG_COLOR; + + if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) + { + /* do_local_compose removes this channel below. */ + if (!do_local_compose) + { + /* do_local_background does the same if required. */ + if (do_local_background != 2 || + (format & PNG_FORMAT_FLAG_ALPHA) != 0) + info_format |= PNG_FORMAT_FLAG_ALPHA; + } + } + + else if (do_local_compose) /* internal error */ + png_error(png_ptr, "png_image_read: alpha channel lost"); + + if (info_ptr->bit_depth == 16) + info_format |= PNG_FORMAT_FLAG_LINEAR; + +# ifdef PNG_FORMAT_BGR_SUPPORTED + if (png_ptr->transformations & PNG_BGR) + info_format |= PNG_FORMAT_FLAG_BGR; +# endif + +# ifdef PNG_FORMAT_AFIRST_SUPPORTED + if (do_local_background == 2) + { + if (format & PNG_FORMAT_FLAG_AFIRST) + info_format |= PNG_FORMAT_FLAG_AFIRST; + } + + if ((png_ptr->transformations & PNG_SWAP_ALPHA) != 0 || + ((png_ptr->transformations & PNG_ADD_ALPHA) != 0 && + (png_ptr->flags & PNG_FLAG_FILLER_AFTER) == 0)) + { + if (do_local_background == 2) + png_error(png_ptr, "unexpected alpha swap transformation"); + + info_format |= PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* This is actually an internal error. */ + if (info_format != format) + png_error(png_ptr, "png_read_image: invalid transformations"); + } + + /* Now read the rows. If do_local_compose is set then it is necessary to use + * a local row buffer. The output will be GA, RGBA or BGRA and must be + * converted to G, RGB or BGR as appropriate. The 'local_row' member of the + * display acts as a flag. + */ + { + png_voidp first_row = display->buffer; + ptrdiff_t row_bytes = display->row_stride; + + if (linear) + row_bytes *= 2; + + /* The following expression is designed to work correctly whether it gives + * a signed or an unsigned result. + */ + if (row_bytes < 0) + { + char *ptr = png_voidcast(char*, first_row); + ptr += (image->height-1) * (-row_bytes); + first_row = png_voidcast(png_voidp, ptr); + } + + display->first_row = first_row; + display->row_bytes = row_bytes; + } + + if (do_local_compose) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_composite, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else if (do_local_background == 2) + { + int result; + png_voidp row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_background, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else + { + png_alloc_size_t row_bytes = display->row_bytes; + + while (--passes >= 0) + { + png_uint_32 y = image->height; + png_bytep row = png_voidcast(png_bytep, display->first_row); + + while (y-- > 0) + { + png_read_row(png_ptr, row, NULL); + row += row_bytes; + } + } + + return 1; + } +} + +int PNGAPI +png_image_finish_read(png_imagep image, png_const_colorp background, + void *buffer, png_int_32 row_stride, void *colormap) +{ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + png_uint_32 check; + + if (row_stride == 0) + row_stride = PNG_IMAGE_ROW_STRIDE(*image); + + if (row_stride < 0) + check = -row_stride; + + else + check = row_stride; + + if (image->opaque != NULL && buffer != NULL && + check >= PNG_IMAGE_ROW_STRIDE(*image)) + { + if ((image->format & PNG_FORMAT_FLAG_COLORMAP) == 0 || + (image->colormap_entries > 0 && colormap != NULL)) + { + int result; + png_image_read_control display; + + memset(&display, 0, (sizeof display)); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.background = background; + display.local_row = NULL; + + /* Choose the correct 'end' routine; for the color-map case all the + * setup has already been done. + */ + if (image->format & PNG_FORMAT_FLAG_COLORMAP) + result = + png_safe_execute(image, png_image_read_colormap, &display) && + png_safe_execute(image, png_image_read_colormapped, &display); + + else + result = + png_safe_execute(image, png_image_read_direct, &display); + + png_image_free(image); + return result; + } + + else + return png_image_error(image, + "png_image_finish_read[color-map]: no color-map"); + } + + else + return png_image_error(image, + "png_image_finish_read: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_finish_read: damaged PNG_IMAGE_VERSION"); + + return 0; +} + +#endif /* PNG_SIMPLIFIED_READ_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrio.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrio.c new file mode 100644 index 0000000..2b9c103 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrio.c @@ -0,0 +1,118 @@ + +/* pngrio.c - functions for data input + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all input. Users who need + * special handling are expected to write a function that has the same + * arguments as this and performs a similar function, but that possibly + * has a different input method. Note that you shouldn't change this + * function, but rather write a replacement function and then make + * libpng use it at run time with png_set_read_fn(...). + */ + +#include "pngpriv.h" + +#ifdef PNG_READ_SUPPORTED + +/* Read the data from whatever input you are using. The default routine + * reads from a file pointer. Note that this routine sometimes gets called + * with very small lengths, so you should implement some kind of simple + * buffering if you are using unbuffered reads. This should never be asked + * to read more then 64K on a 16 bit machine. + */ +void /* PRIVATE */ +png_read_data(png_structrp png_ptr, png_bytep data, png_size_t length) +{ + png_debug1(4, "reading %d bytes", (int)length); + + if (png_ptr->read_data_fn != NULL) + (*(png_ptr->read_data_fn))(png_ptr, data, length); + + else + png_error(png_ptr, "Call to NULL read function"); +} + +#ifdef PNG_STDIO_SUPPORTED +/* This is the function that does the actual reading of data. If you are + * not reading from a standard C stream, you should create a replacement + * read_data function and use it at run time with png_set_read_fn(), rather + * than changing the library. + */ +void PNGCBAPI +png_default_read_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + png_size_t check; + + if (png_ptr == NULL) + return; + + /* fread() returns 0 on error, so it is OK to store this in a png_size_t + * instead of an int, which is what fread() actually returns. + */ + check = fread(data, 1, length, png_voidcast(png_FILE_p, png_ptr->io_ptr)); + + if (check != length) + png_error(png_ptr, "Read Error"); +} +#endif + +/* This function allows the application to supply a new input function + * for libpng if standard C streams aren't being used. + * + * This function takes as its arguments: + * + * png_ptr - pointer to a png input data structure + * + * io_ptr - pointer to user supplied structure containing info about + * the input functions. May be NULL. + * + * read_data_fn - pointer to a new input function that takes as its + * arguments a pointer to a png_struct, a pointer to + * a location where input data can be stored, and a 32-bit + * unsigned int that is the number of bytes to be read. + * To exit and output any fatal error messages the new write + * function should call png_error(png_ptr, "Error msg"). + * May be NULL, in which case libpng's default function will + * be used. + */ +void PNGAPI +png_set_read_fn(png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr read_data_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = io_ptr; + +#ifdef PNG_STDIO_SUPPORTED + if (read_data_fn != NULL) + png_ptr->read_data_fn = read_data_fn; + + else + png_ptr->read_data_fn = png_default_read_data; +#else + png_ptr->read_data_fn = read_data_fn; +#endif + + /* It is an error to write to a read device */ + if (png_ptr->write_data_fn != NULL) + { + png_ptr->write_data_fn = NULL; + png_warning(png_ptr, + "Can't set both read_data_fn and write_data_fn in the" + " same structure"); + } + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->output_flush_fn = NULL; +#endif +} +#endif /* PNG_READ_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrtran.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrtran.c new file mode 100644 index 0000000..034b9c3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrtran.c @@ -0,0 +1,5102 @@ + +/* pngrtran.c - transforms the data in a row for PNG readers + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains functions optionally called by an application + * in order to tell libpng how to handle data when reading a PNG. + * Transformations that are used in both reading and writing are + * in pngtrans.c. + */ + +#include "pngpriv.h" + +#ifdef PNG_READ_SUPPORTED + +/* Set the action on getting a CRC error for an ancillary or critical chunk. */ +void PNGAPI +png_set_crc_action(png_structrp png_ptr, int crit_action, int ancil_action) +{ + png_debug(1, "in png_set_crc_action"); + + if (png_ptr == NULL) + return; + + /* Tell libpng how we react to CRC errors in critical chunks */ + switch (crit_action) + { + case PNG_CRC_NO_CHANGE: /* Leave setting as is */ + break; + + case PNG_CRC_WARN_USE: /* Warn/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE; + break; + + case PNG_CRC_QUIET_USE: /* Quiet/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + png_ptr->flags |= PNG_FLAG_CRC_CRITICAL_USE | + PNG_FLAG_CRC_CRITICAL_IGNORE; + break; + + case PNG_CRC_WARN_DISCARD: /* Not a valid action for critical data */ + png_warning(png_ptr, + "Can't discard critical data on CRC error"); + case PNG_CRC_ERROR_QUIT: /* Error/quit */ + + case PNG_CRC_DEFAULT: + default: + png_ptr->flags &= ~PNG_FLAG_CRC_CRITICAL_MASK; + break; + } + + /* Tell libpng how we react to CRC errors in ancillary chunks */ + switch (ancil_action) + { + case PNG_CRC_NO_CHANGE: /* Leave setting as is */ + break; + + case PNG_CRC_WARN_USE: /* Warn/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE; + break; + + case PNG_CRC_QUIET_USE: /* Quiet/use data */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_USE | + PNG_FLAG_CRC_ANCILLARY_NOWARN; + break; + + case PNG_CRC_ERROR_QUIT: /* Error/quit */ + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + png_ptr->flags |= PNG_FLAG_CRC_ANCILLARY_NOWARN; + break; + + case PNG_CRC_WARN_DISCARD: /* Warn/discard data */ + + case PNG_CRC_DEFAULT: + default: + png_ptr->flags &= ~PNG_FLAG_CRC_ANCILLARY_MASK; + break; + } +} + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +/* Is it OK to set a transformation now? Only if png_start_read_image or + * png_read_update_info have not been called. It is not necessary for the IHDR + * to have been read in all cases, the parameter allows for this check too. + */ +static int +png_rtran_ok(png_structrp png_ptr, int need_IHDR) +{ + if (png_ptr != NULL) + { + if (png_ptr->flags & PNG_FLAG_ROW_INIT) + png_app_error(png_ptr, + "invalid after png_start_read_image or png_read_update_info"); + + else if (need_IHDR && (png_ptr->mode & PNG_HAVE_IHDR) == 0) + png_app_error(png_ptr, "invalid before the PNG header has been read"); + + else + { + /* Turn on failure to initialize correctly for all transforms. */ + png_ptr->flags |= PNG_FLAG_DETECT_UNINITIALIZED; + + return 1; /* Ok */ + } + } + + return 0; /* no png_error possible! */ +} +#endif + +#ifdef PNG_READ_BACKGROUND_SUPPORTED +/* Handle alpha and tRNS via a background color */ +void PNGFAPI +png_set_background_fixed(png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, png_fixed_point background_gamma) +{ + png_debug(1, "in png_set_background_fixed"); + + if (!png_rtran_ok(png_ptr, 0) || background_color == NULL) + return; + + if (background_gamma_code == PNG_BACKGROUND_GAMMA_UNKNOWN) + { + png_warning(png_ptr, "Application must supply a known background gamma"); + return; + } + + png_ptr->transformations |= PNG_COMPOSE | PNG_STRIP_ALPHA; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + png_ptr->background = *background_color; + png_ptr->background_gamma = background_gamma; + png_ptr->background_gamma_type = (png_byte)(background_gamma_code); + if (need_expand) + png_ptr->transformations |= PNG_BACKGROUND_EXPAND; + else + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_background(png_structrp png_ptr, + png_const_color_16p background_color, int background_gamma_code, + int need_expand, double background_gamma) +{ + png_set_background_fixed(png_ptr, background_color, background_gamma_code, + need_expand, png_fixed(png_ptr, background_gamma, "png_set_background")); +} +# endif /* FLOATING_POINT */ +#endif /* READ_BACKGROUND */ + +/* Scale 16-bit depth files to 8-bit depth. If both of these are set then the + * one that pngrtran does first (scale) happens. This is necessary to allow the + * TRANSFORM and API behavior to be somewhat consistent, and it's simpler. + */ +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +void PNGAPI +png_set_scale_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_scale_16"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= PNG_SCALE_16_TO_8; +} +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +/* Chop 16-bit depth files to 8-bit depth */ +void PNGAPI +png_set_strip_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_strip_16"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= PNG_16_TO_8; +} +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED +void PNGAPI +png_set_strip_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_strip_alpha"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= PNG_STRIP_ALPHA; +} +#endif + +#if defined(PNG_READ_ALPHA_MODE_SUPPORTED) || defined(PNG_READ_GAMMA_SUPPORTED) +static png_fixed_point +translate_gamma_flags(png_structrp png_ptr, png_fixed_point output_gamma, + int is_screen) +{ + /* Check for flag values. The main reason for having the old Mac value as a + * flag is that it is pretty near impossible to work out what the correct + * value is from Apple documentation - a working Mac system is needed to + * discover the value! + */ + if (output_gamma == PNG_DEFAULT_sRGB || + output_gamma == PNG_FP_1 / PNG_DEFAULT_sRGB) + { + /* If there is no sRGB support this just sets the gamma to the standard + * sRGB value. (This is a side effect of using this function!) + */ +# ifdef PNG_READ_sRGB_SUPPORTED + png_ptr->flags |= PNG_FLAG_ASSUME_sRGB; +# endif + if (is_screen) + output_gamma = PNG_GAMMA_sRGB; + else + output_gamma = PNG_GAMMA_sRGB_INVERSE; + } + + else if (output_gamma == PNG_GAMMA_MAC_18 || + output_gamma == PNG_FP_1 / PNG_GAMMA_MAC_18) + { + if (is_screen) + output_gamma = PNG_GAMMA_MAC_OLD; + else + output_gamma = PNG_GAMMA_MAC_INVERSE; + } + + return output_gamma; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +static png_fixed_point +convert_gamma_value(png_structrp png_ptr, double output_gamma) +{ + /* The following silently ignores cases where fixed point (times 100,000) + * gamma values are passed to the floating point API. This is safe and it + * means the fixed point constants work just fine with the floating point + * API. The alternative would just lead to undetected errors and spurious + * bug reports. Negative values fail inside the _fixed API unless they + * correspond to the flag values. + */ + if (output_gamma > 0 && output_gamma < 128) + output_gamma *= PNG_FP_1; + + /* This preserves -1 and -2 exactly: */ + output_gamma = floor(output_gamma + .5); + + if (output_gamma > PNG_FP_MAX || output_gamma < PNG_FP_MIN) + png_fixed_error(png_ptr, "gamma value"); + + return (png_fixed_point)output_gamma; +} +# endif +#endif /* READ_ALPHA_MODE || READ_GAMMA */ + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +void PNGFAPI +png_set_alpha_mode_fixed(png_structrp png_ptr, int mode, + png_fixed_point output_gamma) +{ + int compose = 0; + png_fixed_point file_gamma; + + png_debug(1, "in png_set_alpha_mode"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + output_gamma = translate_gamma_flags(png_ptr, output_gamma, 1/*screen*/); + + /* Validate the value to ensure it is in a reasonable range. The value + * is expected to be 1 or greater, but this range test allows for some + * viewing correction values. The intent is to weed out users of this API + * who use the inverse of the gamma value accidentally! Since some of these + * values are reasonable this may have to be changed. + */ + if (output_gamma < 70000 || output_gamma > 300000) + png_error(png_ptr, "output gamma out of expected range"); + + /* The default file gamma is the inverse of the output gamma; the output + * gamma may be changed below so get the file value first: + */ + file_gamma = png_reciprocal(output_gamma); + + /* There are really 8 possibilities here, composed of any combination + * of: + * + * premultiply the color channels + * do not encode non-opaque pixels + * encode the alpha as well as the color channels + * + * The differences disappear if the input/output ('screen') gamma is 1.0, + * because then the encoding is a no-op and there is only the choice of + * premultiplying the color channels or not. + * + * png_set_alpha_mode and png_set_background interact because both use + * png_compose to do the work. Calling both is only useful when + * png_set_alpha_mode is used to set the default mode - PNG_ALPHA_PNG - along + * with a default gamma value. Otherwise PNG_COMPOSE must not be set. + */ + switch (mode) + { + case PNG_ALPHA_PNG: /* default: png standard */ + /* No compose, but it may be set by png_set_background! */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + case PNG_ALPHA_ASSOCIATED: /* color channels premultiplied */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + /* The output is linear: */ + output_gamma = PNG_FP_1; + break; + + case PNG_ALPHA_OPTIMIZED: /* associated, non-opaque pixels linear */ + compose = 1; + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags |= PNG_FLAG_OPTIMIZE_ALPHA; + /* output_gamma records the encoding of opaque pixels! */ + break; + + case PNG_ALPHA_BROKEN: /* associated, non-linear, alpha encoded */ + compose = 1; + png_ptr->transformations |= PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + break; + + default: + png_error(png_ptr, "invalid alpha mode"); + } + + /* Only set the default gamma if the file gamma has not been set (this has + * the side effect that the gamma in a second call to png_set_alpha_mode will + * be ignored.) + */ + if (png_ptr->colorspace.gamma == 0) + { + png_ptr->colorspace.gamma = file_gamma; + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + } + + /* But always set the output gamma: */ + png_ptr->screen_gamma = output_gamma; + + /* Finally, if pre-multiplying, set the background fields to achieve the + * desired result. + */ + if (compose) + { + /* And obtain alpha pre-multiplication by composing on black: */ + memset(&png_ptr->background, 0, (sizeof png_ptr->background)); + png_ptr->background_gamma = png_ptr->colorspace.gamma; /* just in case */ + png_ptr->background_gamma_type = PNG_BACKGROUND_GAMMA_FILE; + png_ptr->transformations &= ~PNG_BACKGROUND_EXPAND; + + if (png_ptr->transformations & PNG_COMPOSE) + png_error(png_ptr, + "conflicting calls to set alpha mode and background"); + + png_ptr->transformations |= PNG_COMPOSE; + } +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_alpha_mode(png_structrp png_ptr, int mode, double output_gamma) +{ + png_set_alpha_mode_fixed(png_ptr, mode, convert_gamma_value(png_ptr, + output_gamma)); +} +# endif +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* Dither file to 8-bit. Supply a palette, the current number + * of elements in the palette, the maximum number of elements + * allowed, and a histogram if possible. If the current number + * of colors is greater then the maximum number, the palette will be + * modified to fit in the maximum number. "full_quantize" indicates + * whether we need a quantizing cube set up for RGB images, or if we + * simply are reducing the number of colors in a paletted image. + */ + +typedef struct png_dsort_struct +{ + struct png_dsort_struct * next; + png_byte left; + png_byte right; +} png_dsort; +typedef png_dsort * png_dsortp; +typedef png_dsort * * png_dsortpp; + +void PNGAPI +png_set_quantize(png_structrp png_ptr, png_colorp palette, + int num_palette, int maximum_colors, png_const_uint_16p histogram, + int full_quantize) +{ + png_debug(1, "in png_set_quantize"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= PNG_QUANTIZE; + + if (!full_quantize) + { + int i; + + png_ptr->quantize_index = (png_bytep)png_malloc(png_ptr, + (png_uint_32)(num_palette * (sizeof (png_byte)))); + for (i = 0; i < num_palette; i++) + png_ptr->quantize_index[i] = (png_byte)i; + } + + if (num_palette > maximum_colors) + { + if (histogram != NULL) + { + /* This is easy enough, just throw out the least used colors. + * Perhaps not the best solution, but good enough. + */ + + int i; + + /* Initialize an array to sort colors */ + png_ptr->quantize_sort = (png_bytep)png_malloc(png_ptr, + (png_uint_32)(num_palette * (sizeof (png_byte)))); + + /* Initialize the quantize_sort array */ + for (i = 0; i < num_palette; i++) + png_ptr->quantize_sort[i] = (png_byte)i; + + /* Find the least used palette entries by starting a + * bubble sort, and running it until we have sorted + * out enough colors. Note that we don't care about + * sorting all the colors, just finding which are + * least used. + */ + + for (i = num_palette - 1; i >= maximum_colors; i--) + { + int done; /* To stop early if the list is pre-sorted */ + int j; + + done = 1; + for (j = 0; j < i; j++) + { + if (histogram[png_ptr->quantize_sort[j]] + < histogram[png_ptr->quantize_sort[j + 1]]) + { + png_byte t; + + t = png_ptr->quantize_sort[j]; + png_ptr->quantize_sort[j] = png_ptr->quantize_sort[j + 1]; + png_ptr->quantize_sort[j + 1] = t; + done = 0; + } + } + + if (done) + break; + } + + /* Swap the palette around, and set up a table, if necessary */ + if (full_quantize) + { + int j = num_palette; + + /* Put all the useful colors within the max, but don't + * move the others. + */ + for (i = 0; i < maximum_colors; i++) + { + if ((int)png_ptr->quantize_sort[i] >= maximum_colors) + { + do + j--; + while ((int)png_ptr->quantize_sort[j] >= maximum_colors); + + palette[i] = palette[j]; + } + } + } + else + { + int j = num_palette; + + /* Move all the used colors inside the max limit, and + * develop a translation table. + */ + for (i = 0; i < maximum_colors; i++) + { + /* Only move the colors we need to */ + if ((int)png_ptr->quantize_sort[i] >= maximum_colors) + { + png_color tmp_color; + + do + j--; + while ((int)png_ptr->quantize_sort[j] >= maximum_colors); + + tmp_color = palette[j]; + palette[j] = palette[i]; + palette[i] = tmp_color; + /* Indicate where the color went */ + png_ptr->quantize_index[j] = (png_byte)i; + png_ptr->quantize_index[i] = (png_byte)j; + } + } + + /* Find closest color for those colors we are not using */ + for (i = 0; i < num_palette; i++) + { + if ((int)png_ptr->quantize_index[i] >= maximum_colors) + { + int min_d, k, min_k, d_index; + + /* Find the closest color to one we threw out */ + d_index = png_ptr->quantize_index[i]; + min_d = PNG_COLOR_DIST(palette[d_index], palette[0]); + for (k = 1, min_k = 0; k < maximum_colors; k++) + { + int d; + + d = PNG_COLOR_DIST(palette[d_index], palette[k]); + + if (d < min_d) + { + min_d = d; + min_k = k; + } + } + /* Point to closest color */ + png_ptr->quantize_index[i] = (png_byte)min_k; + } + } + } + png_free(png_ptr, png_ptr->quantize_sort); + png_ptr->quantize_sort = NULL; + } + else + { + /* This is much harder to do simply (and quickly). Perhaps + * we need to go through a median cut routine, but those + * don't always behave themselves with only a few colors + * as input. So we will just find the closest two colors, + * and throw out one of them (chosen somewhat randomly). + * [We don't understand this at all, so if someone wants to + * work on improving it, be our guest - AED, GRP] + */ + int i; + int max_d; + int num_new_palette; + png_dsortp t; + png_dsortpp hash; + + t = NULL; + + /* Initialize palette index arrays */ + png_ptr->index_to_palette = (png_bytep)png_malloc(png_ptr, + (png_uint_32)(num_palette * (sizeof (png_byte)))); + png_ptr->palette_to_index = (png_bytep)png_malloc(png_ptr, + (png_uint_32)(num_palette * (sizeof (png_byte)))); + + /* Initialize the sort array */ + for (i = 0; i < num_palette; i++) + { + png_ptr->index_to_palette[i] = (png_byte)i; + png_ptr->palette_to_index[i] = (png_byte)i; + } + + hash = (png_dsortpp)png_calloc(png_ptr, (png_uint_32)(769 * + (sizeof (png_dsortp)))); + + num_new_palette = num_palette; + + /* Initial wild guess at how far apart the farthest pixel + * pair we will be eliminating will be. Larger + * numbers mean more areas will be allocated, Smaller + * numbers run the risk of not saving enough data, and + * having to do this all over again. + * + * I have not done extensive checking on this number. + */ + max_d = 96; + + while (num_new_palette > maximum_colors) + { + for (i = 0; i < num_new_palette - 1; i++) + { + int j; + + for (j = i + 1; j < num_new_palette; j++) + { + int d; + + d = PNG_COLOR_DIST(palette[i], palette[j]); + + if (d <= max_d) + { + + t = (png_dsortp)png_malloc_warn(png_ptr, + (png_uint_32)(sizeof (png_dsort))); + + if (t == NULL) + break; + + t->next = hash[d]; + t->left = (png_byte)i; + t->right = (png_byte)j; + hash[d] = t; + } + } + if (t == NULL) + break; + } + + if (t != NULL) + for (i = 0; i <= max_d; i++) + { + if (hash[i] != NULL) + { + png_dsortp p; + + for (p = hash[i]; p; p = p->next) + { + if ((int)png_ptr->index_to_palette[p->left] + < num_new_palette && + (int)png_ptr->index_to_palette[p->right] + < num_new_palette) + { + int j, next_j; + + if (num_new_palette & 0x01) + { + j = p->left; + next_j = p->right; + } + else + { + j = p->right; + next_j = p->left; + } + + num_new_palette--; + palette[png_ptr->index_to_palette[j]] + = palette[num_new_palette]; + if (!full_quantize) + { + int k; + + for (k = 0; k < num_palette; k++) + { + if (png_ptr->quantize_index[k] == + png_ptr->index_to_palette[j]) + png_ptr->quantize_index[k] = + png_ptr->index_to_palette[next_j]; + + if ((int)png_ptr->quantize_index[k] == + num_new_palette) + png_ptr->quantize_index[k] = + png_ptr->index_to_palette[j]; + } + } + + png_ptr->index_to_palette[png_ptr->palette_to_index + [num_new_palette]] = png_ptr->index_to_palette[j]; + + png_ptr->palette_to_index[png_ptr->index_to_palette[j]] + = png_ptr->palette_to_index[num_new_palette]; + + png_ptr->index_to_palette[j] = + (png_byte)num_new_palette; + + png_ptr->palette_to_index[num_new_palette] = + (png_byte)j; + } + if (num_new_palette <= maximum_colors) + break; + } + if (num_new_palette <= maximum_colors) + break; + } + } + + for (i = 0; i < 769; i++) + { + if (hash[i] != NULL) + { + png_dsortp p = hash[i]; + while (p) + { + t = p->next; + png_free(png_ptr, p); + p = t; + } + } + hash[i] = 0; + } + max_d += 96; + } + png_free(png_ptr, hash); + png_free(png_ptr, png_ptr->palette_to_index); + png_free(png_ptr, png_ptr->index_to_palette); + png_ptr->palette_to_index = NULL; + png_ptr->index_to_palette = NULL; + } + num_palette = maximum_colors; + } + if (png_ptr->palette == NULL) + { + png_ptr->palette = palette; + } + png_ptr->num_palette = (png_uint_16)num_palette; + + if (full_quantize) + { + int i; + png_bytep distance; + int total_bits = PNG_QUANTIZE_RED_BITS + PNG_QUANTIZE_GREEN_BITS + + PNG_QUANTIZE_BLUE_BITS; + int num_red = (1 << PNG_QUANTIZE_RED_BITS); + int num_green = (1 << PNG_QUANTIZE_GREEN_BITS); + int num_blue = (1 << PNG_QUANTIZE_BLUE_BITS); + png_size_t num_entries = ((png_size_t)1 << total_bits); + + png_ptr->palette_lookup = (png_bytep)png_calloc(png_ptr, + (png_uint_32)(num_entries * (sizeof (png_byte)))); + + distance = (png_bytep)png_malloc(png_ptr, (png_uint_32)(num_entries * + (sizeof (png_byte)))); + + memset(distance, 0xff, num_entries * (sizeof (png_byte))); + + for (i = 0; i < num_palette; i++) + { + int ir, ig, ib; + int r = (palette[i].red >> (8 - PNG_QUANTIZE_RED_BITS)); + int g = (palette[i].green >> (8 - PNG_QUANTIZE_GREEN_BITS)); + int b = (palette[i].blue >> (8 - PNG_QUANTIZE_BLUE_BITS)); + + for (ir = 0; ir < num_red; ir++) + { + /* int dr = abs(ir - r); */ + int dr = ((ir > r) ? ir - r : r - ir); + int index_r = (ir << (PNG_QUANTIZE_BLUE_BITS + + PNG_QUANTIZE_GREEN_BITS)); + + for (ig = 0; ig < num_green; ig++) + { + /* int dg = abs(ig - g); */ + int dg = ((ig > g) ? ig - g : g - ig); + int dt = dr + dg; + int dm = ((dr > dg) ? dr : dg); + int index_g = index_r | (ig << PNG_QUANTIZE_BLUE_BITS); + + for (ib = 0; ib < num_blue; ib++) + { + int d_index = index_g | ib; + /* int db = abs(ib - b); */ + int db = ((ib > b) ? ib - b : b - ib); + int dmax = ((dm > db) ? dm : db); + int d = dmax + dt + db; + + if (d < (int)distance[d_index]) + { + distance[d_index] = (png_byte)d; + png_ptr->palette_lookup[d_index] = (png_byte)i; + } + } + } + } + } + + png_free(png_ptr, distance); + } +} +#endif /* PNG_READ_QUANTIZE_SUPPORTED */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +void PNGFAPI +png_set_gamma_fixed(png_structrp png_ptr, png_fixed_point scrn_gamma, + png_fixed_point file_gamma) +{ + png_debug(1, "in png_set_gamma_fixed"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + /* New in libpng-1.5.4 - reserve particular negative values as flags. */ + scrn_gamma = translate_gamma_flags(png_ptr, scrn_gamma, 1/*screen*/); + file_gamma = translate_gamma_flags(png_ptr, file_gamma, 0/*file*/); + + /* Checking the gamma values for being >0 was added in 1.5.4 along with the + * premultiplied alpha support; this actually hides an undocumented feature + * of the previous implementation which allowed gamma processing to be + * disabled in background handling. There is no evidence (so far) that this + * was being used; however, png_set_background itself accepted and must still + * accept '0' for the gamma value it takes, because it isn't always used. + * + * Since this is an API change (albeit a very minor one that removes an + * undocumented API feature) the following checks were only enabled in + * libpng-1.6.0. + */ + if (file_gamma <= 0) + png_error(png_ptr, "invalid file gamma in png_set_gamma"); + + if (scrn_gamma <= 0) + png_error(png_ptr, "invalid screen gamma in png_set_gamma"); + + /* Set the gamma values unconditionally - this overrides the value in the PNG + * file if a gAMA chunk was present. png_set_alpha_mode provides a + * different, easier, way to default the file gamma. + */ + png_ptr->colorspace.gamma = file_gamma; + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + png_ptr->screen_gamma = scrn_gamma; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_gamma(png_structrp png_ptr, double scrn_gamma, double file_gamma) +{ + png_set_gamma_fixed(png_ptr, convert_gamma_value(png_ptr, scrn_gamma), + convert_gamma_value(png_ptr, file_gamma)); +} +# endif /* FLOATING_POINT_SUPPORTED */ +#endif /* READ_GAMMA */ + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expand paletted images to RGB, expand grayscale images of + * less than 8-bit depth to 8-bit depth, and expand tRNS chunks + * to alpha channels. + */ +void PNGAPI +png_set_expand(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} + +/* GRR 19990627: the following three functions currently are identical + * to png_set_expand(). However, it is entirely reasonable that someone + * might wish to expand an indexed image to RGB but *not* expand a single, + * fully transparent palette entry to a full alpha channel--perhaps instead + * convert tRNS to the grayscale/RGB format (16-bit RGB value), or replace + * the transparent color with a particular RGB value, or drop tRNS entirely. + * IOW, a future version of the library may make the transformations flag + * a bit more fine-grained, with separate bits for each of these three + * functions. + * + * More to the point, these functions make it obvious what libpng will be + * doing, whereas "expand" can (and does) mean any number of things. + * + * GRP 20060307: In libpng-1.2.9, png_set_gray_1_2_4_to_8() was modified + * to expand only the sample depth but not to expand the tRNS to alpha + * and its name was changed to png_set_expand_gray_1_2_4_to_8(). + */ + +/* Expand paletted images to RGB. */ +void PNGAPI +png_set_palette_to_rgb(png_structrp png_ptr) +{ + png_debug(1, "in png_set_palette_to_rgb"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} + +/* Expand grayscale images of less than 8-bit depth to 8 bits. */ +void PNGAPI +png_set_expand_gray_1_2_4_to_8(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand_gray_1_2_4_to_8"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= PNG_EXPAND; +} + +/* Expand tRNS chunks to alpha channels. */ +void PNGAPI +png_set_tRNS_to_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_tRNS_to_alpha"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= (PNG_EXPAND | PNG_EXPAND_tRNS); +} +#endif /* defined(PNG_READ_EXPAND_SUPPORTED) */ + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* Expand to 16-bit channels, expand the tRNS chunk too (because otherwise + * it may not work correctly.) + */ +void PNGAPI +png_set_expand_16(png_structrp png_ptr) +{ + png_debug(1, "in png_set_expand_16"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + png_ptr->transformations |= (PNG_EXPAND_16 | PNG_EXPAND | PNG_EXPAND_tRNS); +} +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +void PNGAPI +png_set_gray_to_rgb(png_structrp png_ptr) +{ + png_debug(1, "in png_set_gray_to_rgb"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + + /* Because rgb must be 8 bits or more: */ + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_ptr->transformations |= PNG_GRAY_TO_RGB; +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +void PNGFAPI +png_set_rgb_to_gray_fixed(png_structrp png_ptr, int error_action, + png_fixed_point red, png_fixed_point green) +{ + png_debug(1, "in png_set_rgb_to_gray"); + + /* Need the IHDR here because of the check on color_type below. */ + /* TODO: fix this */ + if (!png_rtran_ok(png_ptr, 1)) + return; + + switch(error_action) + { + case PNG_ERROR_ACTION_NONE: + png_ptr->transformations |= PNG_RGB_TO_GRAY; + break; + + case PNG_ERROR_ACTION_WARN: + png_ptr->transformations |= PNG_RGB_TO_GRAY_WARN; + break; + + case PNG_ERROR_ACTION_ERROR: + png_ptr->transformations |= PNG_RGB_TO_GRAY_ERR; + break; + + default: + png_error(png_ptr, "invalid error action to rgb_to_gray"); + break; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) +#ifdef PNG_READ_EXPAND_SUPPORTED + png_ptr->transformations |= PNG_EXPAND; +#else + { + /* Make this an error in 1.6 because otherwise the application may assume + * that it just worked and get a memory overwrite. + */ + png_error(png_ptr, + "Cannot do RGB_TO_GRAY without EXPAND_SUPPORTED"); + + /* png_ptr->transformations &= ~PNG_RGB_TO_GRAY; */ + } +#endif + { + if (red >= 0 && green >= 0 && red + green <= PNG_FP_1) + { + png_uint_16 red_int, green_int; + + /* NOTE: this calculation does not round, but this behavior is retained + * for consistency, the inaccuracy is very small. The code here always + * overwrites the coefficients, regardless of whether they have been + * defaulted or set already. + */ + red_int = (png_uint_16)(((png_uint_32)red*32768)/100000); + green_int = (png_uint_16)(((png_uint_32)green*32768)/100000); + + png_ptr->rgb_to_gray_red_coeff = red_int; + png_ptr->rgb_to_gray_green_coeff = green_int; + png_ptr->rgb_to_gray_coefficients_set = 1; + } + + else + { + if (red >= 0 && green >= 0) + png_app_warning(png_ptr, + "ignoring out of range rgb_to_gray coefficients"); + + /* Use the defaults, from the cHRM chunk if set, else the historical + * values which are close to the sRGB/HDTV/ITU-Rec 709 values. See + * png_do_rgb_to_gray for more discussion of the values. In this case + * the coefficients are not marked as 'set' and are not overwritten if + * something has already provided a default. + */ + if (png_ptr->rgb_to_gray_red_coeff == 0 && + png_ptr->rgb_to_gray_green_coeff == 0) + { + png_ptr->rgb_to_gray_red_coeff = 6968; + png_ptr->rgb_to_gray_green_coeff = 23434; + /* png_ptr->rgb_to_gray_blue_coeff = 2366; */ + } + } + } +} + +#ifdef PNG_FLOATING_POINT_SUPPORTED +/* Convert a RGB image to a grayscale of the same width. This allows us, + * for example, to convert a 24 bpp RGB image into an 8 bpp grayscale image. + */ + +void PNGAPI +png_set_rgb_to_gray(png_structrp png_ptr, int error_action, double red, + double green) +{ + png_set_rgb_to_gray_fixed(png_ptr, error_action, + png_fixed(png_ptr, red, "rgb to gray red coefficient"), + png_fixed(png_ptr, green, "rgb to gray green coefficient")); +} +#endif /* FLOATING POINT */ + +#endif /* RGB_TO_GRAY */ + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +void PNGAPI +png_set_read_user_transform_fn(png_structrp png_ptr, png_user_transform_ptr + read_user_transform_fn) +{ + png_debug(1, "in png_set_read_user_transform_fn"); + + if (!png_rtran_ok(png_ptr, 0)) + return; + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + png_ptr->transformations |= PNG_USER_TRANSFORM; + png_ptr->read_user_transform_fn = read_user_transform_fn; +#endif +} +#endif + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +#ifdef PNG_READ_GAMMA_SUPPORTED +/* In the case of gamma transformations only do transformations on images where + * the [file] gamma and screen_gamma are not close reciprocals, otherwise it + * slows things down slightly, and also needlessly introduces small errors. + */ +static int /* PRIVATE */ +png_gamma_threshold(png_fixed_point screen_gamma, png_fixed_point file_gamma) +{ + /* PNG_GAMMA_THRESHOLD is the threshold for performing gamma + * correction as a difference of the overall transform from 1.0 + * + * We want to compare the threshold with s*f - 1, if we get + * overflow here it is because of wacky gamma values so we + * turn on processing anyway. + */ + png_fixed_point gtest; + return !png_muldiv(>est, screen_gamma, file_gamma, PNG_FP_1) || + png_gamma_significant(gtest); +} +#endif + +/* Initialize everything needed for the read. This includes modifying + * the palette. + */ + +/*For the moment 'png_init_palette_transformations' and + * 'png_init_rgb_transformations' only do some flag canceling optimizations. + * The intent is that these two routines should have palette or rgb operations + * extracted from 'png_init_read_transformations'. + */ +static void /* PRIVATE */ +png_init_palette_transformations(png_structrp png_ptr) +{ + /* Called to handle the (input) palette case. In png_do_read_transformations + * the first step is to expand the palette if requested, so this code must + * take care to only make changes that are invariant with respect to the + * palette expansion, or only do them if there is no expansion. + * + * STRIP_ALPHA has already been handled in the caller (by setting num_trans + * to 0.) + */ + int input_has_alpha = 0; + int input_has_transparency = 0; + + if (png_ptr->num_trans > 0) + { + int i; + + /* Ignore if all the entries are opaque (unlikely!) */ + for (i=0; inum_trans; ++i) + if (png_ptr->trans_alpha[i] == 255) + continue; + else if (png_ptr->trans_alpha[i] == 0) + input_has_transparency = 1; + else + input_has_alpha = 1; + } + + /* If no alpha we can optimize. */ + if (!input_has_alpha) + { + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + if (!input_has_transparency) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); + } + +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ + + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. + */ + if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) && + (png_ptr->transformations & PNG_EXPAND)) + { + { + png_ptr->background.red = + png_ptr->palette[png_ptr->background.index].red; + png_ptr->background.green = + png_ptr->palette[png_ptr->background.index].green; + png_ptr->background.blue = + png_ptr->palette[png_ptr->background.index].blue; + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_ALPHA) + { + if (!(png_ptr->transformations & PNG_EXPAND_tRNS)) + { + /* Invert the alpha channel (in tRNS) unless the pixels are + * going to be expanded, in which case leave it for later + */ + int i, istop = png_ptr->num_trans; + + for (i=0; itrans_alpha[i] = (png_byte)(255 - + png_ptr->trans_alpha[i]); + } + } +#endif /* PNG_READ_INVERT_ALPHA_SUPPORTED */ + } + } /* background expand and (therefore) no alpha association. */ +#endif /* PNG_READ_EXPAND_SUPPORTED && PNG_READ_BACKGROUND_SUPPORTED */ +} + +static void /* PRIVATE */ +png_init_rgb_transformations(png_structrp png_ptr) +{ + /* Added to libpng-1.5.4: check the color type to determine whether there + * is any alpha or transparency in the image and simply cancel the + * background and alpha mode stuff if there isn't. + */ + int input_has_alpha = (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0; + int input_has_transparency = png_ptr->num_trans > 0; + + /* If no alpha we can optimize. */ + if (!input_has_alpha) + { + /* Any alpha means background and associative alpha processing is + * required, however if the alpha is 0 or 1 throughout OPTIIMIZE_ALPHA + * and ENCODE_ALPHA are irrelevant. + */ +# ifdef PNG_READ_ALPHA_MODE_SUPPORTED + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; +# endif + + if (!input_has_transparency) + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_BACKGROUND_EXPAND); + } + +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* png_set_background handling - deals with the complexity of whether the + * background color is in the file format or the screen format in the case + * where an 'expand' will happen. + */ + + /* The following code cannot be entered in the alpha pre-multiplication case + * because PNG_BACKGROUND_EXPAND is cancelled below. + */ + if ((png_ptr->transformations & PNG_BACKGROUND_EXPAND) && + (png_ptr->transformations & PNG_EXPAND) && + !(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) + /* i.e., GRAY or GRAY_ALPHA */ + { + { + /* Expand background and tRNS chunks */ + int gray = png_ptr->background.gray; + int trans_gray = png_ptr->trans_color.gray; + + switch (png_ptr->bit_depth) + { + case 1: + gray *= 0xff; + trans_gray *= 0xff; + break; + + case 2: + gray *= 0x55; + trans_gray *= 0x55; + break; + + case 4: + gray *= 0x11; + trans_gray *= 0x11; + break; + + default: + + case 8: + /* FALL THROUGH (Already 8 bits) */ + + case 16: + /* Already a full 16 bits */ + break; + } + + png_ptr->background.red = png_ptr->background.green = + png_ptr->background.blue = (png_uint_16)gray; + + if (!(png_ptr->transformations & PNG_EXPAND_tRNS)) + { + png_ptr->trans_color.red = png_ptr->trans_color.green = + png_ptr->trans_color.blue = (png_uint_16)trans_gray; + } + } + } /* background expand and (therefore) no alpha association. */ +#endif /* PNG_READ_EXPAND_SUPPORTED && PNG_READ_BACKGROUND_SUPPORTED */ +} + +void /* PRIVATE */ +png_init_read_transformations(png_structrp png_ptr) +{ + png_debug(1, "in png_init_read_transformations"); + + /* This internal function is called from png_read_start_row in pngrutil.c + * and it is called before the 'rowbytes' calculation is done, so the code + * in here can change or update the transformations flags. + * + * First do updates that do not depend on the details of the PNG image data + * being processed. + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED + /* Prior to 1.5.4 these tests were performed from png_set_gamma, 1.5.4 adds + * png_set_alpha_mode and this is another source for a default file gamma so + * the test needs to be performed later - here. In addition prior to 1.5.4 + * the tests were repeated for the PALETTE color type here - this is no + * longer necessary (and doesn't seem to have been necessary before.) + */ + { + /* The following temporary indicates if overall gamma correction is + * required. + */ + int gamma_correction = 0; + + if (png_ptr->colorspace.gamma != 0) /* has been set */ + { + if (png_ptr->screen_gamma != 0) /* screen set too */ + gamma_correction = png_gamma_threshold(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + + else + /* Assume the output matches the input; a long time default behavior + * of libpng, although the standard has nothing to say about this. + */ + png_ptr->screen_gamma = png_reciprocal(png_ptr->colorspace.gamma); + } + + else if (png_ptr->screen_gamma != 0) + /* The converse - assume the file matches the screen, note that this + * perhaps undesireable default can (from 1.5.4) be changed by calling + * png_set_alpha_mode (even if the alpha handling mode isn't required + * or isn't changed from the default.) + */ + png_ptr->colorspace.gamma = png_reciprocal(png_ptr->screen_gamma); + + else /* neither are set */ + /* Just in case the following prevents any processing - file and screen + * are both assumed to be linear and there is no way to introduce a + * third gamma value other than png_set_background with 'UNIQUE', and, + * prior to 1.5.4 + */ + png_ptr->screen_gamma = png_ptr->colorspace.gamma = PNG_FP_1; + + /* We have a gamma value now. */ + png_ptr->colorspace.flags |= PNG_COLORSPACE_HAVE_GAMMA; + + /* Now turn the gamma transformation on or off as appropriate. Notice + * that PNG_GAMMA just refers to the file->screen correction. Alpha + * composition may independently cause gamma correction because it needs + * linear data (e.g. if the file has a gAMA chunk but the screen gamma + * hasn't been specified.) In any case this flag may get turned off in + * the code immediately below if the transform can be handled outside the + * row loop. + */ + if (gamma_correction) + png_ptr->transformations |= PNG_GAMMA; + + else + png_ptr->transformations &= ~PNG_GAMMA; + } +#endif + + /* Certain transformations have the effect of preventing other + * transformations that happen afterward in png_do_read_transformations, + * resolve the interdependencies here. From the code of + * png_do_read_transformations the order is: + * + * 1) PNG_EXPAND (including PNG_EXPAND_tRNS) + * 2) PNG_STRIP_ALPHA (if no compose) + * 3) PNG_RGB_TO_GRAY + * 4) PNG_GRAY_TO_RGB iff !PNG_BACKGROUND_IS_GRAY + * 5) PNG_COMPOSE + * 6) PNG_GAMMA + * 7) PNG_STRIP_ALPHA (if compose) + * 8) PNG_ENCODE_ALPHA + * 9) PNG_SCALE_16_TO_8 + * 10) PNG_16_TO_8 + * 11) PNG_QUANTIZE (converts to palette) + * 12) PNG_EXPAND_16 + * 13) PNG_GRAY_TO_RGB iff PNG_BACKGROUND_IS_GRAY + * 14) PNG_INVERT_MONO + * 15) PNG_SHIFT + * 16) PNG_PACK + * 17) PNG_BGR + * 18) PNG_PACKSWAP + * 19) PNG_FILLER (includes PNG_ADD_ALPHA) + * 20) PNG_INVERT_ALPHA + * 21) PNG_SWAP_ALPHA + * 22) PNG_SWAP_BYTES + * 23) PNG_USER_TRANSFORM [must be last] + */ +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + !(png_ptr->transformations & PNG_COMPOSE)) + { + /* Stripping the alpha channel happens immediately after the 'expand' + * transformations, before all other transformation, so it cancels out + * the alpha handling. It has the side effect negating the effect of + * PNG_EXPAND_tRNS too: + */ + png_ptr->transformations &= ~(PNG_BACKGROUND_EXPAND | PNG_ENCODE_ALPHA | + PNG_EXPAND_tRNS); + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + + /* Kill the tRNS chunk itself too. Prior to 1.5.4 this did not happen + * so transparency information would remain just so long as it wasn't + * expanded. This produces unexpected API changes if the set of things + * that do PNG_EXPAND_tRNS changes (perfectly possible given the + * documentation - which says ask for what you want, accept what you + * get.) This makes the behavior consistent from 1.5.4: + */ + png_ptr->num_trans = 0; + } +#endif /* STRIP_ALPHA supported, no COMPOSE */ + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + /* If the screen gamma is about 1.0 then the OPTIMIZE_ALPHA and ENCODE_ALPHA + * settings will have no effect. + */ + if (!png_gamma_significant(png_ptr->screen_gamma)) + { + png_ptr->transformations &= ~PNG_ENCODE_ALPHA; + png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA; + } +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* Make sure the coefficients for the rgb to gray conversion are set + * appropriately. + */ + if (png_ptr->transformations & PNG_RGB_TO_GRAY) + png_colorspace_set_rgb_coefficients(png_ptr); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +#if defined(PNG_READ_EXPAND_SUPPORTED) && defined(PNG_READ_BACKGROUND_SUPPORTED) + /* Detect gray background and attempt to enable optimization for + * gray --> RGB case. + * + * Note: if PNG_BACKGROUND_EXPAND is set and color_type is either RGB or + * RGB_ALPHA (in which case need_expand is superfluous anyway), the + * background color might actually be gray yet not be flagged as such. + * This is not a problem for the current code, which uses + * PNG_BACKGROUND_IS_GRAY only to decide when to do the + * png_do_gray_to_rgb() transformation. + * + * TODO: this code needs to be revised to avoid the complexity and + * interdependencies. The color type of the background should be recorded in + * png_set_background, along with the bit depth, then the code has a record + * of exactly what color space the background is currently in. + */ + if (png_ptr->transformations & PNG_BACKGROUND_EXPAND) + { + /* PNG_BACKGROUND_EXPAND: the background is in the file color space, so if + * the file was grayscale the background value is gray. + */ + if (!(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + } + + else if (png_ptr->transformations & PNG_COMPOSE) + { + /* PNG_COMPOSE: png_set_background was called with need_expand false, + * so the color is in the color space of the output or png_set_alpha_mode + * was called and the color is black. Ignore RGB_TO_GRAY because that + * happens before GRAY_TO_RGB. + */ + if (png_ptr->transformations & PNG_GRAY_TO_RGB) + { + if (png_ptr->background.red == png_ptr->background.green && + png_ptr->background.red == png_ptr->background.blue) + { + png_ptr->mode |= PNG_BACKGROUND_IS_GRAY; + png_ptr->background.gray = png_ptr->background.red; + } + } + } +#endif /* PNG_READ_EXPAND_SUPPORTED && PNG_READ_BACKGROUND_SUPPORTED */ +#endif /* PNG_READ_GRAY_TO_RGB_SUPPORTED */ + + /* For indexed PNG data (PNG_COLOR_TYPE_PALETTE) many of the transformations + * can be performed directly on the palette, and some (such as rgb to gray) + * can be optimized inside the palette. This is particularly true of the + * composite (background and alpha) stuff, which can be pretty much all done + * in the palette even if the result is expanded to RGB or gray afterward. + * + * NOTE: this is Not Yet Implemented, the code behaves as in 1.5.1 and + * earlier and the palette stuff is actually handled on the first row. This + * leads to the reported bug that the palette returned by png_get_PLTE is not + * updated. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_init_palette_transformations(png_ptr); + + else + png_init_rgb_transformations(png_ptr); + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \ + defined(PNG_READ_EXPAND_16_SUPPORTED) + if ((png_ptr->transformations & PNG_EXPAND_16) && + (png_ptr->transformations & PNG_COMPOSE) && + !(png_ptr->transformations & PNG_BACKGROUND_EXPAND) && + png_ptr->bit_depth != 16) + { + /* TODO: fix this. Because the expand_16 operation is after the compose + * handling the background color must be 8, not 16, bits deep, but the + * application will supply a 16-bit value so reduce it here. + * + * The PNG_BACKGROUND_EXPAND code above does not expand to 16 bits at + * present, so that case is ok (until do_expand_16 is moved.) + * + * NOTE: this discards the low 16 bits of the user supplied background + * color, but until expand_16 works properly there is no choice! + */ +# define CHOP(x) (x)=((png_uint_16)PNG_DIV257(x)) + CHOP(png_ptr->background.red); + CHOP(png_ptr->background.green); + CHOP(png_ptr->background.blue); + CHOP(png_ptr->background.gray); +# undef CHOP + } +#endif /* PNG_READ_BACKGROUND_SUPPORTED && PNG_READ_EXPAND_16_SUPPORTED */ + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) && \ + (defined(PNG_READ_SCALE_16_TO_8_SUPPORTED) || \ + defined(PNG_READ_STRIP_16_TO_8_SUPPORTED)) + if ((png_ptr->transformations & (PNG_16_TO_8|PNG_SCALE_16_TO_8)) && + (png_ptr->transformations & PNG_COMPOSE) && + !(png_ptr->transformations & PNG_BACKGROUND_EXPAND) && + png_ptr->bit_depth == 16) + { + /* On the other hand, if a 16-bit file is to be reduced to 8-bits per + * component this will also happen after PNG_COMPOSE and so the background + * color must be pre-expanded here. + * + * TODO: fix this too. + */ + png_ptr->background.red = (png_uint_16)(png_ptr->background.red * 257); + png_ptr->background.green = + (png_uint_16)(png_ptr->background.green * 257); + png_ptr->background.blue = (png_uint_16)(png_ptr->background.blue * 257); + png_ptr->background.gray = (png_uint_16)(png_ptr->background.gray * 257); + } +#endif + + /* NOTE: below 'PNG_READ_ALPHA_MODE_SUPPORTED' is presumed to also enable the + * background support (see the comments in scripts/pnglibconf.dfa), this + * allows pre-multiplication of the alpha channel to be implemented as + * compositing on black. This is probably sub-optimal and has been done in + * 1.5.4 betas simply to enable external critique and testing (i.e. to + * implement the new API quickly, without lots of internal changes.) + */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +# ifdef PNG_READ_BACKGROUND_SUPPORTED + /* Includes ALPHA_MODE */ + png_ptr->background_1 = png_ptr->background; +# endif + + /* This needs to change - in the palette image case a whole set of tables are + * built when it would be quicker to just calculate the correct value for + * each palette entry directly. Also, the test is too tricky - why check + * PNG_RGB_TO_GRAY if PNG_GAMMA is not set? The answer seems to be that + * PNG_GAMMA is cancelled even if the gamma is known? The test excludes the + * PNG_COMPOSE case, so apparently if there is no *overall* gamma correction + * the gamma tables will not be built even if composition is required on a + * gamma encoded value. + * + * In 1.5.4 this is addressed below by an additional check on the individual + * file gamma - if it is not 1.0 both RGB_TO_GRAY and COMPOSE need the + * tables. + */ + if ((png_ptr->transformations & PNG_GAMMA) + || ((png_ptr->transformations & PNG_RGB_TO_GRAY) + && (png_gamma_significant(png_ptr->colorspace.gamma) || + png_gamma_significant(png_ptr->screen_gamma))) + || ((png_ptr->transformations & PNG_COMPOSE) + && (png_gamma_significant(png_ptr->colorspace.gamma) + || png_gamma_significant(png_ptr->screen_gamma) +# ifdef PNG_READ_BACKGROUND_SUPPORTED + || (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_UNIQUE + && png_gamma_significant(png_ptr->background_gamma)) +# endif + )) || ((png_ptr->transformations & PNG_ENCODE_ALPHA) + && png_gamma_significant(png_ptr->screen_gamma)) + ) + { + png_build_gamma_table(png_ptr, png_ptr->bit_depth); + +#ifdef PNG_READ_BACKGROUND_SUPPORTED + if (png_ptr->transformations & PNG_COMPOSE) + { + /* Issue a warning about this combination: because RGB_TO_GRAY is + * optimized to do the gamma transform if present yet do_background has + * to do the same thing if both options are set a + * double-gamma-correction happens. This is true in all versions of + * libpng to date. + */ + if (png_ptr->transformations & PNG_RGB_TO_GRAY) + png_warning(png_ptr, + "libpng does not support gamma+background+rgb_to_gray"); + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + /* We don't get to here unless there is a tRNS chunk with non-opaque + * entries - see the checking code at the start of this function. + */ + png_color back, back_1; + png_colorp palette = png_ptr->palette; + int num_palette = png_ptr->num_palette; + int i; + if (png_ptr->background_gamma_type == PNG_BACKGROUND_GAMMA_FILE) + { + + back.red = png_ptr->gamma_table[png_ptr->background.red]; + back.green = png_ptr->gamma_table[png_ptr->background.green]; + back.blue = png_ptr->gamma_table[png_ptr->background.blue]; + + back_1.red = png_ptr->gamma_to_1[png_ptr->background.red]; + back_1.green = png_ptr->gamma_to_1[png_ptr->background.green]; + back_1.blue = png_ptr->gamma_to_1[png_ptr->background.blue]; + } + else + { + png_fixed_point g, gs; + + switch (png_ptr->background_gamma_type) + { + case PNG_BACKGROUND_GAMMA_SCREEN: + g = (png_ptr->screen_gamma); + gs = PNG_FP_1; + break; + + case PNG_BACKGROUND_GAMMA_FILE: + g = png_reciprocal(png_ptr->colorspace.gamma); + gs = png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + break; + + case PNG_BACKGROUND_GAMMA_UNIQUE: + g = png_reciprocal(png_ptr->background_gamma); + gs = png_reciprocal2(png_ptr->background_gamma, + png_ptr->screen_gamma); + break; + default: + g = PNG_FP_1; /* back_1 */ + gs = PNG_FP_1; /* back */ + break; + } + + if (png_gamma_significant(gs)) + { + back.red = png_gamma_8bit_correct(png_ptr->background.red, + gs); + back.green = png_gamma_8bit_correct(png_ptr->background.green, + gs); + back.blue = png_gamma_8bit_correct(png_ptr->background.blue, + gs); + } + + else + { + back.red = (png_byte)png_ptr->background.red; + back.green = (png_byte)png_ptr->background.green; + back.blue = (png_byte)png_ptr->background.blue; + } + + if (png_gamma_significant(g)) + { + back_1.red = png_gamma_8bit_correct(png_ptr->background.red, + g); + back_1.green = png_gamma_8bit_correct( + png_ptr->background.green, g); + back_1.blue = png_gamma_8bit_correct(png_ptr->background.blue, + g); + } + + else + { + back_1.red = (png_byte)png_ptr->background.red; + back_1.green = (png_byte)png_ptr->background.green; + back_1.blue = (png_byte)png_ptr->background.blue; + } + } + + for (i = 0; i < num_palette; i++) + { + if (i < (int)png_ptr->num_trans && + png_ptr->trans_alpha[i] != 0xff) + { + if (png_ptr->trans_alpha[i] == 0) + { + palette[i] = back; + } + else /* if (png_ptr->trans_alpha[i] != 0xff) */ + { + png_byte v, w; + + v = png_ptr->gamma_to_1[palette[i].red]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.red); + palette[i].red = png_ptr->gamma_from_1[w]; + + v = png_ptr->gamma_to_1[palette[i].green]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.green); + palette[i].green = png_ptr->gamma_from_1[w]; + + v = png_ptr->gamma_to_1[palette[i].blue]; + png_composite(w, v, png_ptr->trans_alpha[i], back_1.blue); + palette[i].blue = png_ptr->gamma_from_1[w]; + } + } + else + { + palette[i].red = png_ptr->gamma_table[palette[i].red]; + palette[i].green = png_ptr->gamma_table[palette[i].green]; + palette[i].blue = png_ptr->gamma_table[palette[i].blue]; + } + } + + /* Prevent the transformations being done again. + * + * NOTE: this is highly dubious; it removes the transformations in + * place. This seems inconsistent with the general treatment of the + * transformations elsewhere. + */ + png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA); + } /* color_type == PNG_COLOR_TYPE_PALETTE */ + + /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */ + else /* color_type != PNG_COLOR_TYPE_PALETTE */ + { + int gs_sig, g_sig; + png_fixed_point g = PNG_FP_1; /* Correction to linear */ + png_fixed_point gs = PNG_FP_1; /* Correction to screen */ + + switch (png_ptr->background_gamma_type) + { + case PNG_BACKGROUND_GAMMA_SCREEN: + g = png_ptr->screen_gamma; + /* gs = PNG_FP_1; */ + break; + + case PNG_BACKGROUND_GAMMA_FILE: + g = png_reciprocal(png_ptr->colorspace.gamma); + gs = png_reciprocal2(png_ptr->colorspace.gamma, + png_ptr->screen_gamma); + break; + + case PNG_BACKGROUND_GAMMA_UNIQUE: + g = png_reciprocal(png_ptr->background_gamma); + gs = png_reciprocal2(png_ptr->background_gamma, + png_ptr->screen_gamma); + break; + + default: + png_error(png_ptr, "invalid background gamma type"); + } + + g_sig = png_gamma_significant(g); + gs_sig = png_gamma_significant(gs); + + if (g_sig) + png_ptr->background_1.gray = png_gamma_correct(png_ptr, + png_ptr->background.gray, g); + + if (gs_sig) + png_ptr->background.gray = png_gamma_correct(png_ptr, + png_ptr->background.gray, gs); + + if ((png_ptr->background.red != png_ptr->background.green) || + (png_ptr->background.red != png_ptr->background.blue) || + (png_ptr->background.red != png_ptr->background.gray)) + { + /* RGB or RGBA with color background */ + if (g_sig) + { + png_ptr->background_1.red = png_gamma_correct(png_ptr, + png_ptr->background.red, g); + + png_ptr->background_1.green = png_gamma_correct(png_ptr, + png_ptr->background.green, g); + + png_ptr->background_1.blue = png_gamma_correct(png_ptr, + png_ptr->background.blue, g); + } + + if (gs_sig) + { + png_ptr->background.red = png_gamma_correct(png_ptr, + png_ptr->background.red, gs); + + png_ptr->background.green = png_gamma_correct(png_ptr, + png_ptr->background.green, gs); + + png_ptr->background.blue = png_gamma_correct(png_ptr, + png_ptr->background.blue, gs); + } + } + + else + { + /* GRAY, GRAY ALPHA, RGB, or RGBA with gray background */ + png_ptr->background_1.red = png_ptr->background_1.green + = png_ptr->background_1.blue = png_ptr->background_1.gray; + + png_ptr->background.red = png_ptr->background.green + = png_ptr->background.blue = png_ptr->background.gray; + } + + /* The background is now in screen gamma: */ + png_ptr->background_gamma_type = PNG_BACKGROUND_GAMMA_SCREEN; + } /* color_type != PNG_COLOR_TYPE_PALETTE */ + }/* png_ptr->transformations & PNG_BACKGROUND */ + + else + /* Transformation does not include PNG_BACKGROUND */ +#endif /* PNG_READ_BACKGROUND_SUPPORTED */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* RGB_TO_GRAY needs to have non-gamma-corrected values! */ + && ((png_ptr->transformations & PNG_EXPAND) == 0 || + (png_ptr->transformations & PNG_RGB_TO_GRAY) == 0) +#endif + ) + { + png_colorp palette = png_ptr->palette; + int num_palette = png_ptr->num_palette; + int i; + + /* NOTE: there are other transformations that should probably be in + * here too. + */ + for (i = 0; i < num_palette; i++) + { + palette[i].red = png_ptr->gamma_table[palette[i].red]; + palette[i].green = png_ptr->gamma_table[palette[i].green]; + palette[i].blue = png_ptr->gamma_table[palette[i].blue]; + } + + /* Done the gamma correction. */ + png_ptr->transformations &= ~PNG_GAMMA; + } /* color_type == PALETTE && !PNG_BACKGROUND transformation */ + } +#ifdef PNG_READ_BACKGROUND_SUPPORTED + else +#endif +#endif /* PNG_READ_GAMMA_SUPPORTED */ + +#ifdef PNG_READ_BACKGROUND_SUPPORTED + /* No GAMMA transformation (see the hanging else 4 lines above) */ + if ((png_ptr->transformations & PNG_COMPOSE) && + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) + { + int i; + int istop = (int)png_ptr->num_trans; + png_color back; + png_colorp palette = png_ptr->palette; + + back.red = (png_byte)png_ptr->background.red; + back.green = (png_byte)png_ptr->background.green; + back.blue = (png_byte)png_ptr->background.blue; + + for (i = 0; i < istop; i++) + { + if (png_ptr->trans_alpha[i] == 0) + { + palette[i] = back; + } + + else if (png_ptr->trans_alpha[i] != 0xff) + { + /* The png_composite() macro is defined in png.h */ + png_composite(palette[i].red, palette[i].red, + png_ptr->trans_alpha[i], back.red); + + png_composite(palette[i].green, palette[i].green, + png_ptr->trans_alpha[i], back.green); + + png_composite(palette[i].blue, palette[i].blue, + png_ptr->trans_alpha[i], back.blue); + } + } + + png_ptr->transformations &= ~PNG_COMPOSE; + } +#endif /* PNG_READ_BACKGROUND_SUPPORTED */ + +#ifdef PNG_READ_SHIFT_SUPPORTED + if ((png_ptr->transformations & PNG_SHIFT) && + !(png_ptr->transformations & PNG_EXPAND) && + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE)) + { + int i; + int istop = png_ptr->num_palette; + int shift = 8 - png_ptr->sig_bit.red; + + png_ptr->transformations &= ~PNG_SHIFT; + + /* significant bits can be in the range 1 to 7 for a meaninful result, if + * the number of significant bits is 0 then no shift is done (this is an + * error condition which is silently ignored.) + */ + if (shift > 0 && shift < 8) for (i=0; ipalette[i].red; + + component >>= shift; + png_ptr->palette[i].red = (png_byte)component; + } + + shift = 8 - png_ptr->sig_bit.green; + if (shift > 0 && shift < 8) for (i=0; ipalette[i].green; + + component >>= shift; + png_ptr->palette[i].green = (png_byte)component; + } + + shift = 8 - png_ptr->sig_bit.blue; + if (shift > 0 && shift < 8) for (i=0; ipalette[i].blue; + + component >>= shift; + png_ptr->palette[i].blue = (png_byte)component; + } + } +#endif /* PNG_READ_SHIFT_SUPPORTED */ +} + +/* Modify the info structure to reflect the transformations. The + * info should be updated so a PNG file could be written with it, + * assuming the transformations result in valid PNG data. + */ +void /* PRIVATE */ +png_read_transform_info(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_read_transform_info"); + +#ifdef PNG_READ_EXPAND_SUPPORTED + if (png_ptr->transformations & PNG_EXPAND) + { + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + /* This check must match what actually happens in + * png_do_expand_palette; if it ever checks the tRNS chunk to see if + * it is all opaque we must do the same (at present it does not.) + */ + if (png_ptr->num_trans > 0) + info_ptr->color_type = PNG_COLOR_TYPE_RGB_ALPHA; + + else + info_ptr->color_type = PNG_COLOR_TYPE_RGB; + + info_ptr->bit_depth = 8; + info_ptr->num_trans = 0; + } + else + { + if (png_ptr->num_trans) + { + if (png_ptr->transformations & PNG_EXPAND_tRNS) + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + } + if (info_ptr->bit_depth < 8) + info_ptr->bit_depth = 8; + + info_ptr->num_trans = 0; + } + } +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + /* The following is almost certainly wrong unless the background value is in + * the screen space! + */ + if (png_ptr->transformations & PNG_COMPOSE) + info_ptr->background = png_ptr->background; +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + /* The following used to be conditional on PNG_GAMMA (prior to 1.5.4), + * however it seems that the code in png_init_read_transformations, which has + * been called before this from png_read_update_info->png_read_start_row + * sometimes does the gamma transform and cancels the flag. + * + * TODO: this looks wrong; the info_ptr should end up with a gamma equal to + * the screen_gamma value. The following probably results in weirdness if + * the info_ptr is used by the app after the rows have been read. + */ + info_ptr->colorspace.gamma = png_ptr->colorspace.gamma; +#endif + + if (info_ptr->bit_depth == 16) + { +# ifdef PNG_READ_16BIT_SUPPORTED +# ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + if (png_ptr->transformations & PNG_SCALE_16_TO_8) + info_ptr->bit_depth = 8; +# endif + +# ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + if (png_ptr->transformations & PNG_16_TO_8) + info_ptr->bit_depth = 8; +# endif + +# else + /* No 16 bit support: force chopping 16-bit input down to 8, in this case + * the app program can chose if both APIs are available by setting the + * correct scaling to use. + */ +# ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + /* For compatibility with previous versions use the strip method by + * default. This code works because if PNG_SCALE_16_TO_8 is already + * set the code below will do that in preference to the chop. + */ + png_ptr->transformations |= PNG_16_TO_8; + info_ptr->bit_depth = 8; +# else + +# ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_ptr->transformations |= PNG_SCALE_16_TO_8; + info_ptr->bit_depth = 8; +# else + + CONFIGURATION ERROR: you must enable at least one 16 to 8 method +# endif +# endif +#endif /* !READ_16BIT_SUPPORTED */ + } + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + if (png_ptr->transformations & PNG_GRAY_TO_RGB) + info_ptr->color_type = (png_byte)(info_ptr->color_type | + PNG_COLOR_MASK_COLOR); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + if (png_ptr->transformations & PNG_RGB_TO_GRAY) + info_ptr->color_type = (png_byte)(info_ptr->color_type & + ~PNG_COLOR_MASK_COLOR); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + if (png_ptr->transformations & PNG_QUANTIZE) + { + if (((info_ptr->color_type == PNG_COLOR_TYPE_RGB) || + (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA)) && + png_ptr->palette_lookup && info_ptr->bit_depth == 8) + { + info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; + } + } +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + if (png_ptr->transformations & PNG_EXPAND_16 && info_ptr->bit_depth == 8 && + info_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + { + info_ptr->bit_depth = 16; + } +#endif + +#ifdef PNG_READ_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) && (info_ptr->bit_depth < 8)) + info_ptr->bit_depth = 8; +#endif + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + info_ptr->channels = 1; + + else if (info_ptr->color_type & PNG_COLOR_MASK_COLOR) + info_ptr->channels = 3; + + else + info_ptr->channels = 1; + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_STRIP_ALPHA) + { + info_ptr->color_type = (png_byte)(info_ptr->color_type & + ~PNG_COLOR_MASK_ALPHA); + info_ptr->num_trans = 0; + } +#endif + + if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) + info_ptr->channels++; + +#ifdef PNG_READ_FILLER_SUPPORTED + /* STRIP_ALPHA and FILLER allowed: MASK_ALPHA bit stripped above */ + if ((png_ptr->transformations & PNG_FILLER) && + ((info_ptr->color_type == PNG_COLOR_TYPE_RGB) || + (info_ptr->color_type == PNG_COLOR_TYPE_GRAY))) + { + info_ptr->channels++; + /* If adding a true alpha channel not just filler */ + if (png_ptr->transformations & PNG_ADD_ALPHA) + info_ptr->color_type |= PNG_COLOR_MASK_ALPHA; + } +#endif + +#if defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) && \ +defined(PNG_READ_USER_TRANSFORM_SUPPORTED) + if (png_ptr->transformations & PNG_USER_TRANSFORM) + { + if (info_ptr->bit_depth < png_ptr->user_transform_depth) + info_ptr->bit_depth = png_ptr->user_transform_depth; + + if (info_ptr->channels < png_ptr->user_transform_channels) + info_ptr->channels = png_ptr->user_transform_channels; + } +#endif + + info_ptr->pixel_depth = (png_byte)(info_ptr->channels * + info_ptr->bit_depth); + + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, info_ptr->width); + + /* Adding in 1.5.4: cache the above value in png_struct so that we can later + * check in png_rowbytes that the user buffer won't get overwritten. Note + * that the field is not always set - if png_read_update_info isn't called + * the application has to either not do any transforms or get the calculation + * right itself. + */ + png_ptr->info_rowbytes = info_ptr->rowbytes; + +#ifndef PNG_READ_EXPAND_SUPPORTED + if (png_ptr) + return; +#endif +} + +/* Transform the row. The order of transformations is significant, + * and is very touchy. If you add a transformation, take care to + * decide how it fits in with the other transformations here. + */ +void /* PRIVATE */ +png_do_read_transformations(png_structrp png_ptr, png_row_infop row_info) +{ + png_debug(1, "in png_do_read_transformations"); + + if (png_ptr->row_buf == NULL) + { + /* Prior to 1.5.4 this output row/pass where the NULL pointer is, but this + * error is incredibly rare and incredibly easy to debug without this + * information. + */ + png_error(png_ptr, "NULL row buffer"); + } + + /* The following is debugging; prior to 1.5.4 the code was never compiled in; + * in 1.5.4 PNG_FLAG_DETECT_UNINITIALIZED was added and the macro + * PNG_WARN_UNINITIALIZED_ROW removed. In 1.6 the new flag is set only for + * all transformations, however in practice the ROW_INIT always gets done on + * demand, if necessary. + */ + if ((png_ptr->flags & PNG_FLAG_DETECT_UNINITIALIZED) != 0 && + !(png_ptr->flags & PNG_FLAG_ROW_INIT)) + { + /* Application has failed to call either png_read_start_image() or + * png_read_update_info() after setting transforms that expand pixels. + * This check added to libpng-1.2.19 (but not enabled until 1.5.4). + */ + png_error(png_ptr, "Uninitialized row"); + } + +#ifdef PNG_READ_EXPAND_SUPPORTED + if (png_ptr->transformations & PNG_EXPAND) + { + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE) + { + png_do_expand_palette(row_info, png_ptr->row_buf + 1, + png_ptr->palette, png_ptr->trans_alpha, png_ptr->num_trans); + } + + else + { + if (png_ptr->num_trans && + (png_ptr->transformations & PNG_EXPAND_tRNS)) + png_do_expand(row_info, png_ptr->row_buf + 1, + &(png_ptr->trans_color)); + + else + png_do_expand(row_info, png_ptr->row_buf + 1, + NULL); + } + } +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + !(png_ptr->transformations & PNG_COMPOSE) && + (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + 0 /* at_start == false, because SWAP_ALPHA happens later */); +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + if (png_ptr->transformations & PNG_RGB_TO_GRAY) + { + int rgb_error = + png_do_rgb_to_gray(png_ptr, row_info, + png_ptr->row_buf + 1); + + if (rgb_error) + { + png_ptr->rgb_to_gray_status=1; + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == + PNG_RGB_TO_GRAY_WARN) + png_warning(png_ptr, "png_do_rgb_to_gray found nongray pixel"); + + if ((png_ptr->transformations & PNG_RGB_TO_GRAY) == + PNG_RGB_TO_GRAY_ERR) + png_error(png_ptr, "png_do_rgb_to_gray found nongray pixel"); + } + } +#endif + +/* From Andreas Dilger e-mail to png-implement, 26 March 1998: + * + * In most cases, the "simple transparency" should be done prior to doing + * gray-to-RGB, or you will have to test 3x as many bytes to check if a + * pixel is transparent. You would also need to make sure that the + * transparency information is upgraded to RGB. + * + * To summarize, the current flow is: + * - Gray + simple transparency -> compare 1 or 2 gray bytes and composite + * with background "in place" if transparent, + * convert to RGB if necessary + * - Gray + alpha -> composite with gray background and remove alpha bytes, + * convert to RGB if necessary + * + * To support RGB backgrounds for gray images we need: + * - Gray + simple transparency -> convert to RGB + simple transparency, + * compare 3 or 6 bytes and composite with + * background "in place" if transparent + * (3x compare/pixel compared to doing + * composite with gray bkgrnd) + * - Gray + alpha -> convert to RGB + alpha, composite with background and + * remove alpha bytes (3x float + * operations/pixel compared with composite + * on gray background) + * + * Greg's change will do this. The reason it wasn't done before is for + * performance, as this increases the per-pixel operations. If we would check + * in advance if the background was gray or RGB, and position the gray-to-RGB + * transform appropriately, then it would save a lot of work/time. + */ + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /* If gray -> RGB, do so now only if background is non-gray; else do later + * for performance reasons + */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) && + !(png_ptr->mode & PNG_BACKGROUND_IS_GRAY)) + png_do_gray_to_rgb(row_info, png_ptr->row_buf + 1); +#endif + +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + if (png_ptr->transformations & PNG_COMPOSE) + png_do_compose(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + if ((png_ptr->transformations & PNG_GAMMA) && +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + /* Because RGB_TO_GRAY does the gamma transform. */ + !(png_ptr->transformations & PNG_RGB_TO_GRAY) && +#endif +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + /* Because PNG_COMPOSE does the gamma transform if there is something to + * do (if there is an alpha channel or transparency.) + */ + !((png_ptr->transformations & PNG_COMPOSE) && + ((png_ptr->num_trans != 0) || + (png_ptr->color_type & PNG_COLOR_MASK_ALPHA))) && +#endif + /* Because png_init_read_transformations transforms the palette, unless + * RGB_TO_GRAY will do the transform. + */ + (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE)) + png_do_gamma(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + if ((png_ptr->transformations & PNG_STRIP_ALPHA) && + (png_ptr->transformations & PNG_COMPOSE) && + (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + 0 /* at_start == false, because SWAP_ALPHA happens later */); +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED + if ((png_ptr->transformations & PNG_ENCODE_ALPHA) && + (row_info->color_type & PNG_COLOR_MASK_ALPHA)) + png_do_encode_alpha(row_info, png_ptr->row_buf + 1, png_ptr); +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + if (png_ptr->transformations & PNG_SCALE_16_TO_8) + png_do_scale_16_to_8(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + /* There is no harm in doing both of these because only one has any effect, + * by putting the 'scale' option first if the app asks for scale (either by + * calling the API or in a TRANSFORM flag) this is what happens. + */ + if (png_ptr->transformations & PNG_16_TO_8) + png_do_chop(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + if (png_ptr->transformations & PNG_QUANTIZE) + { + png_do_quantize(row_info, png_ptr->row_buf + 1, + png_ptr->palette_lookup, png_ptr->quantize_index); + + if (row_info->rowbytes == 0) + png_error(png_ptr, "png_do_quantize returned rowbytes=0"); + } +#endif /* PNG_READ_QUANTIZE_SUPPORTED */ + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + /* Do the expansion now, after all the arithmetic has been done. Notice + * that previous transformations can handle the PNG_EXPAND_16 flag if this + * is efficient (particularly true in the case of gamma correction, where + * better accuracy results faster!) + */ + if (png_ptr->transformations & PNG_EXPAND_16) + png_do_expand_16(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + /* NOTE: moved here in 1.5.4 (from much later in this list.) */ + if ((png_ptr->transformations & PNG_GRAY_TO_RGB) && + (png_ptr->mode & PNG_BACKGROUND_IS_GRAY)) + png_do_gray_to_rgb(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_INVERT_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_MONO) + png_do_invert(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED + if (png_ptr->transformations & PNG_SHIFT) + png_do_unshift(row_info, png_ptr->row_buf + 1, + &(png_ptr->shift)); +#endif + +#ifdef PNG_READ_PACK_SUPPORTED + if (png_ptr->transformations & PNG_PACK) + png_do_unpack(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Added at libpng-1.5.10 */ + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max >= 0) + png_do_check_palette_indexes(png_ptr, row_info); +#endif + +#ifdef PNG_READ_BGR_SUPPORTED + if (png_ptr->transformations & PNG_BGR) + png_do_bgr(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if (png_ptr->transformations & PNG_PACKSWAP) + png_do_packswap(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED + if (png_ptr->transformations & PNG_FILLER) + png_do_read_filler(row_info, png_ptr->row_buf + 1, + (png_uint_32)png_ptr->filler, png_ptr->flags); +#endif + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_ALPHA) + png_do_read_invert_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_SWAP_ALPHA) + png_do_read_swap_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_READ_16BIT_SUPPORTED +#ifdef PNG_READ_SWAP_SUPPORTED + if (png_ptr->transformations & PNG_SWAP_BYTES) + png_do_swap(row_info, png_ptr->row_buf + 1); +#endif +#endif + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + if (png_ptr->transformations & PNG_USER_TRANSFORM) + { + if (png_ptr->read_user_transform_fn != NULL) + (*(png_ptr->read_user_transform_fn)) /* User read transform function */ + (png_ptr, /* png_ptr */ + row_info, /* row_info: */ + /* png_uint_32 width; width of row */ + /* png_size_t rowbytes; number of bytes in row */ + /* png_byte color_type; color type of pixels */ + /* png_byte bit_depth; bit depth of samples */ + /* png_byte channels; number of channels (1-4) */ + /* png_byte pixel_depth; bits per pixel (depth*channels) */ + png_ptr->row_buf + 1); /* start of pixel data for row */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED + if (png_ptr->user_transform_depth) + row_info->bit_depth = png_ptr->user_transform_depth; + + if (png_ptr->user_transform_channels) + row_info->channels = png_ptr->user_transform_channels; +#endif + row_info->pixel_depth = (png_byte)(row_info->bit_depth * + row_info->channels); + + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_info->width); + } +#endif +} + +#ifdef PNG_READ_PACK_SUPPORTED +/* Unpack pixels of 1, 2, or 4 bits per pixel into 1 byte per pixel, + * without changing the actual values. Thus, if you had a row with + * a bit depth of 1, you would end up with bytes that only contained + * the numbers 0 or 1. If you would rather they contain 0 and 255, use + * png_do_shift() after this. + */ +void /* PRIVATE */ +png_do_unpack(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_unpack"); + + if (row_info->bit_depth < 8) + { + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + switch (row_info->bit_depth) + { + case 1: + { + png_bytep sp = row + (png_size_t)((row_width - 1) >> 3); + png_bytep dp = row + (png_size_t)row_width - 1; + png_uint_32 shift = 7 - (int)((row_width + 7) & 0x07); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x01); + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + + png_bytep sp = row + (png_size_t)((row_width - 1) >> 2); + png_bytep dp = row + (png_size_t)row_width - 1; + png_uint_32 shift = (int)((3 - ((row_width + 3) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x03); + + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + png_bytep sp = row + (png_size_t)((row_width - 1) >> 1); + png_bytep dp = row + (png_size_t)row_width - 1; + png_uint_32 shift = (int)((1 - ((row_width + 1) & 0x01)) << 2); + for (i = 0; i < row_width; i++) + { + *dp = (png_byte)((*sp >> shift) & 0x0f); + + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift = 4; + + dp--; + } + break; + } + + default: + break; + } + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_SHIFT_SUPPORTED +/* Reverse the effects of png_do_shift. This routine merely shifts the + * pixels back to their significant bits values. Thus, if you have + * a row of bit depth 8, but only 5 are significant, this will shift + * the values back to 0 through 31. + */ +void /* PRIVATE */ +png_do_unshift(png_row_infop row_info, png_bytep row, + png_const_color_8p sig_bits) +{ + int color_type; + + png_debug(1, "in png_do_unshift"); + + /* The palette case has already been handled in the _init routine. */ + color_type = row_info->color_type; + + if (color_type != PNG_COLOR_TYPE_PALETTE) + { + int shift[4]; + int channels = 0; + int bit_depth = row_info->bit_depth; + + if (color_type & PNG_COLOR_MASK_COLOR) + { + shift[channels++] = bit_depth - sig_bits->red; + shift[channels++] = bit_depth - sig_bits->green; + shift[channels++] = bit_depth - sig_bits->blue; + } + + else + { + shift[channels++] = bit_depth - sig_bits->gray; + } + + if (color_type & PNG_COLOR_MASK_ALPHA) + { + shift[channels++] = bit_depth - sig_bits->alpha; + } + + { + int c, have_shift; + + for (c = have_shift = 0; c < channels; ++c) + { + /* A shift of more than the bit depth is an error condition but it + * gets ignored here. + */ + if (shift[c] <= 0 || shift[c] >= bit_depth) + shift[c] = 0; + + else + have_shift = 1; + } + + if (!have_shift) + return; + } + + switch (bit_depth) + { + default: + /* Must be 1bpp gray: should not be here! */ + /* NOTREACHED */ + break; + + case 2: + /* Must be 2bpp gray */ + /* assert(channels == 1 && shift[0] == 1) */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + + while (bp < bp_end) + { + int b = (*bp >> 1) & 0x55; + *bp++ = (png_byte)b; + } + break; + } + + case 4: + /* Must be 4bpp gray */ + /* assert(channels == 1) */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int gray_shift = shift[0]; + int mask = 0xf >> gray_shift; + + mask |= mask << 4; + + while (bp < bp_end) + { + int b = (*bp >> gray_shift) & mask; + *bp++ = (png_byte)b; + } + break; + } + + case 8: + /* Single byte components, G, GA, RGB, RGBA */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int channel = 0; + + while (bp < bp_end) + { + int b = *bp >> shift[channel]; + if (++channel >= channels) + channel = 0; + *bp++ = (png_byte)b; + } + break; + } + +#ifdef PNG_READ_16BIT_SUPPORTED + case 16: + /* Double byte components, G, GA, RGB, RGBA */ + { + png_bytep bp = row; + png_bytep bp_end = bp + row_info->rowbytes; + int channel = 0; + + while (bp < bp_end) + { + int value = (bp[0] << 8) + bp[1]; + + value >>= shift[channel]; + if (++channel >= channels) + channel = 0; + *bp++ = (png_byte)(value >> 8); + *bp++ = (png_byte)(value & 0xff); + } + break; + } +#endif + } + } +} +#endif + +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED +/* Scale rows of bit depth 16 down to 8 accurately */ +void /* PRIVATE */ +png_do_scale_16_to_8(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_scale_16_to_8"); + + if (row_info->bit_depth == 16) + { + png_bytep sp = row; /* source */ + png_bytep dp = row; /* destination */ + png_bytep ep = sp + row_info->rowbytes; /* end+1 */ + + while (sp < ep) + { + /* The input is an array of 16 bit components, these must be scaled to + * 8 bits each. For a 16 bit value V the required value (from the PNG + * specification) is: + * + * (V * 255) / 65535 + * + * This reduces to round(V / 257), or floor((V + 128.5)/257) + * + * Represent V as the two byte value vhi.vlo. Make a guess that the + * result is the top byte of V, vhi, then the correction to this value + * is: + * + * error = floor(((V-vhi.vhi) + 128.5) / 257) + * = floor(((vlo-vhi) + 128.5) / 257) + * + * This can be approximated using integer arithmetic (and a signed + * shift): + * + * error = (vlo-vhi+128) >> 8; + * + * The approximate differs from the exact answer only when (vlo-vhi) is + * 128; it then gives a correction of +1 when the exact correction is + * 0. This gives 128 errors. The exact answer (correct for all 16 bit + * input values) is: + * + * error = (vlo-vhi+128)*65535 >> 24; + * + * An alternative arithmetic calculation which also gives no errors is: + * + * (V * 255 + 32895) >> 16 + */ + + png_int_32 tmp = *sp++; /* must be signed! */ + tmp += (((int)*sp++ - tmp + 128) * 65535) >> 24; + *dp++ = (png_byte)tmp; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_info->width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED +void /* PRIVATE */ +/* Simply discard the low byte. This was the default behavior prior + * to libpng-1.5.4. + */ +png_do_chop(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_chop"); + + if (row_info->bit_depth == 16) + { + png_bytep sp = row; /* source */ + png_bytep dp = row; /* destination */ + png_bytep ep = sp + row_info->rowbytes; /* end+1 */ + + while (sp < ep) + { + *dp++ = *sp; + sp += 2; /* skip low byte */ + } + + row_info->bit_depth = 8; + row_info->pixel_depth = (png_byte)(8 * row_info->channels); + row_info->rowbytes = row_info->width * row_info->channels; + } +} +#endif + +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED +void /* PRIVATE */ +png_do_read_swap_alpha(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_read_swap_alpha"); + + { + png_uint_32 row_width = row_info->width; + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + /* This converts from RGBA to ARGB */ + if (row_info->bit_depth == 8) + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This converts from RRGGBBAA to AARRGGBB */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save[2]; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save[0] = *(--sp); + save[1] = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save[0]; + *(--dp) = save[1]; + } + } +#endif + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + /* This converts from GA to AG */ + if (row_info->bit_depth == 8) + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This converts from GGAA to AAGG */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_byte save[2]; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + save[0] = *(--sp); + save[1] = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = save[0]; + *(--dp) = save[1]; + } + } +#endif + } + } +} +#endif + +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED +void /* PRIVATE */ +png_do_read_invert_alpha(png_row_infop row_info, png_bytep row) +{ + png_uint_32 row_width; + png_debug(1, "in png_do_read_invert_alpha"); + + row_width = row_info->width; + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in RGBA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + +/* This does nothing: + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + We can replace it with: +*/ + sp-=3; + dp=sp; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + /* This inverts the alpha channel in RRGGBBAA */ + else + { + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = (png_byte)(255 - *(--sp)); + +/* This does nothing: + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + We can replace it with: +*/ + sp-=6; + dp=sp; + } + } +#endif + } + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in GA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = *(--sp); + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in GGAA */ + png_bytep sp = row + row_info->rowbytes; + png_bytep dp = sp; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + *(--dp) = (png_byte)(255 - *(--sp)); + *(--dp) = (png_byte)(255 - *(--sp)); +/* + *(--dp) = *(--sp); + *(--dp) = *(--sp); +*/ + sp-=2; + dp=sp; + } + } +#endif + } +} +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED +/* Add filler channel if we have RGB color */ +void /* PRIVATE */ +png_do_read_filler(png_row_infop row_info, png_bytep row, + png_uint_32 filler, png_uint_32 flags) +{ + png_uint_32 i; + png_uint_32 row_width = row_info->width; + +#ifdef PNG_READ_16BIT_SUPPORTED + png_byte hi_filler = (png_byte)((filler>>8) & 0xff); +#endif + png_byte lo_filler = (png_byte)(filler & 0xff); + + png_debug(1, "in png_do_read_filler"); + + if ( + row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + if (row_info->bit_depth == 8) + { + if (flags & PNG_FLAG_FILLER_AFTER) + { + /* This changes the data from G to GX */ + png_bytep sp = row + (png_size_t)row_width; + png_bytep dp = sp + (png_size_t)row_width; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + row_info->channels = 2; + row_info->pixel_depth = 16; + row_info->rowbytes = row_width * 2; + } + + else + { + /* This changes the data from G to XG */ + png_bytep sp = row + (png_size_t)row_width; + png_bytep dp = sp + (png_size_t)row_width; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = lo_filler; + } + row_info->channels = 2; + row_info->pixel_depth = 16; + row_info->rowbytes = row_width * 2; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if (flags & PNG_FLAG_FILLER_AFTER) + { + /* This changes the data from GG to GGXX */ + png_bytep sp = row + (png_size_t)row_width * 2; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 1; i < row_width; i++) + { + *(--dp) = hi_filler; + *(--dp) = lo_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = hi_filler; + *(--dp) = lo_filler; + row_info->channels = 2; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + + else + { + /* This changes the data from GG to XXGG */ + png_bytep sp = row + (png_size_t)row_width * 2; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = hi_filler; + *(--dp) = lo_filler; + } + row_info->channels = 2; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + } +#endif + } /* COLOR_TYPE == GRAY */ + else if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + if (row_info->bit_depth == 8) + { + if (flags & PNG_FLAG_FILLER_AFTER) + { + /* This changes the data from RGB to RGBX */ + png_bytep sp = row + (png_size_t)row_width * 3; + png_bytep dp = sp + (png_size_t)row_width; + for (i = 1; i < row_width; i++) + { + *(--dp) = lo_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = lo_filler; + row_info->channels = 4; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + + else + { + /* This changes the data from RGB to XRGB */ + png_bytep sp = row + (png_size_t)row_width * 3; + png_bytep dp = sp + (png_size_t)row_width; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = lo_filler; + } + row_info->channels = 4; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + } + } + +#ifdef PNG_READ_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if (flags & PNG_FLAG_FILLER_AFTER) + { + /* This changes the data from RRGGBB to RRGGBBXX */ + png_bytep sp = row + (png_size_t)row_width * 6; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 1; i < row_width; i++) + { + *(--dp) = hi_filler; + *(--dp) = lo_filler; + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + } + *(--dp) = hi_filler; + *(--dp) = lo_filler; + row_info->channels = 4; + row_info->pixel_depth = 64; + row_info->rowbytes = row_width * 8; + } + + else + { + /* This changes the data from RRGGBB to XXRRGGBB */ + png_bytep sp = row + (png_size_t)row_width * 6; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = *(--sp); + *(--dp) = hi_filler; + *(--dp) = lo_filler; + } + + row_info->channels = 4; + row_info->pixel_depth = 64; + row_info->rowbytes = row_width * 8; + } + } +#endif + } /* COLOR_TYPE == RGB */ +} +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED +/* Expand grayscale files to RGB, with or without alpha */ +void /* PRIVATE */ +png_do_gray_to_rgb(png_row_infop row_info, png_bytep row) +{ + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_gray_to_rgb"); + + if (row_info->bit_depth >= 8 && + !(row_info->color_type & PNG_COLOR_MASK_COLOR)) + { + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + if (row_info->bit_depth == 8) + { + /* This changes G to RGB */ + png_bytep sp = row + (png_size_t)row_width - 1; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(dp--) = *sp; + *(dp--) = *sp; + *(dp--) = *(sp--); + } + } + + else + { + /* This changes GG to RRGGBB */ + png_bytep sp = row + (png_size_t)row_width * 2 - 1; + png_bytep dp = sp + (png_size_t)row_width * 4; + for (i = 0; i < row_width; i++) + { + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *(sp--); + *(dp--) = *(sp--); + } + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This changes GA to RGBA */ + png_bytep sp = row + (png_size_t)row_width * 2 - 1; + png_bytep dp = sp + (png_size_t)row_width * 2; + for (i = 0; i < row_width; i++) + { + *(dp--) = *(sp--); + *(dp--) = *sp; + *(dp--) = *sp; + *(dp--) = *(sp--); + } + } + + else + { + /* This changes GGAA to RRGGBBAA */ + png_bytep sp = row + (png_size_t)row_width * 4 - 1; + png_bytep dp = sp + (png_size_t)row_width * 4; + for (i = 0; i < row_width; i++) + { + *(dp--) = *(sp--); + *(dp--) = *(sp--); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *sp; + *(dp--) = *(sp - 1); + *(dp--) = *(sp--); + *(dp--) = *(sp--); + } + } + } + row_info->channels = (png_byte)(row_info->channels + 2); + row_info->color_type |= PNG_COLOR_MASK_COLOR; + row_info->pixel_depth = (png_byte)(row_info->channels * + row_info->bit_depth); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } +} +#endif + +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED +/* Reduce RGB files to grayscale, with or without alpha + * using the equation given in Poynton's ColorFAQ of 1998-01-04 at + * (THIS LINK IS DEAD June 2008 but + * versions dated 1998 through November 2002 have been archived at + * http://web.archive.org/web/20000816232553/http://www.inforamp.net/ + * ~poynton/notes/colour_and_gamma/ColorFAQ.txt ) + * Charles Poynton poynton at poynton.com + * + * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B + * + * which can be expressed with integers as + * + * Y = (6969 * R + 23434 * G + 2365 * B)/32768 + * + * Poynton's current link (as of January 2003 through July 2011): + * + * has changed the numbers slightly: + * + * Y = 0.2126*R + 0.7152*G + 0.0722*B + * + * which can be expressed with integers as + * + * Y = (6966 * R + 23436 * G + 2366 * B)/32768 + * + * Historically, however, libpng uses numbers derived from the ITU-R Rec 709 + * end point chromaticities and the D65 white point. Depending on the + * precision used for the D65 white point this produces a variety of different + * numbers, however if the four decimal place value used in ITU-R Rec 709 is + * used (0.3127,0.3290) the Y calculation would be: + * + * Y = (6968 * R + 23435 * G + 2366 * B)/32768 + * + * While this is correct the rounding results in an overflow for white, because + * the sum of the rounded coefficients is 32769, not 32768. Consequently + * libpng uses, instead, the closest non-overflowing approximation: + * + * Y = (6968 * R + 23434 * G + 2366 * B)/32768 + * + * Starting with libpng-1.5.5, if the image being converted has a cHRM chunk + * (including an sRGB chunk) then the chromaticities are used to calculate the + * coefficients. See the chunk handling in pngrutil.c for more information. + * + * In all cases the calculation is to be done in a linear colorspace. If no + * gamma information is available to correct the encoding of the original RGB + * values this results in an implicit assumption that the original PNG RGB + * values were linear. + * + * Other integer coefficents can be used via png_set_rgb_to_gray(). Because + * the API takes just red and green coefficients the blue coefficient is + * calculated to make the sum 32768. This will result in different rounding + * to that used above. + */ +int /* PRIVATE */ +png_do_rgb_to_gray(png_structrp png_ptr, png_row_infop row_info, png_bytep row) + +{ + int rgb_error = 0; + + png_debug(1, "in png_do_rgb_to_gray"); + + if (!(row_info->color_type & PNG_COLOR_MASK_PALETTE) && + (row_info->color_type & PNG_COLOR_MASK_COLOR)) + { + PNG_CONST png_uint_32 rc = png_ptr->rgb_to_gray_red_coeff; + PNG_CONST png_uint_32 gc = png_ptr->rgb_to_gray_green_coeff; + PNG_CONST png_uint_32 bc = 32768 - rc - gc; + PNG_CONST png_uint_32 row_width = row_info->width; + PNG_CONST int have_alpha = + (row_info->color_type & PNG_COLOR_MASK_ALPHA) != 0; + + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + /* Notice that gamma to/from 1 are not necessarily inverses (if + * there is an overall gamma correction). Prior to 1.5.5 this code + * checked the linearized values for equality; this doesn't match + * the documentation, the original values must be checked. + */ + if (png_ptr->gamma_from_1 != NULL && png_ptr->gamma_to_1 != NULL) + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_byte red = *(sp++); + png_byte green = *(sp++); + png_byte blue = *(sp++); + + if (red != green || red != blue) + { + red = png_ptr->gamma_to_1[red]; + green = png_ptr->gamma_to_1[green]; + blue = png_ptr->gamma_to_1[blue]; + + rgb_error |= 1; + *(dp++) = png_ptr->gamma_from_1[ + (rc*red + gc*green + bc*blue + 16384)>>15]; + } + + else + { + /* If there is no overall correction the table will not be + * set. + */ + if (png_ptr->gamma_table != NULL) + red = png_ptr->gamma_table[red]; + + *(dp++) = red; + } + + if (have_alpha) + *(dp++) = *(sp++); + } + } + else +#endif + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_byte red = *(sp++); + png_byte green = *(sp++); + png_byte blue = *(sp++); + + if (red != green || red != blue) + { + rgb_error |= 1; + /* NOTE: this is the historical approach which simply + * truncates the results. + */ + *(dp++) = (png_byte)((rc*red + gc*green + bc*blue)>>15); + } + + else + *(dp++) = red; + + if (have_alpha) + *(dp++) = *(sp++); + } + } + } + + else /* RGB bit_depth == 16 */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (png_ptr->gamma_16_to_1 != NULL && png_ptr->gamma_16_from_1 != NULL) + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_uint_16 red, green, blue, w; + + red = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + green = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + blue = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + + if (red == green && red == blue) + { + if (png_ptr->gamma_16_table != NULL) + w = png_ptr->gamma_16_table[(red&0xff) + >> png_ptr->gamma_shift][red>>8]; + + else + w = red; + } + + else + { + png_uint_16 red_1 = png_ptr->gamma_16_to_1[(red&0xff) + >> png_ptr->gamma_shift][red>>8]; + png_uint_16 green_1 = + png_ptr->gamma_16_to_1[(green&0xff) >> + png_ptr->gamma_shift][green>>8]; + png_uint_16 blue_1 = png_ptr->gamma_16_to_1[(blue&0xff) + >> png_ptr->gamma_shift][blue>>8]; + png_uint_16 gray16 = (png_uint_16)((rc*red_1 + gc*green_1 + + bc*blue_1 + 16384)>>15); + w = png_ptr->gamma_16_from_1[(gray16&0xff) >> + png_ptr->gamma_shift][gray16 >> 8]; + rgb_error |= 1; + } + + *(dp++) = (png_byte)((w>>8) & 0xff); + *(dp++) = (png_byte)(w & 0xff); + + if (have_alpha) + { + *(dp++) = *(sp++); + *(dp++) = *(sp++); + } + } + } + else +#endif + { + png_bytep sp = row; + png_bytep dp = row; + png_uint_32 i; + + for (i = 0; i < row_width; i++) + { + png_uint_16 red, green, blue, gray16; + + red = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + green = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + blue = (png_uint_16)(((*(sp))<<8) | *(sp + 1)); sp += 2; + + if (red != green || red != blue) + rgb_error |= 1; + + /* From 1.5.5 in the 16 bit case do the accurate conversion even + * in the 'fast' case - this is because this is where the code + * ends up when handling linear 16 bit data. + */ + gray16 = (png_uint_16)((rc*red + gc*green + bc*blue + 16384) >> + 15); + *(dp++) = (png_byte)((gray16>>8) & 0xff); + *(dp++) = (png_byte)(gray16 & 0xff); + + if (have_alpha) + { + *(dp++) = *(sp++); + *(dp++) = *(sp++); + } + } + } + } + + row_info->channels = (png_byte)(row_info->channels - 2); + row_info->color_type = (png_byte)(row_info->color_type & + ~PNG_COLOR_MASK_COLOR); + row_info->pixel_depth = (png_byte)(row_info->channels * + row_info->bit_depth); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + return rgb_error; +} +#endif +#endif /* PNG_READ_TRANSFORMS_SUPPORTED */ + +#ifdef PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +/* Build a grayscale palette. Palette is assumed to be 1 << bit_depth + * large of png_color. This lets grayscale images be treated as + * paletted. Most useful for gamma correction and simplification + * of code. This API is not used internally. + */ +void PNGAPI +png_build_grayscale_palette(int bit_depth, png_colorp palette) +{ + int num_palette; + int color_inc; + int i; + int v; + + png_debug(1, "in png_do_build_grayscale_palette"); + + if (palette == NULL) + return; + + switch (bit_depth) + { + case 1: + num_palette = 2; + color_inc = 0xff; + break; + + case 2: + num_palette = 4; + color_inc = 0x55; + break; + + case 4: + num_palette = 16; + color_inc = 0x11; + break; + + case 8: + num_palette = 256; + color_inc = 1; + break; + + default: + num_palette = 0; + color_inc = 0; + break; + } + + for (i = 0, v = 0; i < num_palette; i++, v += color_inc) + { + palette[i].red = (png_byte)v; + palette[i].green = (png_byte)v; + palette[i].blue = (png_byte)v; + } +} +#endif + + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED +#if defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) +/* Replace any alpha or transparency with the supplied background color. + * "background" is already in the screen gamma, while "background_1" is + * at a gamma of 1.0. Paletted files have already been taken care of. + */ +void /* PRIVATE */ +png_do_compose(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ +#ifdef PNG_READ_GAMMA_SUPPORTED + png_const_bytep gamma_table = png_ptr->gamma_table; + png_const_bytep gamma_from_1 = png_ptr->gamma_from_1; + png_const_bytep gamma_to_1 = png_ptr->gamma_to_1; + png_const_uint_16pp gamma_16 = png_ptr->gamma_16_table; + png_const_uint_16pp gamma_16_from_1 = png_ptr->gamma_16_from_1; + png_const_uint_16pp gamma_16_to_1 = png_ptr->gamma_16_to_1; + int gamma_shift = png_ptr->gamma_shift; + int optimize = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0; +#endif + + png_bytep sp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + int shift; + + png_debug(1, "in png_do_compose"); + + { + switch (row_info->color_type) + { + case PNG_COLOR_TYPE_GRAY: + { + switch (row_info->bit_depth) + { + case 1: + { + sp = row; + shift = 7; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x01) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x7f7f >> (7 - shift)); + tmp |= png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (!shift) + { + shift = 7; + sp++; + } + + else + shift--; + } + break; + } + + case 2: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + shift = 6; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x03) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + else + { + unsigned int p = (*sp >> shift) & 0x03; + unsigned int g = (gamma_table [p | (p << 2) | + (p << 4) | (p << 6)] >> 6) & 0x03; + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= g << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (!shift) + { + shift = 6; + sp++; + } + + else + shift -= 2; + } + } + + else +#endif + { + sp = row; + shift = 6; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x03) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0x3f3f >> (6 - shift)); + tmp |= png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (!shift) + { + shift = 6; + sp++; + } + + else + shift -= 2; + } + } + break; + } + + case 4: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + shift = 4; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x0f) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0xf0f >> (4 - shift)); + tmp |= png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + else + { + unsigned int p = (*sp >> shift) & 0x0f; + unsigned int g = (gamma_table[p | (p << 4)] >> 4) & + 0x0f; + unsigned int tmp = *sp & (0xf0f >> (4 - shift)); + tmp |= g << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (!shift) + { + shift = 4; + sp++; + } + + else + shift -= 4; + } + } + + else +#endif + { + sp = row; + shift = 4; + for (i = 0; i < row_width; i++) + { + if ((png_uint_16)((*sp >> shift) & 0x0f) + == png_ptr->trans_color.gray) + { + unsigned int tmp = *sp & (0xf0f >> (4 - shift)); + tmp |= png_ptr->background.gray << shift; + *sp = (png_byte)(tmp & 0xff); + } + + if (!shift) + { + shift = 4; + sp++; + } + + else + shift -= 4; + } + } + break; + } + + case 8: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp++) + { + if (*sp == png_ptr->trans_color.gray) + *sp = (png_byte)png_ptr->background.gray; + + else + *sp = gamma_table[*sp]; + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp++) + { + if (*sp == png_ptr->trans_color.gray) + *sp = (png_byte)png_ptr->background.gray; + } + } + break; + } + + case 16: + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 v; + + v = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + if (v == png_ptr->trans_color.gray) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); + } + + else + { + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 v; + + v = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + if (v == png_ptr->trans_color.gray) + { + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray + & 0xff); + } + } + } + break; + } + + default: + break; + } + break; + } + + case PNG_COLOR_TYPE_RGB: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 3) + { + if (*sp == png_ptr->trans_color.red && + *(sp + 1) == png_ptr->trans_color.green && + *(sp + 2) == png_ptr->trans_color.blue) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else + { + *sp = gamma_table[*sp]; + *(sp + 1) = gamma_table[*(sp + 1)]; + *(sp + 2) = gamma_table[*(sp + 2)]; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 3) + { + if (*sp == png_ptr->trans_color.red && + *(sp + 1) == png_ptr->trans_color.green && + *(sp + 2) == png_ptr->trans_color.blue) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + } + } + } + else /* if (row_info->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 6) + { + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + if (r == png_ptr->trans_color.red && + g == png_ptr->trans_color.green && + b == png_ptr->trans_color.blue) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else + { + png_uint_16 v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)]; + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)]; + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + } + } + + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 6) + { + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + if (r == png_ptr->trans_color.red && + g == png_ptr->trans_color.green && + b == png_ptr->trans_color.blue) + { + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + } + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY_ALPHA: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_to_1 != NULL && gamma_from_1 != NULL && + gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_uint_16 a = *(sp + 1); + + if (a == 0xff) + *sp = gamma_table[*sp]; + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)png_ptr->background.gray; + } + + else + { + png_byte v, w; + + v = gamma_to_1[*sp]; + png_composite(w, v, a, png_ptr->background_1.gray); + if (!optimize) + w = gamma_from_1[w]; + *sp = w; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 2) + { + png_byte a = *(sp + 1); + + if (a == 0) + *sp = (png_byte)png_ptr->background.gray; + + else if (a < 0xff) + png_composite(*sp, *sp, a, png_ptr->background.gray); + } + } + } + else /* if (png_ptr->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL && gamma_16_from_1 != NULL && + gamma_16_to_1 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + if (a == (png_uint_16)0xffff) + { + png_uint_16 v; + + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + } + + else + { + png_uint_16 g, v, w; + + g = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; + png_composite_16(v, g, a, png_ptr->background_1.gray); + if (optimize) + w = v; + else + w = gamma_16_from_1[(v&0xff) >> gamma_shift][v >> 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_uint_16 a = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + + if (a == 0) + { + *sp = (png_byte)((png_ptr->background.gray >> 8) + & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.gray & 0xff); + } + + else if (a < 0xffff) + { + png_uint_16 g, v; + + g = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + png_composite_16(v, g, a, png_ptr->background.gray); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + } + } + } + } + break; + } + + case PNG_COLOR_TYPE_RGB_ALPHA: + { + if (row_info->bit_depth == 8) + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_to_1 != NULL && gamma_from_1 != NULL && + gamma_table != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_byte a = *(sp + 3); + + if (a == 0xff) + { + *sp = gamma_table[*sp]; + *(sp + 1) = gamma_table[*(sp + 1)]; + *(sp + 2) = gamma_table[*(sp + 2)]; + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else + { + png_byte v, w; + + v = gamma_to_1[*sp]; + png_composite(w, v, a, png_ptr->background_1.red); + if (!optimize) w = gamma_from_1[w]; + *sp = w; + + v = gamma_to_1[*(sp + 1)]; + png_composite(w, v, a, png_ptr->background_1.green); + if (!optimize) w = gamma_from_1[w]; + *(sp + 1) = w; + + v = gamma_to_1[*(sp + 2)]; + png_composite(w, v, a, png_ptr->background_1.blue); + if (!optimize) w = gamma_from_1[w]; + *(sp + 2) = w; + } + } + } + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 4) + { + png_byte a = *(sp + 3); + + if (a == 0) + { + *sp = (png_byte)png_ptr->background.red; + *(sp + 1) = (png_byte)png_ptr->background.green; + *(sp + 2) = (png_byte)png_ptr->background.blue; + } + + else if (a < 0xff) + { + png_composite(*sp, *sp, a, png_ptr->background.red); + + png_composite(*(sp + 1), *(sp + 1), a, + png_ptr->background.green); + + png_composite(*(sp + 2), *(sp + 2), a, + png_ptr->background.blue); + } + } + } + } + else /* if (row_info->bit_depth == 16) */ + { +#ifdef PNG_READ_GAMMA_SUPPORTED + if (gamma_16 != NULL && gamma_16_from_1 != NULL && + gamma_16_to_1 != NULL) + { + sp = row; + for (i = 0; i < row_width; i++, sp += 8) + { + png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) + << 8) + (png_uint_16)(*(sp + 7))); + + if (a == (png_uint_16)0xffff) + { + png_uint_16 v; + + v = gamma_16[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 3) >> gamma_shift][*(sp + 2)]; + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + v = gamma_16[*(sp + 5) >> gamma_shift][*(sp + 4)]; + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + + else if (a == 0) + { + /* Background is already in screen gamma */ + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else + { + png_uint_16 v, w; + + v = gamma_16_to_1[*(sp + 1) >> gamma_shift][*sp]; + png_composite_16(w, v, a, png_ptr->background_1.red); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> + 8]; + *sp = (png_byte)((w >> 8) & 0xff); + *(sp + 1) = (png_byte)(w & 0xff); + + v = gamma_16_to_1[*(sp + 3) >> gamma_shift][*(sp + 2)]; + png_composite_16(w, v, a, png_ptr->background_1.green); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> + 8]; + + *(sp + 2) = (png_byte)((w >> 8) & 0xff); + *(sp + 3) = (png_byte)(w & 0xff); + + v = gamma_16_to_1[*(sp + 5) >> gamma_shift][*(sp + 4)]; + png_composite_16(w, v, a, png_ptr->background_1.blue); + if (!optimize) + w = gamma_16_from_1[((w&0xff) >> gamma_shift)][w >> + 8]; + + *(sp + 4) = (png_byte)((w >> 8) & 0xff); + *(sp + 5) = (png_byte)(w & 0xff); + } + } + } + + else +#endif + { + sp = row; + for (i = 0; i < row_width; i++, sp += 8) + { + png_uint_16 a = (png_uint_16)(((png_uint_16)(*(sp + 6)) + << 8) + (png_uint_16)(*(sp + 7))); + + if (a == 0) + { + *sp = (png_byte)((png_ptr->background.red >> 8) & 0xff); + *(sp + 1) = (png_byte)(png_ptr->background.red & 0xff); + *(sp + 2) = (png_byte)((png_ptr->background.green >> 8) + & 0xff); + *(sp + 3) = (png_byte)(png_ptr->background.green + & 0xff); + *(sp + 4) = (png_byte)((png_ptr->background.blue >> 8) + & 0xff); + *(sp + 5) = (png_byte)(png_ptr->background.blue & 0xff); + } + + else if (a < 0xffff) + { + png_uint_16 v; + + png_uint_16 r = (png_uint_16)(((*sp) << 8) + *(sp + 1)); + png_uint_16 g = (png_uint_16)(((*(sp + 2)) << 8) + + *(sp + 3)); + png_uint_16 b = (png_uint_16)(((*(sp + 4)) << 8) + + *(sp + 5)); + + png_composite_16(v, r, a, png_ptr->background.red); + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + + png_composite_16(v, g, a, png_ptr->background.green); + *(sp + 2) = (png_byte)((v >> 8) & 0xff); + *(sp + 3) = (png_byte)(v & 0xff); + + png_composite_16(v, b, a, png_ptr->background.blue); + *(sp + 4) = (png_byte)((v >> 8) & 0xff); + *(sp + 5) = (png_byte)(v & 0xff); + } + } + } + } + break; + } + + default: + break; + } + } +} +#endif /* PNG_READ_BACKGROUND_SUPPORTED || PNG_READ_ALPHA_MODE_SUPPORTED */ + +#ifdef PNG_READ_GAMMA_SUPPORTED +/* Gamma correct the image, avoiding the alpha channel. Make sure + * you do this after you deal with the transparency issue on grayscale + * or RGB images. If your bit depth is 8, use gamma_table, if it + * is 16, use gamma_16_table and gamma_shift. Build these with + * build_gamma_table(). + */ +void /* PRIVATE */ +png_do_gamma(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ + png_const_bytep gamma_table = png_ptr->gamma_table; + png_const_uint_16pp gamma_16_table = png_ptr->gamma_16_table; + int gamma_shift = png_ptr->gamma_shift; + + png_bytep sp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_gamma"); + + if (((row_info->bit_depth <= 8 && gamma_table != NULL) || + (row_info->bit_depth == 16 && gamma_16_table != NULL))) + { + switch (row_info->color_type) + { + case PNG_COLOR_TYPE_RGB: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + *sp = gamma_table[*sp]; + sp++; + *sp = gamma_table[*sp]; + sp++; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + } + } + break; + } + + case PNG_COLOR_TYPE_RGB_ALPHA: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + + *sp = gamma_table[*sp]; + sp++; + + *sp = gamma_table[*sp]; + sp++; + + sp++; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + + v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 4; + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY_ALPHA: + { + if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp += 2; + } + } + + else /* if (row_info->bit_depth == 16) */ + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 4; + } + } + break; + } + + case PNG_COLOR_TYPE_GRAY: + { + if (row_info->bit_depth == 2) + { + sp = row; + for (i = 0; i < row_width; i += 4) + { + int a = *sp & 0xc0; + int b = *sp & 0x30; + int c = *sp & 0x0c; + int d = *sp & 0x03; + + *sp = (png_byte)( + ((((int)gamma_table[a|(a>>2)|(a>>4)|(a>>6)]) ) & 0xc0)| + ((((int)gamma_table[(b<<2)|b|(b>>2)|(b>>4)])>>2) & 0x30)| + ((((int)gamma_table[(c<<4)|(c<<2)|c|(c>>2)])>>4) & 0x0c)| + ((((int)gamma_table[(d<<6)|(d<<4)|(d<<2)|d])>>6) )); + sp++; + } + } + + if (row_info->bit_depth == 4) + { + sp = row; + for (i = 0; i < row_width; i += 2) + { + int msb = *sp & 0xf0; + int lsb = *sp & 0x0f; + + *sp = (png_byte)((((int)gamma_table[msb | (msb >> 4)]) & 0xf0) + | (((int)gamma_table[(lsb << 4) | lsb]) >> 4)); + sp++; + } + } + + else if (row_info->bit_depth == 8) + { + sp = row; + for (i = 0; i < row_width; i++) + { + *sp = gamma_table[*sp]; + sp++; + } + } + + else if (row_info->bit_depth == 16) + { + sp = row; + for (i = 0; i < row_width; i++) + { + png_uint_16 v = gamma_16_table[*(sp + 1) >> gamma_shift][*sp]; + *sp = (png_byte)((v >> 8) & 0xff); + *(sp + 1) = (png_byte)(v & 0xff); + sp += 2; + } + } + break; + } + + default: + break; + } + } +} +#endif + +#ifdef PNG_READ_ALPHA_MODE_SUPPORTED +/* Encode the alpha channel to the output gamma (the input channel is always + * linear.) Called only with color types that have an alpha channel. Needs the + * from_1 tables. + */ +void /* PRIVATE */ +png_do_encode_alpha(png_row_infop row_info, png_bytep row, png_structrp png_ptr) +{ + png_uint_32 row_width = row_info->width; + + png_debug(1, "in png_do_encode_alpha"); + + if (row_info->color_type & PNG_COLOR_MASK_ALPHA) + { + if (row_info->bit_depth == 8) + { + PNG_CONST png_bytep table = png_ptr->gamma_from_1; + + if (table != NULL) + { + PNG_CONST int step = + (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 4 : 2; + + /* The alpha channel is the last component: */ + row += step - 1; + + for (; row_width > 0; --row_width, row += step) + *row = table[*row]; + + return; + } + } + + else if (row_info->bit_depth == 16) + { + PNG_CONST png_uint_16pp table = png_ptr->gamma_16_from_1; + PNG_CONST int gamma_shift = png_ptr->gamma_shift; + + if (table != NULL) + { + PNG_CONST int step = + (row_info->color_type & PNG_COLOR_MASK_COLOR) ? 8 : 4; + + /* The alpha channel is the last component: */ + row += step - 2; + + for (; row_width > 0; --row_width, row += step) + { + png_uint_16 v; + + v = table[*(row + 1) >> gamma_shift][*row]; + *row = (png_byte)((v >> 8) & 0xff); + *(row + 1) = (png_byte)(v & 0xff); + } + + return; + } + } + } + + /* Only get to here if called with a weird row_info; no harm has been done, + * so just issue a warning. + */ + png_warning(png_ptr, "png_do_encode_alpha: unexpected call"); +} +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED +/* Expands a palette row to an RGB or RGBA row depending + * upon whether you supply trans and num_trans. + */ +void /* PRIVATE */ +png_do_expand_palette(png_row_infop row_info, png_bytep row, + png_const_colorp palette, png_const_bytep trans_alpha, int num_trans) +{ + int shift, value; + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_expand_palette"); + + if (row_info->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (row_info->bit_depth < 8) + { + switch (row_info->bit_depth) + { + case 1: + { + sp = row + (png_size_t)((row_width - 1) >> 3); + dp = row + (png_size_t)row_width - 1; + shift = 7 - (int)((row_width + 7) & 0x07); + for (i = 0; i < row_width; i++) + { + if ((*sp >> shift) & 0x01) + *dp = 1; + + else + *dp = 0; + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + sp = row + (png_size_t)((row_width - 1) >> 2); + dp = row + (png_size_t)row_width - 1; + shift = (int)((3 - ((row_width + 3) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x03; + *dp = (png_byte)value; + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + sp = row + (png_size_t)((row_width - 1) >> 1); + dp = row + (png_size_t)row_width - 1; + shift = (int)((row_width & 0x01) << 2); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x0f; + *dp = (png_byte)value; + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift += 4; + + dp--; + } + break; + } + + default: + break; + } + row_info->bit_depth = 8; + row_info->pixel_depth = 8; + row_info->rowbytes = row_width; + } + + if (row_info->bit_depth == 8) + { + { + if (num_trans > 0) + { + sp = row + (png_size_t)row_width - 1; + dp = row + (png_size_t)(row_width << 2) - 1; + + for (i = 0; i < row_width; i++) + { + if ((int)(*sp) >= num_trans) + *dp-- = 0xff; + + else + *dp-- = trans_alpha[*sp]; + + *dp-- = palette[*sp].blue; + *dp-- = palette[*sp].green; + *dp-- = palette[*sp].red; + sp--; + } + row_info->bit_depth = 8; + row_info->pixel_depth = 32; + row_info->rowbytes = row_width * 4; + row_info->color_type = 6; + row_info->channels = 4; + } + + else + { + sp = row + (png_size_t)row_width - 1; + dp = row + (png_size_t)(row_width * 3) - 1; + + for (i = 0; i < row_width; i++) + { + *dp-- = palette[*sp].blue; + *dp-- = palette[*sp].green; + *dp-- = palette[*sp].red; + sp--; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = 24; + row_info->rowbytes = row_width * 3; + row_info->color_type = 2; + row_info->channels = 3; + } + } + } + } +} + +/* If the bit depth < 8, it is expanded to 8. Also, if the already + * expanded transparency value is supplied, an alpha channel is built. + */ +void /* PRIVATE */ +png_do_expand(png_row_infop row_info, png_bytep row, + png_const_color_16p trans_color) +{ + int shift, value; + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_expand"); + + { + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + unsigned int gray = trans_color ? trans_color->gray : 0; + + if (row_info->bit_depth < 8) + { + switch (row_info->bit_depth) + { + case 1: + { + gray = (gray & 0x01) * 0xff; + sp = row + (png_size_t)((row_width - 1) >> 3); + dp = row + (png_size_t)row_width - 1; + shift = 7 - (int)((row_width + 7) & 0x07); + for (i = 0; i < row_width; i++) + { + if ((*sp >> shift) & 0x01) + *dp = 0xff; + + else + *dp = 0; + + if (shift == 7) + { + shift = 0; + sp--; + } + + else + shift++; + + dp--; + } + break; + } + + case 2: + { + gray = (gray & 0x03) * 0x55; + sp = row + (png_size_t)((row_width - 1) >> 2); + dp = row + (png_size_t)row_width - 1; + shift = (int)((3 - ((row_width + 3) & 0x03)) << 1); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x03; + *dp = (png_byte)(value | (value << 2) | (value << 4) | + (value << 6)); + if (shift == 6) + { + shift = 0; + sp--; + } + + else + shift += 2; + + dp--; + } + break; + } + + case 4: + { + gray = (gray & 0x0f) * 0x11; + sp = row + (png_size_t)((row_width - 1) >> 1); + dp = row + (png_size_t)row_width - 1; + shift = (int)((1 - ((row_width + 1) & 0x01)) << 2); + for (i = 0; i < row_width; i++) + { + value = (*sp >> shift) & 0x0f; + *dp = (png_byte)(value | (value << 4)); + if (shift == 4) + { + shift = 0; + sp--; + } + + else + shift = 4; + + dp--; + } + break; + } + + default: + break; + } + + row_info->bit_depth = 8; + row_info->pixel_depth = 8; + row_info->rowbytes = row_width; + } + + if (trans_color != NULL) + { + if (row_info->bit_depth == 8) + { + gray = gray & 0xff; + sp = row + (png_size_t)row_width - 1; + dp = row + (png_size_t)(row_width << 1) - 1; + + for (i = 0; i < row_width; i++) + { + if (*sp == gray) + *dp-- = 0; + + else + *dp-- = 0xff; + + *dp-- = *sp--; + } + } + + else if (row_info->bit_depth == 16) + { + unsigned int gray_high = (gray >> 8) & 0xff; + unsigned int gray_low = gray & 0xff; + sp = row + row_info->rowbytes - 1; + dp = row + (row_info->rowbytes << 1) - 1; + for (i = 0; i < row_width; i++) + { + if (*(sp - 1) == gray_high && *(sp) == gray_low) + { + *dp-- = 0; + *dp-- = 0; + } + + else + { + *dp-- = 0xff; + *dp-- = 0xff; + } + + *dp-- = *sp--; + *dp-- = *sp--; + } + } + + row_info->color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + row_info->channels = 2; + row_info->pixel_depth = (png_byte)(row_info->bit_depth << 1); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_width); + } + } + else if (row_info->color_type == PNG_COLOR_TYPE_RGB && trans_color) + { + if (row_info->bit_depth == 8) + { + png_byte red = (png_byte)(trans_color->red & 0xff); + png_byte green = (png_byte)(trans_color->green & 0xff); + png_byte blue = (png_byte)(trans_color->blue & 0xff); + sp = row + (png_size_t)row_info->rowbytes - 1; + dp = row + (png_size_t)(row_width << 2) - 1; + for (i = 0; i < row_width; i++) + { + if (*(sp - 2) == red && *(sp - 1) == green && *(sp) == blue) + *dp-- = 0; + + else + *dp-- = 0xff; + + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + } + } + else if (row_info->bit_depth == 16) + { + png_byte red_high = (png_byte)((trans_color->red >> 8) & 0xff); + png_byte green_high = (png_byte)((trans_color->green >> 8) & 0xff); + png_byte blue_high = (png_byte)((trans_color->blue >> 8) & 0xff); + png_byte red_low = (png_byte)(trans_color->red & 0xff); + png_byte green_low = (png_byte)(trans_color->green & 0xff); + png_byte blue_low = (png_byte)(trans_color->blue & 0xff); + sp = row + row_info->rowbytes - 1; + dp = row + (png_size_t)(row_width << 3) - 1; + for (i = 0; i < row_width; i++) + { + if (*(sp - 5) == red_high && + *(sp - 4) == red_low && + *(sp - 3) == green_high && + *(sp - 2) == green_low && + *(sp - 1) == blue_high && + *(sp ) == blue_low) + { + *dp-- = 0; + *dp-- = 0; + } + + else + { + *dp-- = 0xff; + *dp-- = 0xff; + } + + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + *dp-- = *sp--; + } + } + row_info->color_type = PNG_COLOR_TYPE_RGB_ALPHA; + row_info->channels = 4; + row_info->pixel_depth = (png_byte)(row_info->bit_depth << 2); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + } +} +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED +/* If the bit depth is 8 and the color type is not a palette type expand the + * whole row to 16 bits. Has no effect otherwise. + */ +void /* PRIVATE */ +png_do_expand_16(png_row_infop row_info, png_bytep row) +{ + if (row_info->bit_depth == 8 && + row_info->color_type != PNG_COLOR_TYPE_PALETTE) + { + /* The row have a sequence of bytes containing [0..255] and we need + * to turn it into another row containing [0..65535], to do this we + * calculate: + * + * (input / 255) * 65535 + * + * Which happens to be exactly input * 257 and this can be achieved + * simply by byte replication in place (copying backwards). + */ + png_byte *sp = row + row_info->rowbytes; /* source, last byte + 1 */ + png_byte *dp = sp + row_info->rowbytes; /* destination, end + 1 */ + while (dp > sp) + dp[-2] = dp[-1] = *--sp, dp -= 2; + + row_info->rowbytes *= 2; + row_info->bit_depth = 16; + row_info->pixel_depth = (png_byte)(row_info->channels * 16); + } +} +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +void /* PRIVATE */ +png_do_quantize(png_row_infop row_info, png_bytep row, + png_const_bytep palette_lookup, png_const_bytep quantize_lookup) +{ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width=row_info->width; + + png_debug(1, "in png_do_quantize"); + + if (row_info->bit_depth == 8) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB && palette_lookup) + { + int r, g, b, p; + sp = row; + dp = row; + for (i = 0; i < row_width; i++) + { + r = *sp++; + g = *sp++; + b = *sp++; + + /* This looks real messy, but the compiler will reduce + * it down to a reasonable formula. For example, with + * 5 bits per color, we get: + * p = (((r >> 3) & 0x1f) << 10) | + * (((g >> 3) & 0x1f) << 5) | + * ((b >> 3) & 0x1f); + */ + p = (((r >> (8 - PNG_QUANTIZE_RED_BITS)) & + ((1 << PNG_QUANTIZE_RED_BITS) - 1)) << + (PNG_QUANTIZE_GREEN_BITS + PNG_QUANTIZE_BLUE_BITS)) | + (((g >> (8 - PNG_QUANTIZE_GREEN_BITS)) & + ((1 << PNG_QUANTIZE_GREEN_BITS) - 1)) << + (PNG_QUANTIZE_BLUE_BITS)) | + ((b >> (8 - PNG_QUANTIZE_BLUE_BITS)) & + ((1 << PNG_QUANTIZE_BLUE_BITS) - 1)); + + *dp++ = palette_lookup[p]; + } + + row_info->color_type = PNG_COLOR_TYPE_PALETTE; + row_info->channels = 1; + row_info->pixel_depth = row_info->bit_depth; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA && + palette_lookup != NULL) + { + int r, g, b, p; + sp = row; + dp = row; + for (i = 0; i < row_width; i++) + { + r = *sp++; + g = *sp++; + b = *sp++; + sp++; + + p = (((r >> (8 - PNG_QUANTIZE_RED_BITS)) & + ((1 << PNG_QUANTIZE_RED_BITS) - 1)) << + (PNG_QUANTIZE_GREEN_BITS + PNG_QUANTIZE_BLUE_BITS)) | + (((g >> (8 - PNG_QUANTIZE_GREEN_BITS)) & + ((1 << PNG_QUANTIZE_GREEN_BITS) - 1)) << + (PNG_QUANTIZE_BLUE_BITS)) | + ((b >> (8 - PNG_QUANTIZE_BLUE_BITS)) & + ((1 << PNG_QUANTIZE_BLUE_BITS) - 1)); + + *dp++ = palette_lookup[p]; + } + + row_info->color_type = PNG_COLOR_TYPE_PALETTE; + row_info->channels = 1; + row_info->pixel_depth = row_info->bit_depth; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, row_width); + } + + else if (row_info->color_type == PNG_COLOR_TYPE_PALETTE && + quantize_lookup) + { + sp = row; + + for (i = 0; i < row_width; i++, sp++) + { + *sp = quantize_lookup[*sp]; + } + } + } +} +#endif /* PNG_READ_QUANTIZE_SUPPORTED */ +#endif /* PNG_READ_TRANSFORMS_SUPPORTED */ + +#ifdef PNG_MNG_FEATURES_SUPPORTED +/* Undoes intrapixel differencing */ +void /* PRIVATE */ +png_do_read_intrapixel(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_read_intrapixel"); + + if ( + (row_info->color_type & PNG_COLOR_MASK_COLOR)) + { + int bytes_per_pixel; + png_uint_32 row_width = row_info->width; + + if (row_info->bit_depth == 8) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 3; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 4; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + *(rp) = (png_byte)((256 + *rp + *(rp + 1)) & 0xff); + *(rp+2) = (png_byte)((256 + *(rp + 2) + *(rp + 1)) & 0xff); + } + } + else if (row_info->bit_depth == 16) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 6; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 8; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + png_uint_32 s0 = (*(rp ) << 8) | *(rp + 1); + png_uint_32 s1 = (*(rp + 2) << 8) | *(rp + 3); + png_uint_32 s2 = (*(rp + 4) << 8) | *(rp + 5); + png_uint_32 red = (s0 + s1 + 65536) & 0xffff; + png_uint_32 blue = (s2 + s1 + 65536) & 0xffff; + *(rp ) = (png_byte)((red >> 8) & 0xff); + *(rp + 1) = (png_byte)(red & 0xff); + *(rp + 4) = (png_byte)((blue >> 8) & 0xff); + *(rp + 5) = (png_byte)(blue & 0xff); + } + } + } +} +#endif /* PNG_MNG_FEATURES_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrutil.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrutil.c new file mode 100644 index 0000000..b998fe6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngrutil.c @@ -0,0 +1,4459 @@ + +/* pngrutil.c - utilities to read a PNG file + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file contains routines that are only called from within + * libpng itself during the course of reading an image. + */ + +#include "pngpriv.h" + +#ifdef PNG_READ_SUPPORTED + +#define png_strtod(p,a,b) strtod(a,b) + +png_uint_32 PNGAPI +png_get_uint_31(png_const_structrp png_ptr, png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + + if (uval > PNG_UINT_31_MAX) + png_error(png_ptr, "PNG unsigned integer out of range"); + + return (uval); +} + +#if defined(PNG_READ_gAMA_SUPPORTED) || defined(PNG_READ_cHRM_SUPPORTED) +/* The following is a variation on the above for use with the fixed + * point values used for gAMA and cHRM. Instead of png_error it + * issues a warning and returns (-1) - an invalid value because both + * gAMA and cHRM use *unsigned* integers for fixed point values. + */ +#define PNG_FIXED_ERROR (-1) + +static png_fixed_point /* PRIVATE */ +png_get_fixed_point(png_structrp png_ptr, png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + + if (uval <= PNG_UINT_31_MAX) + return (png_fixed_point)uval; /* known to be in range */ + + /* The caller can turn off the warning by passing NULL. */ + if (png_ptr != NULL) + png_warning(png_ptr, "PNG fixed point integer out of range"); + + return PNG_FIXED_ERROR; +} +#endif + +#ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED +/* NOTE: the read macros will obscure these definitions, so that if + * PNG_USE_READ_MACROS is set the library will not use them internally, + * but the APIs will still be available externally. + * + * The parentheses around "PNGAPI function_name" in the following three + * functions are necessary because they allow the macros to co-exist with + * these (unused but exported) functions. + */ + +/* Grab an unsigned 32-bit integer from a buffer in big-endian format. */ +png_uint_32 (PNGAPI +png_get_uint_32)(png_const_bytep buf) +{ + png_uint_32 uval = + ((png_uint_32)(*(buf )) << 24) + + ((png_uint_32)(*(buf + 1)) << 16) + + ((png_uint_32)(*(buf + 2)) << 8) + + ((png_uint_32)(*(buf + 3)) ) ; + + return uval; +} + +/* Grab a signed 32-bit integer from a buffer in big-endian format. The + * data is stored in the PNG file in two's complement format and there + * is no guarantee that a 'png_int_32' is exactly 32 bits, therefore + * the following code does a two's complement to native conversion. + */ +png_int_32 (PNGAPI +png_get_int_32)(png_const_bytep buf) +{ + png_uint_32 uval = png_get_uint_32(buf); + if ((uval & 0x80000000) == 0) /* non-negative */ + return uval; + + uval = (uval ^ 0xffffffff) + 1; /* 2's complement: -x = ~x+1 */ + return -(png_int_32)uval; +} + +/* Grab an unsigned 16-bit integer from a buffer in big-endian format. */ +png_uint_16 (PNGAPI +png_get_uint_16)(png_const_bytep buf) +{ + /* ANSI-C requires an int value to accomodate at least 16 bits so this + * works and allows the compiler not to worry about possible narrowing + * on 32 bit systems. (Pre-ANSI systems did not make integers smaller + * than 16 bits either.) + */ + unsigned int val = + ((unsigned int)(*buf) << 8) + + ((unsigned int)(*(buf + 1))); + + return (png_uint_16)val; +} + +#endif /* PNG_READ_INT_FUNCTIONS_SUPPORTED */ + +/* Read and check the PNG file signature */ +void /* PRIVATE */ +png_read_sig(png_structrp png_ptr, png_inforp info_ptr) +{ + png_size_t num_checked, num_to_check; + + /* Exit if the user application does not expect a signature. */ + if (png_ptr->sig_bytes >= 8) + return; + + num_checked = png_ptr->sig_bytes; + num_to_check = 8 - num_checked; + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_SIGNATURE; +#endif + + /* The signature must be serialized in a single I/O call. */ + png_read_data(png_ptr, &(info_ptr->signature[num_checked]), num_to_check); + png_ptr->sig_bytes = 8; + + if (png_sig_cmp(info_ptr->signature, num_checked, num_to_check)) + { + if (num_checked < 4 && + png_sig_cmp(info_ptr->signature, num_checked, num_to_check - 4)) + png_error(png_ptr, "Not a PNG file"); + else + png_error(png_ptr, "PNG file corrupted by ASCII conversion"); + } + if (num_checked < 3) + png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE; +} + +/* Read the chunk header (length + type name). + * Put the type name into png_ptr->chunk_name, and return the length. + */ +png_uint_32 /* PRIVATE */ +png_read_chunk_header(png_structrp png_ptr) +{ + png_byte buf[8]; + png_uint_32 length; + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_HDR; +#endif + + /* Read the length and the chunk name. + * This must be performed in a single I/O call. + */ + png_read_data(png_ptr, buf, 8); + length = png_get_uint_31(png_ptr, buf); + + /* Put the chunk name into png_ptr->chunk_name. */ + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(buf+4); + + png_debug2(0, "Reading %lx chunk, length = %lu", + (unsigned long)png_ptr->chunk_name, (unsigned long)length); + + /* Reset the crc and run it over the chunk name. */ + png_reset_crc(png_ptr); + png_calculate_crc(png_ptr, buf + 4, 4); + + /* Check to see if chunk name is valid. */ + png_check_chunk_name(png_ptr, png_ptr->chunk_name); + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_DATA; +#endif + + return length; +} + +/* Read data, and (optionally) run it through the CRC. */ +void /* PRIVATE */ +png_crc_read(png_structrp png_ptr, png_bytep buf, png_uint_32 length) +{ + if (png_ptr == NULL) + return; + + png_read_data(png_ptr, buf, length); + png_calculate_crc(png_ptr, buf, length); +} + +/* Optionally skip data and then check the CRC. Depending on whether we + * are reading an ancillary or critical chunk, and how the program has set + * things up, we may calculate the CRC on the data and print a message. + * Returns '1' if there was a CRC error, '0' otherwise. + */ +int /* PRIVATE */ +png_crc_finish(png_structrp png_ptr, png_uint_32 skip) +{ + /* The size of the local buffer for inflate is a good guess as to a + * reasonable size to use for buffering reads from the application. + */ + while (skip > 0) + { + png_uint_32 len; + png_byte tmpbuf[PNG_INFLATE_BUF_SIZE]; + + len = (sizeof tmpbuf); + if (len > skip) + len = skip; + skip -= len; + + png_crc_read(png_ptr, tmpbuf, len); + } + + if (png_crc_error(png_ptr)) + { + if (PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name) ? + !(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN) : + (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_USE)) + { + png_chunk_warning(png_ptr, "CRC error"); + } + + else + { + png_chunk_benign_error(png_ptr, "CRC error"); + return (0); + } + + return (1); + } + + return (0); +} + +/* Compare the CRC stored in the PNG file with that calculated by libpng from + * the data it has read thus far. + */ +int /* PRIVATE */ +png_crc_error(png_structrp png_ptr) +{ + png_byte crc_bytes[4]; + png_uint_32 crc; + int need_crc = 1; + + if (PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name)) + { + if ((png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_MASK) == + (PNG_FLAG_CRC_ANCILLARY_USE | PNG_FLAG_CRC_ANCILLARY_NOWARN)) + need_crc = 0; + } + + else /* critical */ + { + if (png_ptr->flags & PNG_FLAG_CRC_CRITICAL_IGNORE) + need_crc = 0; + } + +#ifdef PNG_IO_STATE_SUPPORTED + png_ptr->io_state = PNG_IO_READING | PNG_IO_CHUNK_CRC; +#endif + + /* The chunk CRC must be serialized in a single I/O call. */ + png_read_data(png_ptr, crc_bytes, 4); + + if (need_crc) + { + crc = png_get_uint_32(crc_bytes); + return ((int)(crc != png_ptr->crc)); + } + + else + return (0); +} + +/* Manage the read buffer; this simply reallocates the buffer if it is not small + * enough (or if it is not allocated). The routine returns a pointer to the + * buffer; if an error occurs and 'warn' is set the routine returns NULL, else + * it will call png_error (via png_malloc) on failure. (warn == 2 means + * 'silent'). + */ +static png_bytep +png_read_buffer(png_structrp png_ptr, png_alloc_size_t new_size, int warn) +{ + png_bytep buffer = png_ptr->read_buffer; + + if (buffer != NULL && new_size > png_ptr->read_buffer_size) + { + png_ptr->read_buffer = NULL; + png_ptr->read_buffer = NULL; + png_ptr->read_buffer_size = 0; + png_free(png_ptr, buffer); + buffer = NULL; + } + + if (buffer == NULL) + { + buffer = png_voidcast(png_bytep, png_malloc_base(png_ptr, new_size)); + + if (buffer != NULL) + { + png_ptr->read_buffer = buffer; + png_ptr->read_buffer_size = new_size; + } + + else if (warn < 2) /* else silent */ + { +#ifdef PNG_WARNINGS_SUPPORTED + if (warn) + png_chunk_warning(png_ptr, "insufficient memory to read chunk"); + else +#endif + { +#ifdef PNG_ERROR_TEXT_SUPPORTED + png_chunk_error(png_ptr, "insufficient memory to read chunk"); +#endif + } + } + } + + return buffer; +} + +/* png_inflate_claim: claim the zstream for some nefarious purpose that involves + * decompression. Returns Z_OK on success, else a zlib error code. It checks + * the owner but, in final release builds, just issues a warning if some other + * chunk apparently owns the stream. Prior to release it does a png_error. + */ +static int +png_inflate_claim(png_structrp png_ptr, png_uint_32 owner, int window_bits) +{ + if (png_ptr->zowner != 0) + { + char msg[64]; + + PNG_STRING_FROM_CHUNK(msg, png_ptr->zowner); + /* So the message that results is " using zstream"; this is an + * internal error, but is very useful for debugging. i18n requirements + * are minimal. + */ + (void)png_safecat(msg, (sizeof msg), 4, " using zstream"); +# if PNG_LIBPNG_BUILD_BASE_TYPE >= PNG_LIBPNG_BUILD_RC + png_chunk_warning(png_ptr, msg); + png_ptr->zowner = 0; +# else + png_chunk_error(png_ptr, msg); +# endif + } + + /* Implementation note: unlike 'png_deflate_claim' this internal function + * does not take the size of the data as an argument. Some efficiency could + * be gained by using this when it is known *if* the zlib stream itself does + * not record the number; however, this is an illusion: the original writer + * of the PNG may have selected a lower window size, and we really must + * follow that because, for systems with with limited capabilities, we + * would otherwise reject the application's attempts to use a smaller window + * size (zlib doesn't have an interface to say "this or lower"!). + * + * inflateReset2 was added to zlib 1.2.4; before this the window could not be + * reset, therefore it is necessary to always allocate the maximum window + * size with earlier zlibs just in case later compressed chunks need it. + */ + { + int ret; /* zlib return code */ + + /* Set this for safety, just in case the previous owner left pointers to + * memory allocations. + */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->zstream.avail_out = 0; + + if (png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) + { +# if ZLIB_VERNUM < 0x1240 + PNG_UNUSED(window_bits) + ret = inflateReset(&png_ptr->zstream); +# else + ret = inflateReset2(&png_ptr->zstream, window_bits); +# endif + } + + else + { +# if ZLIB_VERNUM < 0x1240 + ret = inflateInit(&png_ptr->zstream); +# else + ret = inflateInit2(&png_ptr->zstream, window_bits); +# endif + + if (ret == Z_OK) + png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED; + } + + if (ret == Z_OK) + png_ptr->zowner = owner; + + else + png_zstream_error(png_ptr, ret); + + return ret; + } +} + +#ifdef PNG_READ_COMPRESSED_TEXT_SUPPORTED +/* png_inflate now returns zlib error codes including Z_OK and Z_STREAM_END to + * allow the caller to do multiple calls if required. If the 'finish' flag is + * set Z_FINISH will be passed to the final inflate() call and Z_STREAM_END must + * be returned or there has been a problem, otherwise Z_SYNC_FLUSH is used and + * Z_OK or Z_STREAM_END will be returned on success. + * + * The input and output sizes are updated to the actual amounts of data consumed + * or written, not the amount available (as in a z_stream). The data pointers + * are not changed, so the next input is (data+input_size) and the next + * available output is (output+output_size). + */ +static int +png_inflate(png_structrp png_ptr, png_uint_32 owner, int finish, + /* INPUT: */ png_const_bytep input, png_uint_32p input_size_ptr, + /* OUTPUT: */ png_bytep output, png_alloc_size_t *output_size_ptr) +{ + if (png_ptr->zowner == owner) /* Else not claimed */ + { + int ret; + png_alloc_size_t avail_out = *output_size_ptr; + png_uint_32 avail_in = *input_size_ptr; + + /* zlib can't necessarily handle more than 65535 bytes at once (i.e. it + * can't even necessarily handle 65536 bytes) because the type uInt is + * "16 bits or more". Consequently it is necessary to chunk the input to + * zlib. This code uses ZLIB_IO_MAX, from pngpriv.h, as the maximum (the + * maximum value that can be stored in a uInt.) It is possible to set + * ZLIB_IO_MAX to a lower value in pngpriv.h and this may sometimes have + * a performance advantage, because it reduces the amount of data accessed + * at each step and that may give the OS more time to page it in. + */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input); + /* avail_in and avail_out are set below from 'size' */ + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.avail_out = 0; + + /* Read directly into the output if it is available (this is set to + * a local buffer below if output is NULL). + */ + if (output != NULL) + png_ptr->zstream.next_out = output; + + do + { + uInt avail; + unsigned char local_buffer[PNG_INFLATE_BUF_SIZE]; + + /* zlib INPUT BUFFER */ + /* The setting of 'avail_in' used to be outside the loop; by setting it + * inside it is possible to chunk the input to zlib and simply rely on + * zlib to advance the 'next_in' pointer. This allows arbitrary + * amounts of data to be passed through zlib at the unavoidable cost of + * requiring a window save (memcpy of up to 32768 output bytes) + * every ZLIB_IO_MAX input bytes. + */ + avail_in += png_ptr->zstream.avail_in; /* not consumed last time */ + + avail = ZLIB_IO_MAX; + + if (avail_in < avail) + avail = (uInt)avail_in; /* safe: < than ZLIB_IO_MAX */ + + avail_in -= avail; + png_ptr->zstream.avail_in = avail; + + /* zlib OUTPUT BUFFER */ + avail_out += png_ptr->zstream.avail_out; /* not written last time */ + + avail = ZLIB_IO_MAX; /* maximum zlib can process */ + + if (output == NULL) + { + /* Reset the output buffer each time round if output is NULL and + * make available the full buffer, up to 'remaining_space' + */ + png_ptr->zstream.next_out = local_buffer; + if ((sizeof local_buffer) < avail) + avail = (sizeof local_buffer); + } + + if (avail_out < avail) + avail = (uInt)avail_out; /* safe: < ZLIB_IO_MAX */ + + png_ptr->zstream.avail_out = avail; + avail_out -= avail; + + /* zlib inflate call */ + /* In fact 'avail_out' may be 0 at this point, that happens at the end + * of the read when the final LZ end code was not passed at the end of + * the previous chunk of input data. Tell zlib if we have reached the + * end of the output buffer. + */ + ret = inflate(&png_ptr->zstream, avail_out > 0 ? Z_NO_FLUSH : + (finish ? Z_FINISH : Z_SYNC_FLUSH)); + } while (ret == Z_OK); + + /* For safety kill the local buffer pointer now */ + if (output == NULL) + png_ptr->zstream.next_out = NULL; + + /* Claw back the 'size' and 'remaining_space' byte counts. */ + avail_in += png_ptr->zstream.avail_in; + avail_out += png_ptr->zstream.avail_out; + + /* Update the input and output sizes; the updated values are the amount + * consumed or written, effectively the inverse of what zlib uses. + */ + if (avail_out > 0) + *output_size_ptr -= avail_out; + + if (avail_in > 0) + *input_size_ptr -= avail_in; + + /* Ensure png_ptr->zstream.msg is set (even in the success case!) */ + png_zstream_error(png_ptr, ret); + return ret; + } + + else + { + /* This is a bad internal error. The recovery assigns to the zstream msg + * pointer, which is not owned by the caller, but this is safe; it's only + * used on errors! + */ + png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed"); + return Z_STREAM_ERROR; + } +} + +/* + * Decompress trailing data in a chunk. The assumption is that read_buffer + * points at an allocated area holding the contents of a chunk with a + * trailing compressed part. What we get back is an allocated area + * holding the original prefix part and an uncompressed version of the + * trailing part (the malloc area passed in is freed). + */ +static int +png_decompress_chunk(png_structrp png_ptr, + png_uint_32 chunklength, png_uint_32 prefix_size, + png_alloc_size_t *newlength /* must be initialized to the maximum! */, + int terminate /*add a '\0' to the end of the uncompressed data*/) +{ + /* TODO: implement different limits for different types of chunk. + * + * The caller supplies *newlength set to the maximum length of the + * uncompressed data, but this routine allocates space for the prefix and + * maybe a '\0' terminator too. We have to assume that 'prefix_size' is + * limited only by the maximum chunk size. + */ + png_alloc_size_t limit = PNG_SIZE_MAX; + +# ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + + if (limit >= prefix_size + (terminate != 0)) + { + int ret; + + limit -= prefix_size + (terminate != 0); + + if (limit < *newlength) + *newlength = limit; + + /* Now try to claim the stream; the 'warn' setting causes zlib to be told + * to use the maximum window size during inflate; this hides errors in the + * deflate header window bits value which is used if '0' is passed. In + * fact this only has an effect with zlib versions 1.2.4 and later - see + * the comments in png_inflate_claim above. + */ + ret = png_inflate_claim(png_ptr, png_ptr->chunk_name, + png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN ? 15 : 0); + + if (ret == Z_OK) + { + png_uint_32 lzsize = chunklength - prefix_size; + + ret = png_inflate(png_ptr, png_ptr->chunk_name, 1/*finish*/, + /* input: */ png_ptr->read_buffer + prefix_size, &lzsize, + /* output: */ NULL, newlength); + + if (ret == Z_STREAM_END) + { + /* Use 'inflateReset' here, not 'inflateReset2' because this + * preserves the previously decided window size (otherwise it would + * be necessary to store the previous window size.) In practice + * this doesn't matter anyway, because png_inflate will call inflate + * with Z_FINISH in almost all cases, so the window will not be + * maintained. + */ + if (inflateReset(&png_ptr->zstream) == Z_OK) + { + /* Because of the limit checks above we know that the new, + * expanded, size will fit in a size_t (let alone an + * png_alloc_size_t). Use png_malloc_base here to avoid an + * extra OOM message. + */ + png_alloc_size_t new_size = *newlength; + png_alloc_size_t buffer_size = prefix_size + new_size + + (terminate != 0); + png_bytep text = png_voidcast(png_bytep, png_malloc_base(png_ptr, + buffer_size)); + + if (text != NULL) + { + ret = png_inflate(png_ptr, png_ptr->chunk_name, 1/*finish*/, + png_ptr->read_buffer + prefix_size, &lzsize, + text + prefix_size, newlength); + + if (ret == Z_STREAM_END) + { + if (new_size == *newlength) + { + if (terminate) + text[prefix_size + *newlength] = 0; + + if (prefix_size > 0) + memcpy(text, png_ptr->read_buffer, prefix_size); + + { + png_bytep old_ptr = png_ptr->read_buffer; + + png_ptr->read_buffer = text; + png_ptr->read_buffer_size = buffer_size; + text = old_ptr; /* freed below */ + } + } + + else + { + /* The size changed on the second read, there can be no + * guarantee that anything is correct at this point. + * The 'msg' pointer has been set to "unexpected end of + * LZ stream", which is fine, but return an error code + * that the caller won't accept. + */ + ret = PNG_UNEXPECTED_ZLIB_RETURN; + } + } + + else if (ret == Z_OK) + ret = PNG_UNEXPECTED_ZLIB_RETURN; /* for safety */ + + /* Free the text pointer (this is the old read_buffer on + * success) + */ + png_free(png_ptr, text); + + /* This really is very benign, but it's still an error because + * the extra space may otherwise be used as a Trojan Horse. + */ + if (ret == Z_STREAM_END && + chunklength - prefix_size != lzsize) + png_chunk_benign_error(png_ptr, "extra compressed data"); + } + + else + { + /* Out of memory allocating the buffer */ + ret = Z_MEM_ERROR; + png_zstream_error(png_ptr, Z_MEM_ERROR); + } + } + + else + { + /* inflateReset failed, store the error message */ + png_zstream_error(png_ptr, ret); + + if (ret == Z_STREAM_END) + ret = PNG_UNEXPECTED_ZLIB_RETURN; + } + } + + else if (ret == Z_OK) + ret = PNG_UNEXPECTED_ZLIB_RETURN; + + /* Release the claimed stream */ + png_ptr->zowner = 0; + } + + else /* the claim failed */ if (ret == Z_STREAM_END) /* impossible! */ + ret = PNG_UNEXPECTED_ZLIB_RETURN; + + return ret; + } + + else + { + /* Application/configuration limits exceeded */ + png_zstream_error(png_ptr, Z_MEM_ERROR); + return Z_MEM_ERROR; + } +} +#endif /* PNG_READ_COMPRESSED_TEXT_SUPPORTED */ + +#ifdef PNG_READ_iCCP_SUPPORTED +/* Perform a partial read and decompress, producing 'avail_out' bytes and + * reading from the current chunk as required. + */ +static int +png_inflate_read(png_structrp png_ptr, png_bytep read_buffer, uInt read_size, + png_uint_32p chunk_bytes, png_bytep next_out, png_alloc_size_t *out_size, + int finish) +{ + if (png_ptr->zowner == png_ptr->chunk_name) + { + int ret; + + /* next_in and avail_in must have been initialized by the caller. */ + png_ptr->zstream.next_out = next_out; + png_ptr->zstream.avail_out = 0; /* set in the loop */ + + do + { + if (png_ptr->zstream.avail_in == 0) + { + if (read_size > *chunk_bytes) + read_size = (uInt)*chunk_bytes; + *chunk_bytes -= read_size; + + if (read_size > 0) + png_crc_read(png_ptr, read_buffer, read_size); + + png_ptr->zstream.next_in = read_buffer; + png_ptr->zstream.avail_in = read_size; + } + + if (png_ptr->zstream.avail_out == 0) + { + uInt avail = ZLIB_IO_MAX; + if (avail > *out_size) + avail = (uInt)*out_size; + *out_size -= avail; + + png_ptr->zstream.avail_out = avail; + } + + /* Use Z_SYNC_FLUSH when there is no more chunk data to ensure that all + * the available output is produced; this allows reading of truncated + * streams. + */ + ret = inflate(&png_ptr->zstream, + *chunk_bytes > 0 ? Z_NO_FLUSH : (finish ? Z_FINISH : Z_SYNC_FLUSH)); + } + while (ret == Z_OK && (*out_size > 0 || png_ptr->zstream.avail_out > 0)); + + *out_size += png_ptr->zstream.avail_out; + png_ptr->zstream.avail_out = 0; /* Should not be required, but is safe */ + + /* Ensure the error message pointer is always set: */ + png_zstream_error(png_ptr, ret); + return ret; + } + + else + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed"); + return Z_STREAM_ERROR; + } +} +#endif + +/* Read and check the IDHR chunk */ +void /* PRIVATE */ +png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[13]; + png_uint_32 width, height; + int bit_depth, color_type, compression_type, filter_type; + int interlace_type; + + png_debug(1, "in png_handle_IHDR"); + + if (png_ptr->mode & PNG_HAVE_IHDR) + png_chunk_error(png_ptr, "out of place"); + + /* Check the length */ + if (length != 13) + png_chunk_error(png_ptr, "invalid"); + + png_ptr->mode |= PNG_HAVE_IHDR; + + png_crc_read(png_ptr, buf, 13); + png_crc_finish(png_ptr, 0); + + width = png_get_uint_31(png_ptr, buf); + height = png_get_uint_31(png_ptr, buf + 4); + bit_depth = buf[8]; + color_type = buf[9]; + compression_type = buf[10]; + filter_type = buf[11]; + interlace_type = buf[12]; + + /* Set internal variables */ + png_ptr->width = width; + png_ptr->height = height; + png_ptr->bit_depth = (png_byte)bit_depth; + png_ptr->interlaced = (png_byte)interlace_type; + png_ptr->color_type = (png_byte)color_type; +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_ptr->filter_type = (png_byte)filter_type; +#endif + png_ptr->compression_type = (png_byte)compression_type; + + /* Find number of channels */ + switch (png_ptr->color_type) + { + default: /* invalid, png_set_IHDR calls png_error */ + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_PALETTE: + png_ptr->channels = 1; + break; + + case PNG_COLOR_TYPE_RGB: + png_ptr->channels = 3; + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + png_ptr->channels = 2; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + png_ptr->channels = 4; + break; + } + + /* Set up other useful info */ + png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * + png_ptr->channels); + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width); + png_debug1(3, "bit_depth = %d", png_ptr->bit_depth); + png_debug1(3, "channels = %d", png_ptr->channels); + png_debug1(3, "rowbytes = %lu", (unsigned long)png_ptr->rowbytes); + png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, + color_type, interlace_type, compression_type, filter_type); +} + +/* Read and check the palette */ +void /* PRIVATE */ +png_handle_PLTE(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_color palette[PNG_MAX_PALETTE_LENGTH]; + int num, i; +#ifdef PNG_POINTER_INDEXING_SUPPORTED + png_colorp pal_ptr; +#endif + + png_debug(1, "in png_handle_PLTE"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + /* Moved to before the 'after IDAT' check below because otherwise duplicate + * PLTE chunks are potentially ignored (the spec says there shall not be more + * than one PLTE, the error is not treated as benign, so this check trumps + * the requirement that PLTE appears before IDAT.) + */ + else if (png_ptr->mode & PNG_HAVE_PLTE) + png_chunk_error(png_ptr, "duplicate"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + /* This is benign because the non-benign error happened before, when an + * IDAT was encountered in a color-mapped image with no PLTE. + */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + png_ptr->mode |= PNG_HAVE_PLTE; + + if (!(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "ignored in grayscale PNG"); + return; + } + +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + { + png_crc_finish(png_ptr, length); + return; + } +#endif + + if (length > 3*PNG_MAX_PALETTE_LENGTH || length % 3) + { + png_crc_finish(png_ptr, length); + + if (png_ptr->color_type != PNG_COLOR_TYPE_PALETTE) + png_chunk_benign_error(png_ptr, "invalid"); + + else + png_chunk_error(png_ptr, "invalid"); + + return; + } + + /* The cast is safe because 'length' is less than 3*PNG_MAX_PALETTE_LENGTH */ + num = (int)length / 3; + +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (i = 0, pal_ptr = palette; i < num; i++, pal_ptr++) + { + png_byte buf[3]; + + png_crc_read(png_ptr, buf, 3); + pal_ptr->red = buf[0]; + pal_ptr->green = buf[1]; + pal_ptr->blue = buf[2]; + } +#else + for (i = 0; i < num; i++) + { + png_byte buf[3]; + + png_crc_read(png_ptr, buf, 3); + /* Don't depend upon png_color being any order */ + palette[i].red = buf[0]; + palette[i].green = buf[1]; + palette[i].blue = buf[2]; + } +#endif + + /* If we actually need the PLTE chunk (ie for a paletted image), we do + * whatever the normal CRC configuration tells us. However, if we + * have an RGB image, the PLTE can be considered ancillary, so + * we will act as though it is. + */ +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) +#endif + { + png_crc_finish(png_ptr, 0); + } + +#ifndef PNG_READ_OPT_PLTE_SUPPORTED + else if (png_crc_error(png_ptr)) /* Only if we have a CRC error */ + { + /* If we don't want to use the data from an ancillary chunk, + * we have two options: an error abort, or a warning and we + * ignore the data in this chunk (which should be OK, since + * it's considered ancillary for a RGB or RGBA image). + * + * IMPLEMENTATION NOTE: this is only here because png_crc_finish uses the + * chunk type to determine whether to check the ancillary or the critical + * flags. + */ + if (!(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_USE)) + { + if (png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN) + { + png_chunk_benign_error(png_ptr, "CRC error"); + } + + else + { + png_chunk_warning(png_ptr, "CRC error"); + return; + } + } + + /* Otherwise, we (optionally) emit a warning and use the chunk. */ + else if (!(png_ptr->flags & PNG_FLAG_CRC_ANCILLARY_NOWARN)) + { + png_chunk_warning(png_ptr, "CRC error"); + } + } +#endif + + /* TODO: png_set_PLTE has the side effect of setting png_ptr->palette to its + * own copy of the palette. This has the side effect that when png_start_row + * is called (this happens after any call to png_read_update_info) the + * info_ptr palette gets changed. This is extremely unexpected and + * confusing. + * + * Fix this by not sharing the palette in this way. + */ + png_set_PLTE(png_ptr, info_ptr, palette, num); + + /* The three chunks, bKGD, hIST and tRNS *must* appear after PLTE and before + * IDAT. Prior to 1.6.0 this was not checked; instead the code merely + * checked the apparent validity of a tRNS chunk inserted before PLTE on a + * palette PNG. 1.6.0 attempts to rigorously follow the standard and + * therefore does a benign error if the erroneous condition is detected *and* + * cancels the tRNS if the benign error returns. The alternative is to + * amend the standard since it would be rather hypocritical of the standards + * maintainers to ignore it. + */ +#ifdef PNG_READ_tRNS_SUPPORTED + if (png_ptr->num_trans > 0 || + (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS) != 0)) + { + /* Cancel this because otherwise it would be used if the transforms + * require it. Don't cancel the 'valid' flag because this would prevent + * detection of duplicate chunks. + */ + png_ptr->num_trans = 0; + + if (info_ptr != NULL) + info_ptr->num_trans = 0; + + png_chunk_benign_error(png_ptr, "tRNS must be after"); + } +#endif + +#ifdef PNG_READ_hIST_SUPPORTED + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST) != 0) + png_chunk_benign_error(png_ptr, "hIST must be after"); +#endif + +#ifdef PNG_READ_bKGD_SUPPORTED + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD) != 0) + png_chunk_benign_error(png_ptr, "bKGD must be after"); +#endif +} + +void /* PRIVATE */ +png_handle_IEND(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_debug(1, "in png_handle_IEND"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR) || !(png_ptr->mode & PNG_HAVE_IDAT)) + png_chunk_error(png_ptr, "out of place"); + + png_ptr->mode |= (PNG_AFTER_IDAT | PNG_HAVE_IEND); + + png_crc_finish(png_ptr, length); + + if (length != 0) + png_chunk_benign_error(png_ptr, "invalid"); + + PNG_UNUSED(info_ptr) +} + +#ifdef PNG_READ_gAMA_SUPPORTED +void /* PRIVATE */ +png_handle_gAMA(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_fixed_point igamma; + png_byte buf[4]; + + png_debug(1, "in png_handle_gAMA"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 4) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 4); + + if (png_crc_finish(png_ptr, 0)) + return; + + igamma = png_get_fixed_point(NULL, buf); + + png_colorspace_set_gamma(png_ptr, &png_ptr->colorspace, igamma); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif + +#ifdef PNG_READ_sBIT_SUPPORTED +void /* PRIVATE */ +png_handle_sBIT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int truelen; + png_byte buf[4]; + + png_debug(1, "in png_handle_sBIT"); + + buf[0] = buf[1] = buf[2] = buf[3] = 0; + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sBIT)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + truelen = 3; + + else + truelen = png_ptr->channels; + + if (length != truelen || length > 4) + { + png_chunk_benign_error(png_ptr, "invalid"); + png_crc_finish(png_ptr, length); + return; + } + + png_crc_read(png_ptr, buf, truelen); + + if (png_crc_finish(png_ptr, 0)) + return; + + if (png_ptr->color_type & PNG_COLOR_MASK_COLOR) + { + png_ptr->sig_bit.red = buf[0]; + png_ptr->sig_bit.green = buf[1]; + png_ptr->sig_bit.blue = buf[2]; + png_ptr->sig_bit.alpha = buf[3]; + } + + else + { + png_ptr->sig_bit.gray = buf[0]; + png_ptr->sig_bit.red = buf[0]; + png_ptr->sig_bit.green = buf[0]; + png_ptr->sig_bit.blue = buf[0]; + png_ptr->sig_bit.alpha = buf[1]; + } + + png_set_sBIT(png_ptr, info_ptr, &(png_ptr->sig_bit)); +} +#endif + +#ifdef PNG_READ_cHRM_SUPPORTED +void /* PRIVATE */ +png_handle_cHRM(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[32]; + png_xy xy; + + png_debug(1, "in png_handle_cHRM"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 32) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 32); + + if (png_crc_finish(png_ptr, 0)) + return; + + xy.whitex = png_get_fixed_point(NULL, buf); + xy.whitey = png_get_fixed_point(NULL, buf + 4); + xy.redx = png_get_fixed_point(NULL, buf + 8); + xy.redy = png_get_fixed_point(NULL, buf + 12); + xy.greenx = png_get_fixed_point(NULL, buf + 16); + xy.greeny = png_get_fixed_point(NULL, buf + 20); + xy.bluex = png_get_fixed_point(NULL, buf + 24); + xy.bluey = png_get_fixed_point(NULL, buf + 28); + + if (xy.whitex == PNG_FIXED_ERROR || + xy.whitey == PNG_FIXED_ERROR || + xy.redx == PNG_FIXED_ERROR || + xy.redy == PNG_FIXED_ERROR || + xy.greenx == PNG_FIXED_ERROR || + xy.greeny == PNG_FIXED_ERROR || + xy.bluex == PNG_FIXED_ERROR || + xy.bluey == PNG_FIXED_ERROR) + { + png_chunk_benign_error(png_ptr, "invalid values"); + return; + } + + /* If a colorspace error has already been output skip this chunk */ + if (png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) + return; + + if (png_ptr->colorspace.flags & PNG_COLORSPACE_FROM_cHRM) + { + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + png_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + (void)png_colorspace_set_chromaticities(png_ptr, &png_ptr->colorspace, &xy, + 1/*prefer cHRM values*/); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif + +#ifdef PNG_READ_sRGB_SUPPORTED +void /* PRIVATE */ +png_handle_sRGB(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte intent; + + png_debug(1, "in png_handle_sRGB"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length != 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, &intent, 1); + + if (png_crc_finish(png_ptr, 0)) + return; + + /* If a colorspace error has already been output skip this chunk */ + if (png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) + return; + + /* Only one sRGB or iCCP chunk is allowed, use the HAVE_INTENT flag to detect + * this. + */ + if (png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_INTENT) + { + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + png_chunk_benign_error(png_ptr, "too many profiles"); + return; + } + + (void)png_colorspace_set_sRGB(png_ptr, &png_ptr->colorspace, intent); + png_colorspace_sync(png_ptr, info_ptr); +} +#endif /* PNG_READ_sRGB_SUPPORTED */ + +#ifdef PNG_READ_iCCP_SUPPORTED +void /* PRIVATE */ +png_handle_iCCP(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +/* Note: this does not properly handle profiles that are > 64K under DOS */ +{ + png_const_charp errmsg = NULL; /* error message output, or no error */ + int finished = 0; /* crc checked */ + + png_debug(1, "in png_handle_iCCP"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & (PNG_HAVE_IDAT|PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + /* Consistent with all the above colorspace handling an obviously *invalid* + * chunk is just ignored, so does not invalidate the color space. An + * alternative is to set the 'invalid' flags at the start of this routine + * and only clear them in they were not set before and all the tests pass. + * The minimum 'deflate' stream is assumed to be just the 2 byte header and 4 + * byte checksum. The keyword must be one character and there is a + * terminator (0) byte and the compression method. + */ + if (length < 9) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too short"); + return; + } + + /* If a colorspace error has already been output skip this chunk */ + if (png_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) + { + png_crc_finish(png_ptr, length); + return; + } + + /* Only one sRGB or iCCP chunk is allowed, use the HAVE_INTENT flag to detect + * this. + */ + if ((png_ptr->colorspace.flags & PNG_COLORSPACE_HAVE_INTENT) == 0) + { + uInt read_length, keyword_length; + char keyword[81]; + + /* Find the keyword; the keyword plus separator and compression method + * bytes can be at most 81 characters long. + */ + read_length = 81; /* maximum */ + if (read_length > length) + read_length = (uInt)length; + + png_crc_read(png_ptr, (png_bytep)keyword, read_length); + length -= read_length; + + keyword_length = 0; + while (keyword_length < 80 && keyword_length < read_length && + keyword[keyword_length] != 0) + ++keyword_length; + + /* TODO: make the keyword checking common */ + if (keyword_length >= 1 && keyword_length <= 79) + { + /* We only understand '0' compression - deflate - so if we get a + * different value we can't safely decode the chunk. + */ + if (keyword_length+1 < read_length && + keyword[keyword_length+1] == PNG_COMPRESSION_TYPE_BASE) + { + read_length -= keyword_length+2; + + if (png_inflate_claim(png_ptr, png_iCCP, + png_ptr->flags & PNG_FLAG_BENIGN_ERRORS_WARN ? 15 : 0) == Z_OK) + { + unsigned char profile_header[132]; + unsigned char local_buffer[PNG_INFLATE_BUF_SIZE]; + png_alloc_size_t size = (sizeof profile_header); + + png_ptr->zstream.next_in = (Bytef*)keyword + (keyword_length+2); + png_ptr->zstream.avail_in = read_length; + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, profile_header, &size, + 0/*finish: don't, because the output is too small*/); + + if (size == 0) + { + /* We have the ICC profile header; do the basic header checks. + */ + const png_uint_32 profile_length = + png_get_uint_32(profile_header); + + if (png_icc_check_length(png_ptr, &png_ptr->colorspace, + keyword, profile_length)) + { + /* The length is apparently ok, so we can check the 132 + * byte header. + */ + if (png_icc_check_header(png_ptr, &png_ptr->colorspace, + keyword, profile_length, profile_header, + png_ptr->color_type)) + { + /* Now read the tag table; a variable size buffer is + * needed at this point, allocate one for the whole + * profile. The header check has already validated + * that none of these stuff will overflow. + */ + const png_uint_32 tag_count = png_get_uint_32( + profile_header+128); + png_bytep profile = png_read_buffer(png_ptr, + profile_length, 2/*silent*/); + + if (profile != NULL) + { + memcpy(profile, profile_header, + (sizeof profile_header)); + + size = 12 * tag_count; + + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, + profile + (sizeof profile_header), &size, 0); + + /* Still expect a a buffer error because we expect + * there to be some tag data! + */ + if (size == 0) + { + if (png_icc_check_tag_table(png_ptr, + &png_ptr->colorspace, keyword, profile_length, + profile)) + { + /* The profile has been validated for basic + * security issues, so read the whole thing in. + */ + size = profile_length - (sizeof profile_header) + - 12 * tag_count; + + (void)png_inflate_read(png_ptr, local_buffer, + (sizeof local_buffer), &length, + profile + (sizeof profile_header) + + 12 * tag_count, &size, 1/*finish*/); + + if (length > 0 && !(png_ptr->flags & + PNG_FLAG_BENIGN_ERRORS_WARN)) + errmsg = "extra compressed data"; + + /* But otherwise allow extra data: */ + else if (size == 0) + { + if (length > 0) + { + /* This can be handled completely, so + * keep going. + */ + png_chunk_warning(png_ptr, + "extra compressed data"); + } + + png_crc_finish(png_ptr, length); + finished = 1; + +# ifdef PNG_sRGB_SUPPORTED + /* Check for a match against sRGB */ + png_icc_set_sRGB(png_ptr, + &png_ptr->colorspace, profile, + png_ptr->zstream.adler); +# endif + + /* Steal the profile for info_ptr. */ + if (info_ptr != NULL) + { + png_free_data(png_ptr, info_ptr, + PNG_FREE_ICCP, 0); + + info_ptr->iccp_name = png_voidcast(char*, + png_malloc_base(png_ptr, + keyword_length+1)); + if (info_ptr->iccp_name != NULL) + { + memcpy(info_ptr->iccp_name, keyword, + keyword_length+1); + info_ptr->iccp_proflen = + profile_length; + info_ptr->iccp_profile = profile; + png_ptr->read_buffer = NULL; /*steal*/ + info_ptr->free_me |= PNG_FREE_ICCP; + info_ptr->valid |= PNG_INFO_iCCP; + } + + else + { + png_ptr->colorspace.flags |= + PNG_COLORSPACE_INVALID; + errmsg = "out of memory"; + } + } + + /* else the profile remains in the read + * buffer which gets reused for subsequent + * chunks. + */ + + if (info_ptr != NULL) + png_colorspace_sync(png_ptr, info_ptr); + + if (errmsg == NULL) + { + png_ptr->zowner = 0; + return; + } + } + + else if (size > 0) + errmsg = "truncated"; + + else + errmsg = png_ptr->zstream.msg; + } + + /* else png_icc_check_tag_table output an error */ + } + + else /* profile truncated */ + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "out of memory"; + } + + /* else png_icc_check_header output an error */ + } + + /* else png_icc_check_length output an error */ + } + + else /* profile truncated */ + errmsg = png_ptr->zstream.msg; + + /* Release the stream */ + png_ptr->zowner = 0; + } + + else /* png_inflate_claim failed */ + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "bad compression method"; /* or missing */ + } + + else + errmsg = "bad keyword"; + } + + else + errmsg = "too many profiles"; + + /* Failure: the reason is in 'errmsg' */ + if (!finished) + png_crc_finish(png_ptr, length); + + png_ptr->colorspace.flags |= PNG_COLORSPACE_INVALID; + png_colorspace_sync(png_ptr, info_ptr); + if (errmsg != NULL) /* else already output */ + png_chunk_benign_error(png_ptr, errmsg); +} +#endif /* PNG_READ_iCCP_SUPPORTED */ + +#ifdef PNG_READ_sPLT_SUPPORTED +void /* PRIVATE */ +png_handle_sPLT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +/* Note: this does not properly handle chunks that are > 64K under DOS */ +{ + png_bytep entry_start, buffer; + png_sPLT_t new_palette; + png_sPLT_entryp pp; + png_uint_32 data_length; + int entry_size, i; + png_uint_32 skip = 0; + png_uint_32 dl; + png_size_t max_dl; + + png_debug(1, "in png_handle_sPLT"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_warning(png_ptr, "No space in chunk cache for sPLT"); + png_crc_finish(png_ptr, length); + return; + } + } +#endif + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + +#ifdef PNG_MAX_MALLOC_64K + if (length > 65535U) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too large to fit in memory"); + return; + } +#endif + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + + /* WARNING: this may break if size_t is less than 32 bits; it is assumed + * that the PNG_MAX_MALLOC_64K test is enabled in this case, but this is a + * potential breakage point if the types in pngconf.h aren't exactly right. + */ + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, skip)) + return; + + buffer[length] = 0; + + for (entry_start = buffer; *entry_start; entry_start++) + /* Empty loop to find end of name */ ; + + ++entry_start; + + /* A sample depth should follow the separator, and we should be on it */ + if (entry_start > buffer + length - 2) + { + png_warning(png_ptr, "malformed sPLT chunk"); + return; + } + + new_palette.depth = *entry_start++; + entry_size = (new_palette.depth == 8 ? 6 : 10); + /* This must fit in a png_uint_32 because it is derived from the original + * chunk data length. + */ + data_length = length - (png_uint_32)(entry_start - buffer); + + /* Integrity-check the data length */ + if (data_length % entry_size) + { + png_warning(png_ptr, "sPLT chunk has bad length"); + return; + } + + dl = (png_int_32)(data_length / entry_size); + max_dl = PNG_SIZE_MAX / (sizeof (png_sPLT_entry)); + + if (dl > max_dl) + { + png_warning(png_ptr, "sPLT chunk too long"); + return; + } + + new_palette.nentries = (png_int_32)(data_length / entry_size); + + new_palette.entries = (png_sPLT_entryp)png_malloc_warn( + png_ptr, new_palette.nentries * (sizeof (png_sPLT_entry))); + + if (new_palette.entries == NULL) + { + png_warning(png_ptr, "sPLT chunk requires too much memory"); + return; + } + +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (i = 0; i < new_palette.nentries; i++) + { + pp = new_palette.entries + i; + + if (new_palette.depth == 8) + { + pp->red = *entry_start++; + pp->green = *entry_start++; + pp->blue = *entry_start++; + pp->alpha = *entry_start++; + } + + else + { + pp->red = png_get_uint_16(entry_start); entry_start += 2; + pp->green = png_get_uint_16(entry_start); entry_start += 2; + pp->blue = png_get_uint_16(entry_start); entry_start += 2; + pp->alpha = png_get_uint_16(entry_start); entry_start += 2; + } + + pp->frequency = png_get_uint_16(entry_start); entry_start += 2; + } +#else + pp = new_palette.entries; + + for (i = 0; i < new_palette.nentries; i++) + { + + if (new_palette.depth == 8) + { + pp[i].red = *entry_start++; + pp[i].green = *entry_start++; + pp[i].blue = *entry_start++; + pp[i].alpha = *entry_start++; + } + + else + { + pp[i].red = png_get_uint_16(entry_start); entry_start += 2; + pp[i].green = png_get_uint_16(entry_start); entry_start += 2; + pp[i].blue = png_get_uint_16(entry_start); entry_start += 2; + pp[i].alpha = png_get_uint_16(entry_start); entry_start += 2; + } + + pp[i].frequency = png_get_uint_16(entry_start); entry_start += 2; + } +#endif + + /* Discard all chunk data except the name and stash that */ + new_palette.name = (png_charp)buffer; + + png_set_sPLT(png_ptr, info_ptr, &new_palette, 1); + + png_free(png_ptr, new_palette.entries); +} +#endif /* PNG_READ_sPLT_SUPPORTED */ + +#ifdef PNG_READ_tRNS_SUPPORTED +void /* PRIVATE */ +png_handle_tRNS(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte readbuf[PNG_MAX_PALETTE_LENGTH]; + + png_debug(1, "in png_handle_tRNS"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + png_byte buf[2]; + + if (length != 2) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 2); + png_ptr->num_trans = 1; + png_ptr->trans_color.gray = png_get_uint_16(buf); + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB) + { + png_byte buf[6]; + + if (length != 6) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, length); + png_ptr->num_trans = 1; + png_ptr->trans_color.red = png_get_uint_16(buf); + png_ptr->trans_color.green = png_get_uint_16(buf + 2); + png_ptr->trans_color.blue = png_get_uint_16(buf + 4); + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (!(png_ptr->mode & PNG_HAVE_PLTE)) + { + /* TODO: is this actually an error in the ISO spec? */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + if (length > png_ptr->num_palette || length > PNG_MAX_PALETTE_LENGTH || + length == 0) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, readbuf, length); + png_ptr->num_trans = (png_uint_16)length; + } + + else + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid with alpha channel"); + return; + } + + if (png_crc_finish(png_ptr, 0)) + { + png_ptr->num_trans = 0; + return; + } + + /* TODO: this is a horrible side effect in the palette case because the + * png_struct ends up with a pointer to the tRNS buffer owned by the + * png_info. Fix this. + */ + png_set_tRNS(png_ptr, info_ptr, readbuf, png_ptr->num_trans, + &(png_ptr->trans_color)); +} +#endif + +#ifdef PNG_READ_bKGD_SUPPORTED +void /* PRIVATE */ +png_handle_bKGD(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int truelen; + png_byte buf[6]; + png_color_16 background; + + png_debug(1, "in png_handle_bKGD"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) || + (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + !(png_ptr->mode & PNG_HAVE_PLTE))) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_bKGD)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + truelen = 1; + + else if (png_ptr->color_type & PNG_COLOR_MASK_COLOR) + truelen = 6; + + else + truelen = 2; + + if (length != truelen) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, truelen); + + if (png_crc_finish(png_ptr, 0)) + return; + + /* We convert the index value into RGB components so that we can allow + * arbitrary RGB values for background when we have transparency, and + * so it is easy to determine the RGB values of the background color + * from the info_ptr struct. + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + background.index = buf[0]; + + if (info_ptr && info_ptr->num_palette) + { + if (buf[0] >= info_ptr->num_palette) + { + png_chunk_benign_error(png_ptr, "invalid index"); + return; + } + + background.red = (png_uint_16)png_ptr->palette[buf[0]].red; + background.green = (png_uint_16)png_ptr->palette[buf[0]].green; + background.blue = (png_uint_16)png_ptr->palette[buf[0]].blue; + } + + else + background.red = background.green = background.blue = 0; + + background.gray = 0; + } + + else if (!(png_ptr->color_type & PNG_COLOR_MASK_COLOR)) /* GRAY */ + { + background.index = 0; + background.red = + background.green = + background.blue = + background.gray = png_get_uint_16(buf); + } + + else + { + background.index = 0; + background.red = png_get_uint_16(buf); + background.green = png_get_uint_16(buf + 2); + background.blue = png_get_uint_16(buf + 4); + background.gray = 0; + } + + png_set_bKGD(png_ptr, info_ptr, &background); +} +#endif + +#ifdef PNG_READ_hIST_SUPPORTED +void /* PRIVATE */ +png_handle_hIST(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + unsigned int num, i; + png_uint_16 readbuf[PNG_MAX_PALETTE_LENGTH]; + + png_debug(1, "in png_handle_hIST"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if ((png_ptr->mode & PNG_HAVE_IDAT) || !(png_ptr->mode & PNG_HAVE_PLTE)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_hIST)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + num = length / 2 ; + + if (num != png_ptr->num_palette || num > PNG_MAX_PALETTE_LENGTH) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + for (i = 0; i < num; i++) + { + png_byte buf[2]; + + png_crc_read(png_ptr, buf, 2); + readbuf[i] = png_get_uint_16(buf); + } + + if (png_crc_finish(png_ptr, 0)) + return; + + png_set_hIST(png_ptr, info_ptr, readbuf); +} +#endif + +#ifdef PNG_READ_pHYs_SUPPORTED +void /* PRIVATE */ +png_handle_pHYs(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[9]; + png_uint_32 res_x, res_y; + int unit_type; + + png_debug(1, "in png_handle_pHYs"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pHYs)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (length != 9) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 9); + + if (png_crc_finish(png_ptr, 0)) + return; + + res_x = png_get_uint_32(buf); + res_y = png_get_uint_32(buf + 4); + unit_type = buf[8]; + png_set_pHYs(png_ptr, info_ptr, res_x, res_y, unit_type); +} +#endif + +#ifdef PNG_READ_oFFs_SUPPORTED +void /* PRIVATE */ +png_handle_oFFs(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[9]; + png_int_32 offset_x, offset_y; + int unit_type; + + png_debug(1, "in png_handle_oFFs"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_oFFs)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (length != 9) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 9); + + if (png_crc_finish(png_ptr, 0)) + return; + + offset_x = png_get_int_32(buf); + offset_y = png_get_int_32(buf + 4); + unit_type = buf[8]; + png_set_oFFs(png_ptr, info_ptr, offset_x, offset_y, unit_type); +} +#endif + +#ifdef PNG_READ_pCAL_SUPPORTED +/* Read the pCAL chunk (described in the PNG Extensions document) */ +void /* PRIVATE */ +png_handle_pCAL(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_int_32 X0, X1; + png_byte type, nparams; + png_bytep buffer, buf, units, endptr; + png_charpp params; + int i; + + png_debug(1, "in png_handle_pCAL"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_pCAL)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + png_debug1(2, "Allocating and reading pCAL chunk data (%u bytes)", + length + 1); + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0)) + return; + + buffer[length] = 0; /* Null terminate the last string */ + + png_debug(3, "Finding end of pCAL purpose string"); + for (buf = buffer; *buf; buf++) + /* Empty loop */ ; + + endptr = buffer + length; + + /* We need to have at least 12 bytes after the purpose string + * in order to get the parameter information. + */ + if (endptr <= buf + 12) + { + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_debug(3, "Reading pCAL X0, X1, type, nparams, and units"); + X0 = png_get_int_32((png_bytep)buf+1); + X1 = png_get_int_32((png_bytep)buf+5); + type = buf[9]; + nparams = buf[10]; + units = buf + 11; + + png_debug(3, "Checking pCAL equation type and number of parameters"); + /* Check that we have the right number of parameters for known + * equation types. + */ + if ((type == PNG_EQUATION_LINEAR && nparams != 2) || + (type == PNG_EQUATION_BASE_E && nparams != 3) || + (type == PNG_EQUATION_ARBITRARY && nparams != 3) || + (type == PNG_EQUATION_HYPERBOLIC && nparams != 4)) + { + png_chunk_benign_error(png_ptr, "invalid parameter count"); + return; + } + + else if (type >= PNG_EQUATION_LAST) + { + png_chunk_benign_error(png_ptr, "unrecognized equation type"); + } + + for (buf = units; *buf; buf++) + /* Empty loop to move past the units string. */ ; + + png_debug(3, "Allocating pCAL parameters array"); + + params = png_voidcast(png_charpp, png_malloc_warn(png_ptr, + nparams * (sizeof (png_charp)))); + + if (params == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + /* Get pointers to the start of each parameter string. */ + for (i = 0; i < nparams; i++) + { + buf++; /* Skip the null string terminator from previous parameter. */ + + png_debug1(3, "Reading pCAL parameter %d", i); + + for (params[i] = (png_charp)buf; buf <= endptr && *buf != 0; buf++) + /* Empty loop to move past each parameter string */ ; + + /* Make sure we haven't run out of data yet */ + if (buf > endptr) + { + png_free(png_ptr, params); + png_chunk_benign_error(png_ptr, "invalid data"); + return; + } + } + + png_set_pCAL(png_ptr, info_ptr, (png_charp)buffer, X0, X1, type, nparams, + (png_charp)units, params); + + png_free(png_ptr, params); +} +#endif + +#ifdef PNG_READ_sCAL_SUPPORTED +/* Read the sCAL chunk */ +void /* PRIVATE */ +png_handle_sCAL(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_bytep buffer; + png_size_t i; + int state; + + png_debug(1, "in png_handle_sCAL"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of place"); + return; + } + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_sCAL)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + /* Need unit type, width, \0, height: minimum 4 bytes */ + else if (length < 4) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_debug1(2, "Allocating and reading sCAL chunk data (%u bytes)", + length + 1); + + buffer = png_read_buffer(png_ptr, length+1, 2/*silent*/); + + if (buffer == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + png_crc_finish(png_ptr, length); + return; + } + + png_crc_read(png_ptr, buffer, length); + buffer[length] = 0; /* Null terminate the last string */ + + if (png_crc_finish(png_ptr, 0)) + return; + + /* Validate the unit. */ + if (buffer[0] != 1 && buffer[0] != 2) + { + png_chunk_benign_error(png_ptr, "invalid unit"); + return; + } + + /* Validate the ASCII numbers, need two ASCII numbers separated by + * a '\0' and they need to fit exactly in the chunk data. + */ + i = 1; + state = 0; + + if (!png_check_fp_number((png_const_charp)buffer, length, &state, &i) || + i >= length || buffer[i++] != 0) + png_chunk_benign_error(png_ptr, "bad width format"); + + else if (!PNG_FP_IS_POSITIVE(state)) + png_chunk_benign_error(png_ptr, "non-positive width"); + + else + { + png_size_t heighti = i; + + state = 0; + if (!png_check_fp_number((png_const_charp)buffer, length, &state, &i) || + i != length) + png_chunk_benign_error(png_ptr, "bad height format"); + + else if (!PNG_FP_IS_POSITIVE(state)) + png_chunk_benign_error(png_ptr, "non-positive height"); + + else + /* This is the (only) success case. */ + png_set_sCAL_s(png_ptr, info_ptr, buffer[0], + (png_charp)buffer+1, (png_charp)buffer+heighti); + } +} +#endif + +#ifdef PNG_READ_tIME_SUPPORTED +void /* PRIVATE */ +png_handle_tIME(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_byte buf[7]; + png_time mod_time; + + png_debug(1, "in png_handle_tIME"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tIME)) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "duplicate"); + return; + } + + if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + + if (length != 7) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + + png_crc_read(png_ptr, buf, 7); + + if (png_crc_finish(png_ptr, 0)) + return; + + mod_time.second = buf[6]; + mod_time.minute = buf[5]; + mod_time.hour = buf[4]; + mod_time.day = buf[3]; + mod_time.month = buf[2]; + mod_time.year = png_get_uint_16(buf); + + png_set_tIME(png_ptr, info_ptr, &mod_time); +} +#endif + +#ifdef PNG_READ_tEXt_SUPPORTED +/* Note: this does not properly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_tEXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_text text_info; + png_bytep buffer; + png_charp key; + png_charp text; + png_uint_32 skip = 0; + + png_debug(1, "in png_handle_tEXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + +#ifdef PNG_MAX_MALLOC_64K + if (length > 65535U) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "too large to fit in memory"); + return; + } +#endif + + buffer = png_read_buffer(png_ptr, length+1, 1/*warn*/); + + if (buffer == NULL) + { + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, skip)) + return; + + key = (png_charp)buffer; + key[length] = 0; + + for (text = key; *text; text++) + /* Empty loop to find end of key */ ; + + if (text != key + length) + text++; + + text_info.compression = PNG_TEXT_COMPRESSION_NONE; + text_info.key = key; + text_info.lang = NULL; + text_info.lang_key = NULL; + text_info.itxt_length = 0; + text_info.text = text; + text_info.text_length = strlen(text); + + if (png_set_text_2(png_ptr, info_ptr, &text_info, 1)) + png_warning(png_ptr, "Insufficient memory to process text chunk"); +} +#endif + +#ifdef PNG_READ_zTXt_SUPPORTED +/* Note: this does not correctly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_zTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_const_charp errmsg = NULL; + png_bytep buffer; + png_uint_32 keyword_length; + + png_debug(1, "in png_handle_zTXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + + buffer = png_read_buffer(png_ptr, length, 2/*silent*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0)) + return; + + /* TODO: also check that the keyword contents match the spec! */ + for (keyword_length = 0; + keyword_length < length && buffer[keyword_length] != 0; + ++keyword_length) + /* Empty loop to find end of name */ ; + + if (keyword_length > 79 || keyword_length < 1) + errmsg = "bad keyword"; + + /* zTXt must have some LZ data after the keyword, although it may expand to + * zero bytes; we need a '\0' at the end of the keyword, the compression type + * then the LZ data: + */ + else if (keyword_length + 3 > length) + errmsg = "truncated"; + + else if (buffer[keyword_length+1] != PNG_COMPRESSION_TYPE_BASE) + errmsg = "unknown compression type"; + + else + { + png_alloc_size_t uncompressed_length = PNG_SIZE_MAX; + + /* TODO: at present png_decompress_chunk imposes a single application + * level memory limit, this should be split to different values for iCCP + * and text chunks. + */ + if (png_decompress_chunk(png_ptr, length, keyword_length+2, + &uncompressed_length, 1/*terminate*/) == Z_STREAM_END) + { + png_text text; + + /* It worked; png_ptr->read_buffer now looks like a tEXt chunk except + * for the extra compression type byte and the fact that it isn't + * necessarily '\0' terminated. + */ + buffer = png_ptr->read_buffer; + buffer[uncompressed_length+(keyword_length+2)] = 0; + + text.compression = PNG_TEXT_COMPRESSION_zTXt; + text.key = (png_charp)buffer; + text.text = (png_charp)(buffer + keyword_length+2); + text.text_length = uncompressed_length; + text.itxt_length = 0; + text.lang = NULL; + text.lang_key = NULL; + + if (png_set_text_2(png_ptr, info_ptr, &text, 1)) + errmsg = "insufficient memory"; + } + + else + errmsg = png_ptr->zstream.msg; + } + + if (errmsg != NULL) + png_chunk_benign_error(png_ptr, errmsg); +} +#endif + +#ifdef PNG_READ_iTXt_SUPPORTED +/* Note: this does not correctly handle chunks that are > 64K under DOS */ +void /* PRIVATE */ +png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) +{ + png_const_charp errmsg = NULL; + png_bytep buffer; + png_uint_32 prefix_length; + + png_debug(1, "in png_handle_iTXt"); + +#ifdef PNG_USER_LIMITS_SUPPORTED + if (png_ptr->user_chunk_cache_max != 0) + { + if (png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + return; + } + + if (--png_ptr->user_chunk_cache_max == 1) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + return; + } + } +#endif + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + png_chunk_error(png_ptr, "missing IHDR"); + + if (png_ptr->mode & PNG_HAVE_IDAT) + png_ptr->mode |= PNG_AFTER_IDAT; + + buffer = png_read_buffer(png_ptr, length+1, 1/*warn*/); + + if (buffer == NULL) + { + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "out of memory"); + return; + } + + png_crc_read(png_ptr, buffer, length); + + if (png_crc_finish(png_ptr, 0)) + return; + + /* First the keyword. */ + for (prefix_length=0; + prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* Perform a basic check on the keyword length here. */ + if (prefix_length > 79 || prefix_length < 1) + errmsg = "bad keyword"; + + /* Expect keyword, compression flag, compression type, language, translated + * keyword (both may be empty but are 0 terminated) then the text, which may + * be empty. + */ + else if (prefix_length + 5 > length) + errmsg = "truncated"; + + else if (buffer[prefix_length+1] == 0 || + (buffer[prefix_length+1] == 1 && + buffer[prefix_length+2] == PNG_COMPRESSION_TYPE_BASE)) + { + int compressed = buffer[prefix_length+1] != 0; + png_uint_32 language_offset, translated_keyword_offset; + png_alloc_size_t uncompressed_length = 0; + + /* Now the language tag */ + prefix_length += 3; + language_offset = prefix_length; + + for (; prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* WARNING: the length may be invalid here, this is checked below. */ + translated_keyword_offset = ++prefix_length; + + for (; prefix_length < length && buffer[prefix_length] != 0; + ++prefix_length) + /* Empty loop */ ; + + /* prefix_length should now be at the trailing '\0' of the translated + * keyword, but it may already be over the end. None of this arithmetic + * can overflow because chunks are at most 2^31 bytes long, but on 16-bit + * systems the available allocaton may overflow. + */ + ++prefix_length; + + if (!compressed && prefix_length <= length) + uncompressed_length = length - prefix_length; + + else if (compressed && prefix_length < length) + { + uncompressed_length = PNG_SIZE_MAX; + + /* TODO: at present png_decompress_chunk imposes a single application + * level memory limit, this should be split to different values for + * iCCP and text chunks. + */ + if (png_decompress_chunk(png_ptr, length, prefix_length, + &uncompressed_length, 1/*terminate*/) == Z_STREAM_END) + buffer = png_ptr->read_buffer; + + else + errmsg = png_ptr->zstream.msg; + } + + else + errmsg = "truncated"; + + if (errmsg == NULL) + { + png_text text; + + buffer[uncompressed_length+prefix_length] = 0; + + if (compressed) + text.compression = PNG_ITXT_COMPRESSION_NONE; + + else + text.compression = PNG_ITXT_COMPRESSION_zTXt; + + text.key = (png_charp)buffer; + text.lang = (png_charp)buffer + language_offset; + text.lang_key = (png_charp)buffer + translated_keyword_offset; + text.text = (png_charp)buffer + prefix_length; + text.text_length = 0; + text.itxt_length = uncompressed_length; + + if (png_set_text_2(png_ptr, info_ptr, &text, 1)) + errmsg = "insufficient memory"; + } + } + + else + errmsg = "bad compression info"; + + if (errmsg != NULL) + png_chunk_benign_error(png_ptr, errmsg); +} +#endif + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +/* Utility function for png_handle_unknown; set up png_ptr::unknown_chunk */ +static int +png_cache_unknown_chunk(png_structrp png_ptr, png_uint_32 length) +{ + png_alloc_size_t limit = PNG_SIZE_MAX; + + if (png_ptr->unknown_chunk.data != NULL) + { + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + } + +# ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED + if (png_ptr->user_chunk_malloc_max > 0 && + png_ptr->user_chunk_malloc_max < limit) + limit = png_ptr->user_chunk_malloc_max; + +# elif PNG_USER_CHUNK_MALLOC_MAX > 0 + if (PNG_USER_CHUNK_MALLOC_MAX < limit) + limit = PNG_USER_CHUNK_MALLOC_MAX; +# endif + + if (length <= limit) + { + PNG_CSTRING_FROM_CHUNK(png_ptr->unknown_chunk.name, png_ptr->chunk_name); + /* The following is safe because of the PNG_SIZE_MAX init above */ + png_ptr->unknown_chunk.size = (png_size_t)length/*SAFE*/; + /* 'mode' is a flag array, only the bottom four bits matter here */ + png_ptr->unknown_chunk.location = (png_byte)png_ptr->mode/*SAFE*/; + + if (length == 0) + png_ptr->unknown_chunk.data = NULL; + + else + { + /* Do a 'warn' here - it is handled below. */ + png_ptr->unknown_chunk.data = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, length)); + } + } + + if (png_ptr->unknown_chunk.data == NULL && length > 0) + { + /* This is benign because we clean up correctly */ + png_crc_finish(png_ptr, length); + png_chunk_benign_error(png_ptr, "unknown chunk exceeds memory limits"); + return 0; + } + + else + { + if (length > 0) + png_crc_read(png_ptr, png_ptr->unknown_chunk.data, length); + png_crc_finish(png_ptr, 0); + return 1; + } +} +#endif /* PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + +/* Handle an unknown, or known but disabled, chunk */ +void /* PRIVATE */ +png_handle_unknown(png_structrp png_ptr, png_inforp info_ptr, + png_uint_32 length, int keep) +{ + int handled = 0; /* the chunk was handled */ + + png_debug(1, "in png_handle_unknown"); + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* NOTE: this code is based on the code in libpng-1.4.12 except for fixing + * the bug which meant that setting a non-default behavior for a specific + * chunk would be ignored (the default was always used unless a user + * callback was installed). + * + * 'keep' is the value from the png_chunk_unknown_handling, the setting for + * this specific chunk_name, if PNG_HANDLE_AS_UNKNOWN_SUPPORTED, if not it + * will always be PNG_HANDLE_CHUNK_AS_DEFAULT and it needs to be set here. + * This is just an optimization to avoid multiple calls to the lookup + * function. + */ +# ifndef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +# ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + keep = png_chunk_unknown_handling(png_ptr, png_ptr->chunk_name); +# endif +# endif + + /* One of the following methods will read the chunk or skip it (at least one + * of these is always defined because this is the only way to switch on + * PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) + */ +# ifdef PNG_READ_USER_CHUNKS_SUPPORTED + /* The user callback takes precedence over the chunk keep value, but the + * keep value is still required to validate a save of a critical chunk. + */ + if (png_ptr->read_user_chunk_fn != NULL) + { + if (png_cache_unknown_chunk(png_ptr, length)) + { + /* Callback to user unknown chunk handler */ + int ret = (*(png_ptr->read_user_chunk_fn))(png_ptr, + &png_ptr->unknown_chunk); + + /* ret is: + * negative: An error occured, png_chunk_error will be called. + * zero: The chunk was not handled, the chunk will be discarded + * unless png_set_keep_unknown_chunks has been used to set + * a 'keep' behavior for this particular chunk, in which + * case that will be used. A critical chunk will cause an + * error at this point unless it is to be saved. + * positive: The chunk was handled, libpng will ignore/discard it. + */ + if (ret < 0) + png_chunk_error(png_ptr, "error in user chunk"); + + else if (ret == 0) + { + /* If the keep value is 'default' or 'never' override it, but + * still error out on critical chunks unless the keep value is + * 'always' While this is weird it is the behavior in 1.4.12. + * A possible improvement would be to obey the value set for the + * chunk, but this would be an API change that would probably + * damage some applications. + * + * The png_app_warning below catches the case that matters, where + * the application has not set specific save or ignore for this + * chunk or global save or ignore. + */ + if (keep < PNG_HANDLE_CHUNK_IF_SAFE) + { +# ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + if (png_ptr->unknown_default < PNG_HANDLE_CHUNK_IF_SAFE) + { + png_chunk_warning(png_ptr, "Saving unknown chunk:"); + png_app_warning(png_ptr, + "forcing save of an unhandled chunk;" + " please call png_set_keep_unknown_chunks"); + /* with keep = PNG_HANDLE_CHUNK_IF_SAFE */ + } +# endif + keep = PNG_HANDLE_CHUNK_IF_SAFE; + } + } + + else /* chunk was handled */ + { + handled = 1; + /* Critical chunks can be safely discarded at this point. */ + keep = PNG_HANDLE_CHUNK_NEVER; + } + } + + else + keep = PNG_HANDLE_CHUNK_NEVER; /* insufficient memory */ + } + + else + /* Use the SAVE_UNKNOWN_CHUNKS code or skip the chunk */ +# endif /* PNG_READ_USER_CHUNKS_SUPPORTED */ + +# ifdef PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED + { + /* keep is currently just the per-chunk setting, if there was no + * setting change it to the global default now (not that this may + * still be AS_DEFAULT) then obtain the cache of the chunk if required, + * if not simply skip the chunk. + */ + if (keep == PNG_HANDLE_CHUNK_AS_DEFAULT) + keep = png_ptr->unknown_default; + + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name))) + { + if (!png_cache_unknown_chunk(png_ptr, length)) + keep = PNG_HANDLE_CHUNK_NEVER; + } + + else + png_crc_finish(png_ptr, length); + } +# else +# ifndef PNG_READ_USER_CHUNKS_SUPPORTED +# error no method to support READ_UNKNOWN_CHUNKS +# endif + + { + /* If here there is no read callback pointer set and no support is + * compiled in to just save the unknown chunks, so simply skip this + * chunk. If 'keep' is something other than AS_DEFAULT or NEVER then + * the app has erroneously asked for unknown chunk saving when there + * is no support. + */ + if (keep > PNG_HANDLE_CHUNK_NEVER) + png_app_error(png_ptr, "no unknown chunk support available"); + + png_crc_finish(png_ptr, length); + } +# endif + +# ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + /* Now store the chunk in the chunk list if appropriate, and if the limits + * permit it. + */ + if (keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_IF_SAFE && + PNG_CHUNK_ANCILLIARY(png_ptr->chunk_name))) + { +# ifdef PNG_USER_LIMITS_SUPPORTED + switch (png_ptr->user_chunk_cache_max) + { + case 2: + png_ptr->user_chunk_cache_max = 1; + png_chunk_benign_error(png_ptr, "no space in chunk cache"); + /* FALL THROUGH */ + case 1: + /* NOTE: prior to 1.6.0 this case resulted in an unknown critical + * chunk being skipped, now there will be a hard error below. + */ + break; + + default: /* not at limit */ + --(png_ptr->user_chunk_cache_max); + /* FALL THROUGH */ + case 0: /* no limit */ +# endif /* PNG_USER_LIMITS_SUPPORTED */ + /* Here when the limit isn't reached or when limits are compiled + * out; store the chunk. + */ + png_set_unknown_chunks(png_ptr, info_ptr, + &png_ptr->unknown_chunk, 1); + handled = 1; +# ifdef PNG_USER_LIMITS_SUPPORTED + break; + } +# endif + } +# else /* no store support! */ + PNG_UNUSED(info_ptr) +# error untested code (reading unknown chunks with no store support) +# endif + + /* Regardless of the error handling below the cached data (if any) can be + * freed now. Notice that the data is not freed if there is a png_error, but + * it will be freed by destroy_read_struct. + */ + if (png_ptr->unknown_chunk.data != NULL) + png_free(png_ptr, png_ptr->unknown_chunk.data); + png_ptr->unknown_chunk.data = NULL; + +#else /* !PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + /* There is no support to read an unknown chunk, so just skip it. */ + png_crc_finish(png_ptr, length); + PNG_UNUSED(info_ptr) + PNG_UNUSED(keep) +#endif /* !PNG_READ_UNKNOWN_CHUNKS_SUPPORTED */ + + /* Check for unhandled critical chunks */ + if (!handled && PNG_CHUNK_CRITICAL(png_ptr->chunk_name)) + png_chunk_error(png_ptr, "unhandled critical chunk"); +} + +/* This function is called to verify that a chunk name is valid. + * This function can't have the "critical chunk check" incorporated + * into it, since in the future we will need to be able to call user + * functions to handle unknown critical chunks after we check that + * the chunk name itself is valid. + */ + +/* Bit hacking: the test for an invalid byte in the 4 byte chunk name is: + * + * ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97)) + */ + +void /* PRIVATE */ +png_check_chunk_name(png_structrp png_ptr, png_uint_32 chunk_name) +{ + int i; + + png_debug(1, "in png_check_chunk_name"); + + for (i=1; i<=4; ++i) + { + int c = chunk_name & 0xff; + + if (c < 65 || c > 122 || (c > 90 && c < 97)) + png_chunk_error(png_ptr, "invalid chunk type"); + + chunk_name >>= 8; + } +} + +/* Combines the row recently read in with the existing pixels in the row. This + * routine takes care of alpha and transparency if requested. This routine also + * handles the two methods of progressive display of interlaced images, + * depending on the 'display' value; if 'display' is true then the whole row + * (dp) is filled from the start by replicating the available pixels. If + * 'display' is false only those pixels present in the pass are filled in. + */ +void /* PRIVATE */ +png_combine_row(png_const_structrp png_ptr, png_bytep dp, int display) +{ + unsigned int pixel_depth = png_ptr->transformed_pixel_depth; + png_const_bytep sp = png_ptr->row_buf + 1; + png_uint_32 row_width = png_ptr->width; + unsigned int pass = png_ptr->pass; + png_bytep end_ptr = 0; + png_byte end_byte = 0; + unsigned int end_mask; + + png_debug(1, "in png_combine_row"); + + /* Added in 1.5.6: it should not be possible to enter this routine until at + * least one row has been read from the PNG data and transformed. + */ + if (pixel_depth == 0) + png_error(png_ptr, "internal row logic error"); + + /* Added in 1.5.4: the pixel depth should match the information returned by + * any call to png_read_update_info at this point. Do not continue if we got + * this wrong. + */ + if (png_ptr->info_rowbytes != 0 && png_ptr->info_rowbytes != + PNG_ROWBYTES(pixel_depth, row_width)) + png_error(png_ptr, "internal row size calculation error"); + + /* Don't expect this to ever happen: */ + if (row_width == 0) + png_error(png_ptr, "internal row width error"); + + /* Preserve the last byte in cases where only part of it will be overwritten, + * the multiply below may overflow, we don't care because ANSI-C guarantees + * we get the low bits. + */ + end_mask = (pixel_depth * row_width) & 7; + if (end_mask != 0) + { + /* end_ptr == NULL is a flag to say do nothing */ + end_ptr = dp + PNG_ROWBYTES(pixel_depth, row_width) - 1; + end_byte = *end_ptr; +# ifdef PNG_READ_PACKSWAP_SUPPORTED + if (png_ptr->transformations & PNG_PACKSWAP) /* little-endian byte */ + end_mask = 0xff << end_mask; + + else /* big-endian byte */ +# endif + end_mask = 0xff >> end_mask; + /* end_mask is now the bits to *keep* from the destination row */ + } + + /* For non-interlaced images this reduces to a memcpy(). A memcpy() + * will also happen if interlacing isn't supported or if the application + * does not call png_set_interlace_handling(). In the latter cases the + * caller just gets a sequence of the unexpanded rows from each interlace + * pass. + */ +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE) && + pass < 6 && (display == 0 || + /* The following copies everything for 'display' on passes 0, 2 and 4. */ + (display == 1 && (pass & 1) != 0))) + { + /* Narrow images may have no bits in a pass; the caller should handle + * this, but this test is cheap: + */ + if (row_width <= PNG_PASS_START_COL(pass)) + return; + + if (pixel_depth < 8) + { + /* For pixel depths up to 4 bpp the 8-pixel mask can be expanded to fit + * into 32 bits, then a single loop over the bytes using the four byte + * values in the 32-bit mask can be used. For the 'display' option the + * expanded mask may also not require any masking within a byte. To + * make this work the PACKSWAP option must be taken into account - it + * simply requires the pixels to be reversed in each byte. + * + * The 'regular' case requires a mask for each of the first 6 passes, + * the 'display' case does a copy for the even passes in the range + * 0..6. This has already been handled in the test above. + * + * The masks are arranged as four bytes with the first byte to use in + * the lowest bits (little-endian) regardless of the order (PACKSWAP or + * not) of the pixels in each byte. + * + * NOTE: the whole of this logic depends on the caller of this function + * only calling it on rows appropriate to the pass. This function only + * understands the 'x' logic; the 'y' logic is handled by the caller. + * + * The following defines allow generation of compile time constant bit + * masks for each pixel depth and each possibility of swapped or not + * swapped bytes. Pass 'p' is in the range 0..6; 'x', a pixel index, + * is in the range 0..7; and the result is 1 if the pixel is to be + * copied in the pass, 0 if not. 'S' is for the sparkle method, 'B' + * for the block method. + * + * With some compilers a compile time expression of the general form: + * + * (shift >= 32) ? (a >> (shift-32)) : (b >> shift) + * + * Produces warnings with values of 'shift' in the range 33 to 63 + * because the right hand side of the ?: expression is evaluated by + * the compiler even though it isn't used. Microsoft Visual C (various + * versions) and the Intel C compiler are known to do this. To avoid + * this the following macros are used in 1.5.6. This is a temporary + * solution to avoid destabilizing the code during the release process. + */ +# if PNG_USE_COMPILE_TIME_MASKS +# define PNG_LSR(x,s) ((x)>>((s) & 0x1f)) +# define PNG_LSL(x,s) ((x)<<((s) & 0x1f)) +# else +# define PNG_LSR(x,s) ((x)>>(s)) +# define PNG_LSL(x,s) ((x)<<(s)) +# endif +# define S_COPY(p,x) (((p)<4 ? PNG_LSR(0x80088822,(3-(p))*8+(7-(x))) :\ + PNG_LSR(0xaa55ff00,(7-(p))*8+(7-(x)))) & 1) +# define B_COPY(p,x) (((p)<4 ? PNG_LSR(0xff0fff33,(3-(p))*8+(7-(x))) :\ + PNG_LSR(0xff55ff00,(7-(p))*8+(7-(x)))) & 1) + + /* Return a mask for pass 'p' pixel 'x' at depth 'd'. The mask is + * little endian - the first pixel is at bit 0 - however the extra + * parameter 's' can be set to cause the mask position to be swapped + * within each byte, to match the PNG format. This is done by XOR of + * the shift with 7, 6 or 4 for bit depths 1, 2 and 4. + */ +# define PIXEL_MASK(p,x,d,s) \ + (PNG_LSL(((PNG_LSL(1U,(d)))-1),(((x)*(d))^((s)?8-(d):0)))) + + /* Hence generate the appropriate 'block' or 'sparkle' pixel copy mask. + */ +# define S_MASKx(p,x,d,s) (S_COPY(p,x)?PIXEL_MASK(p,x,d,s):0) +# define B_MASKx(p,x,d,s) (B_COPY(p,x)?PIXEL_MASK(p,x,d,s):0) + + /* Combine 8 of these to get the full mask. For the 1-bpp and 2-bpp + * cases the result needs replicating, for the 4-bpp case the above + * generates a full 32 bits. + */ +# define MASK_EXPAND(m,d) ((m)*((d)==1?0x01010101:((d)==2?0x00010001:1))) + +# define S_MASK(p,d,s) MASK_EXPAND(S_MASKx(p,0,d,s) + S_MASKx(p,1,d,s) +\ + S_MASKx(p,2,d,s) + S_MASKx(p,3,d,s) + S_MASKx(p,4,d,s) +\ + S_MASKx(p,5,d,s) + S_MASKx(p,6,d,s) + S_MASKx(p,7,d,s), d) + +# define B_MASK(p,d,s) MASK_EXPAND(B_MASKx(p,0,d,s) + B_MASKx(p,1,d,s) +\ + B_MASKx(p,2,d,s) + B_MASKx(p,3,d,s) + B_MASKx(p,4,d,s) +\ + B_MASKx(p,5,d,s) + B_MASKx(p,6,d,s) + B_MASKx(p,7,d,s), d) + +#if PNG_USE_COMPILE_TIME_MASKS + /* Utility macros to construct all the masks for a depth/swap + * combination. The 's' parameter says whether the format is PNG + * (big endian bytes) or not. Only the three odd-numbered passes are + * required for the display/block algorithm. + */ +# define S_MASKS(d,s) { S_MASK(0,d,s), S_MASK(1,d,s), S_MASK(2,d,s),\ + S_MASK(3,d,s), S_MASK(4,d,s), S_MASK(5,d,s) } + +# define B_MASKS(d,s) { B_MASK(1,d,s), S_MASK(3,d,s), S_MASK(5,d,s) } + +# define DEPTH_INDEX(d) ((d)==1?0:((d)==2?1:2)) + + /* Hence the pre-compiled masks indexed by PACKSWAP (or not), depth and + * then pass: + */ + static PNG_CONST png_uint_32 row_mask[2/*PACKSWAP*/][3/*depth*/][6] = + { + /* Little-endian byte masks for PACKSWAP */ + { S_MASKS(1,0), S_MASKS(2,0), S_MASKS(4,0) }, + /* Normal (big-endian byte) masks - PNG format */ + { S_MASKS(1,1), S_MASKS(2,1), S_MASKS(4,1) } + }; + + /* display_mask has only three entries for the odd passes, so index by + * pass>>1. + */ + static PNG_CONST png_uint_32 display_mask[2][3][3] = + { + /* Little-endian byte masks for PACKSWAP */ + { B_MASKS(1,0), B_MASKS(2,0), B_MASKS(4,0) }, + /* Normal (big-endian byte) masks - PNG format */ + { B_MASKS(1,1), B_MASKS(2,1), B_MASKS(4,1) } + }; + +# define MASK(pass,depth,display,png)\ + ((display)?display_mask[png][DEPTH_INDEX(depth)][pass>>1]:\ + row_mask[png][DEPTH_INDEX(depth)][pass]) + +#else /* !PNG_USE_COMPILE_TIME_MASKS */ + /* This is the runtime alternative: it seems unlikely that this will + * ever be either smaller or faster than the compile time approach. + */ +# define MASK(pass,depth,display,png)\ + ((display)?B_MASK(pass,depth,png):S_MASK(pass,depth,png)) +#endif /* !PNG_USE_COMPILE_TIME_MASKS */ + + /* Use the appropriate mask to copy the required bits. In some cases + * the byte mask will be 0 or 0xff, optimize these cases. row_width is + * the number of pixels, but the code copies bytes, so it is necessary + * to special case the end. + */ + png_uint_32 pixels_per_byte = 8 / pixel_depth; + png_uint_32 mask; + +# ifdef PNG_READ_PACKSWAP_SUPPORTED + if (png_ptr->transformations & PNG_PACKSWAP) + mask = MASK(pass, pixel_depth, display, 0); + + else +# endif + mask = MASK(pass, pixel_depth, display, 1); + + for (;;) + { + png_uint_32 m; + + /* It doesn't matter in the following if png_uint_32 has more than + * 32 bits because the high bits always match those in m<<24; it is, + * however, essential to use OR here, not +, because of this. + */ + m = mask; + mask = (m >> 8) | (m << 24); /* rotate right to good compilers */ + m &= 0xff; + + if (m != 0) /* something to copy */ + { + if (m != 0xff) + *dp = (png_byte)((*dp & ~m) | (*sp & m)); + else + *dp = *sp; + } + + /* NOTE: this may overwrite the last byte with garbage if the image + * is not an exact number of bytes wide; libpng has always done + * this. + */ + if (row_width <= pixels_per_byte) + break; /* May need to restore part of the last byte */ + + row_width -= pixels_per_byte; + ++dp; + ++sp; + } + } + + else /* pixel_depth >= 8 */ + { + unsigned int bytes_to_copy, bytes_to_jump; + + /* Validate the depth - it must be a multiple of 8 */ + if (pixel_depth & 7) + png_error(png_ptr, "invalid user transform pixel depth"); + + pixel_depth >>= 3; /* now in bytes */ + row_width *= pixel_depth; + + /* Regardless of pass number the Adam 7 interlace always results in a + * fixed number of pixels to copy then to skip. There may be a + * different number of pixels to skip at the start though. + */ + { + unsigned int offset = PNG_PASS_START_COL(pass) * pixel_depth; + + row_width -= offset; + dp += offset; + sp += offset; + } + + /* Work out the bytes to copy. */ + if (display) + { + /* When doing the 'block' algorithm the pixel in the pass gets + * replicated to adjacent pixels. This is why the even (0,2,4,6) + * passes are skipped above - the entire expanded row is copied. + */ + bytes_to_copy = (1<<((6-pass)>>1)) * pixel_depth; + + /* But don't allow this number to exceed the actual row width. */ + if (bytes_to_copy > row_width) + bytes_to_copy = row_width; + } + + else /* normal row; Adam7 only ever gives us one pixel to copy. */ + bytes_to_copy = pixel_depth; + + /* In Adam7 there is a constant offset between where the pixels go. */ + bytes_to_jump = PNG_PASS_COL_OFFSET(pass) * pixel_depth; + + /* And simply copy these bytes. Some optimization is possible here, + * depending on the value of 'bytes_to_copy'. Special case the low + * byte counts, which we know to be frequent. + * + * Notice that these cases all 'return' rather than 'break' - this + * avoids an unnecessary test on whether to restore the last byte + * below. + */ + switch (bytes_to_copy) + { + case 1: + for (;;) + { + *dp = *sp; + + if (row_width <= bytes_to_jump) + return; + + dp += bytes_to_jump; + sp += bytes_to_jump; + row_width -= bytes_to_jump; + } + + case 2: + /* There is a possibility of a partial copy at the end here; this + * slows the code down somewhat. + */ + do + { + dp[0] = sp[0], dp[1] = sp[1]; + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + } + while (row_width > 1); + + /* And there can only be one byte left at this point: */ + *dp = *sp; + return; + + case 3: + /* This can only be the RGB case, so each copy is exactly one + * pixel and it is not necessary to check for a partial copy. + */ + for(;;) + { + dp[0] = sp[0], dp[1] = sp[1], dp[2] = sp[2]; + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + } + + default: +#if PNG_ALIGN_TYPE != PNG_ALIGN_NONE + /* Check for double byte alignment and, if possible, use a + * 16-bit copy. Don't attempt this for narrow images - ones that + * are less than an interlace panel wide. Don't attempt it for + * wide bytes_to_copy either - use the memcpy there. + */ + if (bytes_to_copy < 16 /*else use memcpy*/ && + png_isaligned(dp, png_uint_16) && + png_isaligned(sp, png_uint_16) && + bytes_to_copy % (sizeof (png_uint_16)) == 0 && + bytes_to_jump % (sizeof (png_uint_16)) == 0) + { + /* Everything is aligned for png_uint_16 copies, but try for + * png_uint_32 first. + */ + if (png_isaligned(dp, png_uint_32) && + png_isaligned(sp, png_uint_32) && + bytes_to_copy % (sizeof (png_uint_32)) == 0 && + bytes_to_jump % (sizeof (png_uint_32)) == 0) + { + png_uint_32p dp32 = png_aligncast(png_uint_32p,dp); + png_const_uint_32p sp32 = png_aligncastconst( + png_const_uint_32p, sp); + size_t skip = (bytes_to_jump-bytes_to_copy) / + (sizeof (png_uint_32)); + + do + { + size_t c = bytes_to_copy; + do + { + *dp32++ = *sp32++; + c -= (sizeof (png_uint_32)); + } + while (c > 0); + + if (row_width <= bytes_to_jump) + return; + + dp32 += skip; + sp32 += skip; + row_width -= bytes_to_jump; + } + while (bytes_to_copy <= row_width); + + /* Get to here when the row_width truncates the final copy. + * There will be 1-3 bytes left to copy, so don't try the + * 16-bit loop below. + */ + dp = (png_bytep)dp32; + sp = (png_const_bytep)sp32; + do + *dp++ = *sp++; + while (--row_width > 0); + return; + } + + /* Else do it in 16-bit quantities, but only if the size is + * not too large. + */ + else + { + png_uint_16p dp16 = png_aligncast(png_uint_16p, dp); + png_const_uint_16p sp16 = png_aligncastconst( + png_const_uint_16p, sp); + size_t skip = (bytes_to_jump-bytes_to_copy) / + (sizeof (png_uint_16)); + + do + { + size_t c = bytes_to_copy; + do + { + *dp16++ = *sp16++; + c -= (sizeof (png_uint_16)); + } + while (c > 0); + + if (row_width <= bytes_to_jump) + return; + + dp16 += skip; + sp16 += skip; + row_width -= bytes_to_jump; + } + while (bytes_to_copy <= row_width); + + /* End of row - 1 byte left, bytes_to_copy > row_width: */ + dp = (png_bytep)dp16; + sp = (png_const_bytep)sp16; + do + *dp++ = *sp++; + while (--row_width > 0); + return; + } + } +#endif /* PNG_ALIGN_ code */ + + /* The true default - use a memcpy: */ + for (;;) + { + memcpy(dp, sp, bytes_to_copy); + + if (row_width <= bytes_to_jump) + return; + + sp += bytes_to_jump; + dp += bytes_to_jump; + row_width -= bytes_to_jump; + if (bytes_to_copy > row_width) + bytes_to_copy = row_width; + } + } + + /* NOT REACHED*/ + } /* pixel_depth >= 8 */ + + /* Here if pixel_depth < 8 to check 'end_ptr' below. */ + } + else +#endif + + /* If here then the switch above wasn't used so just memcpy the whole row + * from the temporary row buffer (notice that this overwrites the end of the + * destination row if it is a partial byte.) + */ + memcpy(dp, sp, PNG_ROWBYTES(pixel_depth, row_width)); + + /* Restore the overwritten bits from the last byte if necessary. */ + if (end_ptr != NULL) + *end_ptr = (png_byte)((end_byte & end_mask) | (*end_ptr & ~end_mask)); +} + +#ifdef PNG_READ_INTERLACING_SUPPORTED +void /* PRIVATE */ +png_do_read_interlace(png_row_infop row_info, png_bytep row, int pass, + png_uint_32 transformations /* Because these may affect the byte layout */) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + /* Offset to next interlace block */ + static PNG_CONST int png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + png_debug(1, "in png_do_read_interlace"); + if (row != NULL && row_info != NULL) + { + png_uint_32 final_width; + + final_width = row_info->width * png_pass_inc[pass]; + + switch (row_info->pixel_depth) + { + case 1: + { + png_bytep sp = row + (png_size_t)((row_info->width - 1) >> 3); + png_bytep dp = row + (png_size_t)((final_width - 1) >> 3); + int sshift, dshift; + int s_start, s_end, s_inc; + int jstop = png_pass_inc[pass]; + png_byte v; + png_uint_32 i; + int j; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if (transformations & PNG_PACKSWAP) + { + sshift = (int)((row_info->width + 7) & 0x07); + dshift = (int)((final_width + 7) & 0x07); + s_start = 7; + s_end = 0; + s_inc = -1; + } + + else +#endif + { + sshift = 7 - (int)((row_info->width + 7) & 0x07); + dshift = 7 - (int)((final_width + 7) & 0x07); + s_start = 0; + s_end = 7; + s_inc = 1; + } + + for (i = 0; i < row_info->width; i++) + { + v = (png_byte)((*sp >> sshift) & 0x01); + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0x7f7f >> (7 - dshift)); + tmp |= v << dshift; + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift += s_inc; + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift += s_inc; + } + break; + } + + case 2: + { + png_bytep sp = row + (png_uint_32)((row_info->width - 1) >> 2); + png_bytep dp = row + (png_uint_32)((final_width - 1) >> 2); + int sshift, dshift; + int s_start, s_end, s_inc; + int jstop = png_pass_inc[pass]; + png_uint_32 i; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if (transformations & PNG_PACKSWAP) + { + sshift = (int)(((row_info->width + 3) & 0x03) << 1); + dshift = (int)(((final_width + 3) & 0x03) << 1); + s_start = 6; + s_end = 0; + s_inc = -2; + } + + else +#endif + { + sshift = (int)((3 - ((row_info->width + 3) & 0x03)) << 1); + dshift = (int)((3 - ((final_width + 3) & 0x03)) << 1); + s_start = 0; + s_end = 6; + s_inc = 2; + } + + for (i = 0; i < row_info->width; i++) + { + png_byte v; + int j; + + v = (png_byte)((*sp >> sshift) & 0x03); + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0x3f3f >> (6 - dshift)); + tmp |= v << dshift; + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift += s_inc; + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift += s_inc; + } + break; + } + + case 4: + { + png_bytep sp = row + (png_size_t)((row_info->width - 1) >> 1); + png_bytep dp = row + (png_size_t)((final_width - 1) >> 1); + int sshift, dshift; + int s_start, s_end, s_inc; + png_uint_32 i; + int jstop = png_pass_inc[pass]; + +#ifdef PNG_READ_PACKSWAP_SUPPORTED + if (transformations & PNG_PACKSWAP) + { + sshift = (int)(((row_info->width + 1) & 0x01) << 2); + dshift = (int)(((final_width + 1) & 0x01) << 2); + s_start = 4; + s_end = 0; + s_inc = -4; + } + + else +#endif + { + sshift = (int)((1 - ((row_info->width + 1) & 0x01)) << 2); + dshift = (int)((1 - ((final_width + 1) & 0x01)) << 2); + s_start = 0; + s_end = 4; + s_inc = 4; + } + + for (i = 0; i < row_info->width; i++) + { + png_byte v = (png_byte)((*sp >> sshift) & 0x0f); + int j; + + for (j = 0; j < jstop; j++) + { + unsigned int tmp = *dp & (0xf0f >> (4 - dshift)); + tmp |= v << dshift; + *dp = (png_byte)(tmp & 0xff); + + if (dshift == s_end) + { + dshift = s_start; + dp--; + } + + else + dshift += s_inc; + } + + if (sshift == s_end) + { + sshift = s_start; + sp--; + } + + else + sshift += s_inc; + } + break; + } + + default: + { + png_size_t pixel_bytes = (row_info->pixel_depth >> 3); + + png_bytep sp = row + (png_size_t)(row_info->width - 1) + * pixel_bytes; + + png_bytep dp = row + (png_size_t)(final_width - 1) * pixel_bytes; + + int jstop = png_pass_inc[pass]; + png_uint_32 i; + + for (i = 0; i < row_info->width; i++) + { + png_byte v[8]; + int j; + + memcpy(v, sp, pixel_bytes); + + for (j = 0; j < jstop; j++) + { + memcpy(dp, v, pixel_bytes); + dp -= pixel_bytes; + } + + sp -= pixel_bytes; + } + break; + } + } + + row_info->width = final_width; + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, final_width); + } +#ifndef PNG_READ_PACKSWAP_SUPPORTED + PNG_UNUSED(transformations) /* Silence compiler warning */ +#endif +} +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + +static void +png_read_filter_row_sub(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + png_size_t i; + png_size_t istop = row_info->rowbytes; + unsigned int bpp = (row_info->pixel_depth + 7) >> 3; + png_bytep rp = row + bpp; + + PNG_UNUSED(prev_row) + + for (i = bpp; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + (int)(*(rp-bpp))) & 0xff); + rp++; + } +} + +static void +png_read_filter_row_up(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + png_size_t i; + png_size_t istop = row_info->rowbytes; + png_bytep rp = row; + png_const_bytep pp = prev_row; + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + (int)(*pp++)) & 0xff); + rp++; + } +} + +static void +png_read_filter_row_avg(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + png_size_t i; + png_bytep rp = row; + png_const_bytep pp = prev_row; + unsigned int bpp = (row_info->pixel_depth + 7) >> 3; + png_size_t istop = row_info->rowbytes - bpp; + + for (i = 0; i < bpp; i++) + { + *rp = (png_byte)(((int)(*rp) + + ((int)(*pp++) / 2 )) & 0xff); + + rp++; + } + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(((int)(*rp) + + (int)(*pp++ + *(rp-bpp)) / 2 ) & 0xff); + + rp++; + } +} + +static void +png_read_filter_row_paeth_1byte_pixel(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + png_bytep rp_end = row + row_info->rowbytes; + int a, c; + + /* First pixel/byte */ + c = *prev_row++; + a = *row + c; + *row++ = (png_byte)a; + + /* Remainder */ + while (row < rp_end) + { + int b, pa, pb, pc, p; + + a &= 0xff; /* From previous iteration or start */ + b = *prev_row++; + + p = b - c; + pc = a - c; + +# ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +# else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +# endif + + /* Find the best predictor, the least of pa, pb, pc favoring the earlier + * ones in the case of a tie. + */ + if (pb < pa) pa = pb, a = b; + if (pc < pa) a = c; + + /* Calculate the current pixel in a, and move the previous row pixel to c + * for the next time round the loop + */ + c = b; + a += *row; + *row++ = (png_byte)a; + } +} + +static void +png_read_filter_row_paeth_multibyte_pixel(png_row_infop row_info, png_bytep row, + png_const_bytep prev_row) +{ + int bpp = (row_info->pixel_depth + 7) >> 3; + png_bytep rp_end = row + bpp; + + /* Process the first pixel in the row completely (this is the same as 'up' + * because there is only one candidate predictor for the first row). + */ + while (row < rp_end) + { + int a = *row + *prev_row++; + *row++ = (png_byte)a; + } + + /* Remainder */ + rp_end += row_info->rowbytes - bpp; + + while (row < rp_end) + { + int a, b, c, pa, pb, pc, p; + + c = *(prev_row - bpp); + a = *(row - bpp); + b = *prev_row++; + + p = b - c; + pc = a - c; + +# ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +# else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +# endif + + if (pb < pa) pa = pb, a = b; + if (pc < pa) a = c; + + //c = b; + a += *row; + *row++ = (png_byte)a; + } +} + +static void +png_init_filter_functions(png_structrp pp) + /* This function is called once for every PNG image to set the + * implementations required to reverse the filtering of PNG rows. Reversing + * the filter is the first transformation performed on the row data. It is + * performed in place, therefore an implementation can be selected based on + * the image pixel format. If the implementation depends on image width then + * take care to ensure that it works corretly if the image is interlaced - + * interlacing causes the actual row width to vary. + */ +{ + unsigned int bpp = (pp->pixel_depth + 7) >> 3; + + pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub; + pp->read_filter[PNG_FILTER_VALUE_UP-1] = png_read_filter_row_up; + pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg; + if (bpp == 1) + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth_1byte_pixel; + else + pp->read_filter[PNG_FILTER_VALUE_PAETH-1] = + png_read_filter_row_paeth_multibyte_pixel; + +#ifdef PNG_FILTER_OPTIMIZATIONS + /* To use this define PNG_FILTER_OPTIMIZATIONS as the name of a function to + * call to install hardware optimizations for the above functions; simply + * replace whatever elements of the pp->read_filter[] array with a hardware + * specific (or, for that matter, generic) optimization. + * + * To see an example of this examine what configure.ac does when + * --enable-arm-neon is specified on the command line. + */ + PNG_FILTER_OPTIMIZATIONS(pp, bpp); +#endif +} + +void /* PRIVATE */ +png_read_filter_row(png_structrp pp, png_row_infop row_info, png_bytep row, + png_const_bytep prev_row, int filter) +{ + /* OPTIMIZATION: DO NOT MODIFY THIS FUNCTION, instead #define + * PNG_FILTER_OPTIMIZATIONS to a function that overrides the generic + * implementations. See png_init_filter_functions above. + */ + if (pp->read_filter[0] == NULL) + png_init_filter_functions(pp); + if (filter > PNG_FILTER_VALUE_NONE && filter < PNG_FILTER_VALUE_LAST) + pp->read_filter[filter-1](row_info, row, prev_row); +} + +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED +void /* PRIVATE */ +png_read_IDAT_data(png_structrp png_ptr, png_bytep output, + png_alloc_size_t avail_out) +{ + /* Loop reading IDATs and decompressing the result into output[avail_out] */ + png_ptr->zstream.next_out = output; + png_ptr->zstream.avail_out = 0; /* safety: set below */ + + if (output == NULL) + avail_out = 0; + + do + { + int ret; + png_byte tmpbuf[PNG_INFLATE_BUF_SIZE]; + + if (png_ptr->zstream.avail_in == 0) + { + uInt avail_in; + png_bytep buffer; + + while (png_ptr->idat_size == 0) + { + png_crc_finish(png_ptr, 0); + + png_ptr->idat_size = png_read_chunk_header(png_ptr); + /* This is an error even in the 'check' case because the code just + * consumed a non-IDAT header. + */ + if (png_ptr->chunk_name != png_IDAT) + png_error(png_ptr, "Not enough image data"); + } + + avail_in = png_ptr->IDAT_read_size; + + if (avail_in > png_ptr->idat_size) + avail_in = (uInt)png_ptr->idat_size; + + /* A PNG with a gradually increasing IDAT size will defeat this attempt + * to minimize memory usage by causing lots of re-allocs, but + * realistically doing IDAT_read_size re-allocs is not likely to be a + * big problem. + */ + buffer = png_read_buffer(png_ptr, avail_in, 0/*error*/); + + png_crc_read(png_ptr, buffer, avail_in); + png_ptr->idat_size -= avail_in; + + png_ptr->zstream.next_in = buffer; + png_ptr->zstream.avail_in = avail_in; + } + + /* And set up the output side. */ + if (output != NULL) /* standard read */ + { + uInt out = ZLIB_IO_MAX; + + if (out > avail_out) + out = (uInt)avail_out; + + avail_out -= out; + png_ptr->zstream.avail_out = out; + } + + else /* check for end */ + { + png_ptr->zstream.next_out = tmpbuf; + png_ptr->zstream.avail_out = (sizeof tmpbuf); + } + + /* Use NO_FLUSH; this gives zlib the maximum opportunity to optimize the + * process. If the LZ stream is truncated the sequential reader will + * terminally damage the stream, above, by reading the chunk header of the + * following chunk (it then exits with png_error). + * + * TODO: deal more elegantly with truncated IDAT lists. + */ + ret = inflate(&png_ptr->zstream, Z_NO_FLUSH); + + /* Take the unconsumed output back (so, in the 'check' case this just + * counts up). + */ + avail_out += png_ptr->zstream.avail_out; + png_ptr->zstream.avail_out = 0; + + if (ret == Z_STREAM_END) + { + /* Do this for safety; we won't read any more into this row. */ + png_ptr->zstream.next_out = NULL; + + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + + if (png_ptr->zstream.avail_in > 0 || png_ptr->idat_size > 0) + png_chunk_benign_error(png_ptr, "Extra compressed data"); + break; + } + + if (ret != Z_OK) + { + png_zstream_error(png_ptr, ret); + + if (output != NULL) + png_chunk_error(png_ptr, png_ptr->zstream.msg); + + else /* checking */ + { + png_chunk_benign_error(png_ptr, png_ptr->zstream.msg); + return; + } + } + } while (avail_out > 0); + + if (avail_out > 0) + { + /* The stream ended before the image; this is the same as too few IDATs so + * should be handled the same way. + */ + if (output != NULL) + png_error(png_ptr, "Not enough image data"); + + else /* checking */ + png_chunk_benign_error(png_ptr, "Too much image data"); + } +} + +void /* PRIVATE */ +png_read_finish_IDAT(png_structrp png_ptr) +{ + /* We don't need any more data and the stream should have ended, however the + * LZ end code may actually not have been processed. In this case we must + * read it otherwise stray unread IDAT data or, more likely, an IDAT chunk + * may still remain to be consumed. + */ + if (!(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + { + /* The NULL causes png_read_IDAT_data to swallow any remaining bytes in + * the compressed stream, but the stream may be damaged too, so even after + * this call we may need to terminate the zstream ownership. + */ + png_read_IDAT_data(png_ptr, NULL, 0); + png_ptr->zstream.next_out = NULL; /* safety */ + + /* Now clear everything out for safety; the following may not have been + * done. + */ + if (!(png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED)) + { + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + } + } + + /* If the zstream has not been released do it now *and* terminate the reading + * of the final IDAT chunk. + */ + if (png_ptr->zowner == png_IDAT) + { + /* Always do this; the pointers otherwise point into the read buffer. */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + + /* Now we no longer own the zstream. */ + png_ptr->zowner = 0; + + /* The slightly weird semantics of the sequential IDAT reading is that we + * are always in or at the end of an IDAT chunk, so we always need to do a + * crc_finish here. If idat_size is non-zero we also need to read the + * spurious bytes at the end of the chunk now. + */ + (void)png_crc_finish(png_ptr, png_ptr->idat_size); + } +} + +void /* PRIVATE */ +png_read_finish_row(png_structrp png_ptr) +{ +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static PNG_CONST png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static PNG_CONST png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + + png_debug(1, "in png_read_finish_row"); + png_ptr->row_number++; + if (png_ptr->row_number < png_ptr->num_rows) + return; + +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced) + { + png_ptr->row_number = 0; + + /* TO DO: don't do this if prev_row isn't needed (requires + * read-ahead of the next row's filter byte. + */ + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + do + { + png_ptr->pass++; + + if (png_ptr->pass >= 7) + break; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + if (!(png_ptr->transformations & PNG_INTERLACE)) + { + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + } + + else /* if (png_ptr->transformations & PNG_INTERLACE) */ + break; /* libpng deinterlacing sees every row */ + + } while (png_ptr->num_rows == 0 || png_ptr->iwidth == 0); + + if (png_ptr->pass < 7) + return; + } +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + + /* Here after at the end of the last row of the last pass. */ + png_read_finish_IDAT(png_ptr); +} +#endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ + +void /* PRIVATE */ +png_read_start_row(png_structrp png_ptr) +{ +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static PNG_CONST png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static PNG_CONST png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif + + int max_pixel_depth; + png_size_t row_bytes; + + png_debug(1, "in png_read_start_row"); + +#ifdef PNG_READ_TRANSFORMS_SUPPORTED + png_init_read_transformations(png_ptr); +#endif +#ifdef PNG_READ_INTERLACING_SUPPORTED + if (png_ptr->interlaced) + { + if (!(png_ptr->transformations & PNG_INTERLACE)) + png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - + png_pass_ystart[0]) / png_pass_yinc[0]; + + else + png_ptr->num_rows = png_ptr->height; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + } + + else +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + { + png_ptr->num_rows = png_ptr->height; + png_ptr->iwidth = png_ptr->width; + } + + max_pixel_depth = png_ptr->pixel_depth; + + /* WARNING: * png_read_transform_info (pngrtran.c) performs a simpliar set of + * calculations to calculate the final pixel depth, then + * png_do_read_transforms actually does the transforms. This means that the + * code which effectively calculates this value is actually repeated in three + * separate places. They must all match. Innocent changes to the order of + * transformations can and will break libpng in a way that causes memory + * overwrites. + * + * TODO: fix this. + */ +#ifdef PNG_READ_PACK_SUPPORTED + if ((png_ptr->transformations & PNG_PACK) && png_ptr->bit_depth < 8) + max_pixel_depth = 8; +#endif + +#ifdef PNG_READ_EXPAND_SUPPORTED + if (png_ptr->transformations & PNG_EXPAND) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (png_ptr->num_trans) + max_pixel_depth = 32; + + else + max_pixel_depth = 24; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + if (max_pixel_depth < 8) + max_pixel_depth = 8; + + if (png_ptr->num_trans) + max_pixel_depth *= 2; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB) + { + if (png_ptr->num_trans) + { + max_pixel_depth *= 4; + max_pixel_depth /= 3; + } + } + } +#endif + +#ifdef PNG_READ_EXPAND_16_SUPPORTED + if (png_ptr->transformations & PNG_EXPAND_16) + { +# ifdef PNG_READ_EXPAND_SUPPORTED + /* In fact it is an error if it isn't supported, but checking is + * the safe way. + */ + if (png_ptr->transformations & PNG_EXPAND) + { + if (png_ptr->bit_depth < 16) + max_pixel_depth *= 2; + } + else +# endif + png_ptr->transformations &= ~PNG_EXPAND_16; + } +#endif + +#ifdef PNG_READ_FILLER_SUPPORTED + if (png_ptr->transformations & (PNG_FILLER)) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_GRAY) + { + if (max_pixel_depth <= 8) + max_pixel_depth = 16; + + else + max_pixel_depth = 32; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB || + png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + if (max_pixel_depth <= 32) + max_pixel_depth = 32; + + else + max_pixel_depth = 64; + } + } +#endif + +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + if (png_ptr->transformations & PNG_GRAY_TO_RGB) + { + if ( +#ifdef PNG_READ_EXPAND_SUPPORTED + (png_ptr->num_trans && (png_ptr->transformations & PNG_EXPAND)) || +#endif +#ifdef PNG_READ_FILLER_SUPPORTED + (png_ptr->transformations & (PNG_FILLER)) || +#endif + png_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (max_pixel_depth <= 16) + max_pixel_depth = 32; + + else + max_pixel_depth = 64; + } + + else + { + if (max_pixel_depth <= 8) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + max_pixel_depth = 32; + + else + max_pixel_depth = 24; + } + + else if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + max_pixel_depth = 64; + + else + max_pixel_depth = 48; + } + } +#endif + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) && \ +defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) + if (png_ptr->transformations & PNG_USER_TRANSFORM) + { + int user_pixel_depth = png_ptr->user_transform_depth * + png_ptr->user_transform_channels; + + if (user_pixel_depth > max_pixel_depth) + max_pixel_depth = user_pixel_depth; + } +#endif + + /* This value is stored in png_struct and double checked in the row read + * code. + */ + png_ptr->maximum_pixel_depth = (png_byte)max_pixel_depth; + png_ptr->transformed_pixel_depth = 0; /* calculated on demand */ + + /* Align the width on the next larger 8 pixels. Mainly used + * for interlacing + */ + row_bytes = ((png_ptr->width + 7) & ~((png_uint_32)7)); + /* Calculate the maximum bytes needed, adding a byte and a pixel + * for safety's sake + */ + row_bytes = PNG_ROWBYTES(max_pixel_depth, row_bytes) + + 1 + ((max_pixel_depth + 7) >> 3); + +#ifdef PNG_MAX_MALLOC_64K + if (row_bytes > (png_uint_32)65536L) + png_error(png_ptr, "This image requires a row greater than 64KB"); +#endif + + if (row_bytes + 48 > png_ptr->old_big_row_buf_size) + { + png_free(png_ptr, png_ptr->big_row_buf); + png_free(png_ptr, png_ptr->big_prev_row); + + if (png_ptr->interlaced) + png_ptr->big_row_buf = (png_bytep)png_calloc(png_ptr, + row_bytes + 48); + + else + png_ptr->big_row_buf = (png_bytep)png_malloc(png_ptr, row_bytes + 48); + + png_ptr->big_prev_row = (png_bytep)png_malloc(png_ptr, row_bytes + 48); + +#ifdef PNG_ALIGNED_MEMORY_SUPPORTED + /* Use 16-byte aligned memory for row_buf with at least 16 bytes + * of padding before and after row_buf; treat prev_row similarly. + * NOTE: the alignment is to the start of the pixels, one beyond the start + * of the buffer, because of the filter byte. Prior to libpng 1.5.6 this + * was incorrect; the filter byte was aligned, which had the exact + * opposite effect of that intended. + */ + { + png_bytep temp = png_ptr->big_row_buf + 32; + int extra = (int)((temp - (png_bytep)0) & 0x0f); + png_ptr->row_buf = temp - extra - 1/*filter byte*/; + + temp = png_ptr->big_prev_row + 32; + extra = (int)((temp - (png_bytep)0) & 0x0f); + png_ptr->prev_row = temp - extra - 1/*filter byte*/; + } + +#else + /* Use 31 bytes of padding before and 17 bytes after row_buf. */ + png_ptr->row_buf = png_ptr->big_row_buf + 31; + png_ptr->prev_row = png_ptr->big_prev_row + 31; +#endif + png_ptr->old_big_row_buf_size = row_bytes + 48; + } + +#ifdef PNG_MAX_MALLOC_64K + if (png_ptr->rowbytes > 65535) + png_error(png_ptr, "This image requires a row greater than 64KB"); + +#endif + if (png_ptr->rowbytes > (PNG_SIZE_MAX - 1)) + png_error(png_ptr, "Row has too many bytes to allocate in memory"); + + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); + + png_debug1(3, "width = %u,", png_ptr->width); + png_debug1(3, "height = %u,", png_ptr->height); + png_debug1(3, "iwidth = %u,", png_ptr->iwidth); + png_debug1(3, "num_rows = %u,", png_ptr->num_rows); + png_debug1(3, "rowbytes = %lu,", (unsigned long)png_ptr->rowbytes); + png_debug1(3, "irowbytes = %lu", + (unsigned long)PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->iwidth) + 1); + + /* The sequential reader needs a buffer for IDAT, but the progressive reader + * does not, so free the read buffer now regardless; the sequential reader + * reallocates it on demand. + */ + if (png_ptr->read_buffer) + { + png_bytep buffer = png_ptr->read_buffer; + + png_ptr->read_buffer_size = 0; + png_ptr->read_buffer = NULL; + png_free(png_ptr, buffer); + } + + /* Finally claim the zstream for the inflate of the IDAT data, use the bits + * value from the stream (note that this will result in a fatal error if the + * IDAT stream has a bogus deflate header window_bits value, but this should + * not be happening any longer!) + */ + if (png_inflate_claim(png_ptr, png_IDAT, 0) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + png_ptr->flags |= PNG_FLAG_ROW_INIT; +} +#endif /* PNG_READ_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngset.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngset.c new file mode 100644 index 0000000..a1b9d49 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngset.c @@ -0,0 +1,1606 @@ + +/* pngset.c - storage of image information into info struct + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * The functions here are used during reads to store data from the file + * into the info struct, and during writes to store application data + * into the info struct for writing into the file. This abstracts the + * info struct and allows us to change the structure in the future. + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +#ifdef PNG_bKGD_SUPPORTED +void PNGAPI +png_set_bKGD(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_color_16p background) +{ + png_debug1(1, "in %s storage function", "bKGD"); + + if (png_ptr == NULL || info_ptr == NULL || background == NULL) + return; + + info_ptr->background = *background; + info_ptr->valid |= PNG_INFO_bKGD; +} +#endif + +#ifdef PNG_cHRM_SUPPORTED +void PNGFAPI +png_set_cHRM_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point white_x, png_fixed_point white_y, png_fixed_point red_x, + png_fixed_point red_y, png_fixed_point green_x, png_fixed_point green_y, + png_fixed_point blue_x, png_fixed_point blue_y) +{ + png_xy xy; + + png_debug1(1, "in %s storage function", "cHRM fixed"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + xy.redx = red_x; + xy.redy = red_y; + xy.greenx = green_x; + xy.greeny = green_y; + xy.bluex = blue_x; + xy.bluey = blue_y; + xy.whitex = white_x; + xy.whitey = white_y; + + if (png_colorspace_set_chromaticities(png_ptr, &info_ptr->colorspace, &xy, + 2/* override with app values*/)) + info_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + + png_colorspace_sync_info(png_ptr, info_ptr); +} + +void PNGFAPI +png_set_cHRM_XYZ_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point int_red_X, png_fixed_point int_red_Y, + png_fixed_point int_red_Z, png_fixed_point int_green_X, + png_fixed_point int_green_Y, png_fixed_point int_green_Z, + png_fixed_point int_blue_X, png_fixed_point int_blue_Y, + png_fixed_point int_blue_Z) +{ + png_XYZ XYZ; + + png_debug1(1, "in %s storage function", "cHRM XYZ fixed"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + XYZ.red_X = int_red_X; + XYZ.red_Y = int_red_Y; + XYZ.red_Z = int_red_Z; + XYZ.green_X = int_green_X; + XYZ.green_Y = int_green_Y; + XYZ.green_Z = int_green_Z; + XYZ.blue_X = int_blue_X; + XYZ.blue_Y = int_blue_Y; + XYZ.blue_Z = int_blue_Z; + + if (png_colorspace_set_endpoints(png_ptr, &info_ptr->colorspace, &XYZ, 2)) + info_ptr->colorspace.flags |= PNG_COLORSPACE_FROM_cHRM; + + png_colorspace_sync_info(png_ptr, info_ptr); +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_cHRM(png_const_structrp png_ptr, png_inforp info_ptr, + double white_x, double white_y, double red_x, double red_y, + double green_x, double green_y, double blue_x, double blue_y) +{ + png_set_cHRM_fixed(png_ptr, info_ptr, + png_fixed(png_ptr, white_x, "cHRM White X"), + png_fixed(png_ptr, white_y, "cHRM White Y"), + png_fixed(png_ptr, red_x, "cHRM Red X"), + png_fixed(png_ptr, red_y, "cHRM Red Y"), + png_fixed(png_ptr, green_x, "cHRM Green X"), + png_fixed(png_ptr, green_y, "cHRM Green Y"), + png_fixed(png_ptr, blue_x, "cHRM Blue X"), + png_fixed(png_ptr, blue_y, "cHRM Blue Y")); +} + +void PNGAPI +png_set_cHRM_XYZ(png_const_structrp png_ptr, png_inforp info_ptr, double red_X, + double red_Y, double red_Z, double green_X, double green_Y, double green_Z, + double blue_X, double blue_Y, double blue_Z) +{ + png_set_cHRM_XYZ_fixed(png_ptr, info_ptr, + png_fixed(png_ptr, red_X, "cHRM Red X"), + png_fixed(png_ptr, red_Y, "cHRM Red Y"), + png_fixed(png_ptr, red_Z, "cHRM Red Z"), + png_fixed(png_ptr, green_X, "cHRM Red X"), + png_fixed(png_ptr, green_Y, "cHRM Red Y"), + png_fixed(png_ptr, green_Z, "cHRM Red Z"), + png_fixed(png_ptr, blue_X, "cHRM Red X"), + png_fixed(png_ptr, blue_Y, "cHRM Red Y"), + png_fixed(png_ptr, blue_Z, "cHRM Red Z")); +} +# endif /* PNG_FLOATING_POINT_SUPPORTED */ + +#endif /* PNG_cHRM_SUPPORTED */ + +#ifdef PNG_gAMA_SUPPORTED +void PNGFAPI +png_set_gAMA_fixed(png_const_structrp png_ptr, png_inforp info_ptr, + png_fixed_point file_gamma) +{ + png_debug1(1, "in %s storage function", "gAMA"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_colorspace_set_gamma(png_ptr, &info_ptr->colorspace, file_gamma); + png_colorspace_sync_info(png_ptr, info_ptr); +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_gAMA(png_const_structrp png_ptr, png_inforp info_ptr, double file_gamma) +{ + png_set_gAMA_fixed(png_ptr, info_ptr, png_fixed(png_ptr, file_gamma, + "png_set_gAMA")); +} +# endif +#endif + +#ifdef PNG_hIST_SUPPORTED +void PNGAPI +png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_uint_16p hist) +{ + int i; + + png_debug1(1, "in %s storage function", "hIST"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (info_ptr->num_palette == 0 || info_ptr->num_palette + > PNG_MAX_PALETTE_LENGTH) + { + png_warning(png_ptr, + "Invalid palette size, hIST allocation skipped"); + + return; + } + + png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0); + + /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in + * version 1.2.1 + */ + info_ptr->hist = png_voidcast(png_uint_16p, png_malloc_warn(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_uint_16)))); + + if (info_ptr->hist == NULL) + { + png_warning(png_ptr, "Insufficient memory for hIST chunk data"); + return; + } + + info_ptr->free_me |= PNG_FREE_HIST; + + for (i = 0; i < info_ptr->num_palette; i++) + info_ptr->hist[i] = hist[i]; + + info_ptr->valid |= PNG_INFO_hIST; +} +#endif + +void PNGAPI +png_set_IHDR(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_32 width, png_uint_32 height, int bit_depth, + int color_type, int interlace_type, int compression_type, + int filter_type) +{ + png_debug1(1, "in %s storage function", "IHDR"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->width = width; + info_ptr->height = height; + info_ptr->bit_depth = (png_byte)bit_depth; + info_ptr->color_type = (png_byte)color_type; + info_ptr->compression_type = (png_byte)compression_type; + info_ptr->filter_type = (png_byte)filter_type; + info_ptr->interlace_type = (png_byte)interlace_type; + + png_check_IHDR (png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->interlace_type, + info_ptr->compression_type, info_ptr->filter_type); + + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + info_ptr->channels = 1; + + else if (info_ptr->color_type & PNG_COLOR_MASK_COLOR) + info_ptr->channels = 3; + + else + info_ptr->channels = 1; + + if (info_ptr->color_type & PNG_COLOR_MASK_ALPHA) + info_ptr->channels++; + + info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth); + + /* Check for potential overflow */ + if (width > + (PNG_UINT_32_MAX >> 3) /* 8-byte RRGGBBAA pixels */ + - 48 /* bigrowbuf hack */ + - 1 /* filter byte */ + - 7*8 /* rounding of width to multiple of 8 pixels */ + - 8) /* extra max_pixel_depth pad */ + info_ptr->rowbytes = 0; + else + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); +} + +#ifdef PNG_oFFs_SUPPORTED +void PNGAPI +png_set_oFFs(png_const_structrp png_ptr, png_inforp info_ptr, + png_int_32 offset_x, png_int_32 offset_y, int unit_type) +{ + png_debug1(1, "in %s storage function", "oFFs"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->x_offset = offset_x; + info_ptr->y_offset = offset_y; + info_ptr->offset_unit_type = (png_byte)unit_type; + info_ptr->valid |= PNG_INFO_oFFs; +} +#endif + +#ifdef PNG_pCAL_SUPPORTED +void PNGAPI +png_set_pCAL(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_charp purpose, png_int_32 X0, png_int_32 X1, int type, + int nparams, png_const_charp units, png_charpp params) +{ + png_size_t length; + int i; + + png_debug1(1, "in %s storage function", "pCAL"); + + if (png_ptr == NULL || info_ptr == NULL || purpose == NULL || units == NULL + || (nparams > 0 && params == NULL)) + return; + + length = strlen(purpose) + 1; + png_debug1(3, "allocating purpose for info (%lu bytes)", + (unsigned long)length); + + /* TODO: validate format of calibration name and unit name */ + + /* Check that the type matches the specification. */ + if (type < 0 || type > 3) + png_error(png_ptr, "Invalid pCAL equation type"); + + if (nparams < 0 || nparams > 255) + png_error(png_ptr, "Invalid pCAL parameter count"); + + /* Validate params[nparams] */ + for (i=0; ipcal_purpose = png_voidcast(png_charp, + png_malloc_warn(png_ptr, length)); + + if (info_ptr->pcal_purpose == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL purpose"); + return; + } + + memcpy(info_ptr->pcal_purpose, purpose, length); + + png_debug(3, "storing X0, X1, type, and nparams in info"); + info_ptr->pcal_X0 = X0; + info_ptr->pcal_X1 = X1; + info_ptr->pcal_type = (png_byte)type; + info_ptr->pcal_nparams = (png_byte)nparams; + + length = strlen(units) + 1; + png_debug1(3, "allocating units for info (%lu bytes)", + (unsigned long)length); + + info_ptr->pcal_units = png_voidcast(png_charp, + png_malloc_warn(png_ptr, length)); + + if (info_ptr->pcal_units == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL units"); + return; + } + + memcpy(info_ptr->pcal_units, units, length); + + info_ptr->pcal_params = png_voidcast(png_charpp, png_malloc_warn(png_ptr, + (png_size_t)((nparams + 1) * (sizeof (png_charp))))); + + if (info_ptr->pcal_params == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL params"); + return; + } + + memset(info_ptr->pcal_params, 0, (nparams + 1) * (sizeof (png_charp))); + + for (i = 0; i < nparams; i++) + { + length = strlen(params[i]) + 1; + png_debug2(3, "allocating parameter %d for info (%lu bytes)", i, + (unsigned long)length); + + info_ptr->pcal_params[i] = (png_charp)png_malloc_warn(png_ptr, length); + + if (info_ptr->pcal_params[i] == NULL) + { + png_warning(png_ptr, "Insufficient memory for pCAL parameter"); + return; + } + + memcpy(info_ptr->pcal_params[i], params[i], length); + } + + info_ptr->valid |= PNG_INFO_pCAL; + info_ptr->free_me |= PNG_FREE_PCAL; +} +#endif + +#ifdef PNG_sCAL_SUPPORTED +void PNGAPI +png_set_sCAL_s(png_const_structrp png_ptr, png_inforp info_ptr, + int unit, png_const_charp swidth, png_const_charp sheight) +{ + png_size_t lengthw = 0, lengthh = 0; + + png_debug1(1, "in %s storage function", "sCAL"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Double check the unit (should never get here with an invalid + * unit unless this is an API call.) + */ + if (unit != 1 && unit != 2) + png_error(png_ptr, "Invalid sCAL unit"); + + if (swidth == NULL || (lengthw = strlen(swidth)) == 0 || + swidth[0] == 45 /* '-' */ || !png_check_fp_string(swidth, lengthw)) + png_error(png_ptr, "Invalid sCAL width"); + + if (sheight == NULL || (lengthh = strlen(sheight)) == 0 || + sheight[0] == 45 /* '-' */ || !png_check_fp_string(sheight, lengthh)) + png_error(png_ptr, "Invalid sCAL height"); + + info_ptr->scal_unit = (png_byte)unit; + + ++lengthw; + + png_debug1(3, "allocating unit for info (%u bytes)", (unsigned int)lengthw); + + info_ptr->scal_s_width = png_voidcast(png_charp, + png_malloc_warn(png_ptr, lengthw)); + + if (info_ptr->scal_s_width == NULL) + { + png_warning(png_ptr, "Memory allocation failed while processing sCAL"); + return; + } + + memcpy(info_ptr->scal_s_width, swidth, lengthw); + + ++lengthh; + + png_debug1(3, "allocating unit for info (%u bytes)", (unsigned int)lengthh); + + info_ptr->scal_s_height = png_voidcast(png_charp, + png_malloc_warn(png_ptr, lengthh)); + + if (info_ptr->scal_s_height == NULL) + { + png_free (png_ptr, info_ptr->scal_s_width); + info_ptr->scal_s_width = NULL; + + png_warning(png_ptr, "Memory allocation failed while processing sCAL"); + return; + } + + memcpy(info_ptr->scal_s_height, sheight, lengthh); + + info_ptr->valid |= PNG_INFO_sCAL; + info_ptr->free_me |= PNG_FREE_SCAL; +} + +# ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_sCAL(png_const_structrp png_ptr, png_inforp info_ptr, int unit, + double width, double height) +{ + png_debug1(1, "in %s storage function", "sCAL"); + + /* Check the arguments. */ + if (width <= 0) + png_warning(png_ptr, "Invalid sCAL width ignored"); + + else if (height <= 0) + png_warning(png_ptr, "Invalid sCAL height ignored"); + + else + { + /* Convert 'width' and 'height' to ASCII. */ + char swidth[PNG_sCAL_MAX_DIGITS+1]; + char sheight[PNG_sCAL_MAX_DIGITS+1]; + + png_ascii_from_fp(png_ptr, swidth, (sizeof swidth), width, + PNG_sCAL_PRECISION); + png_ascii_from_fp(png_ptr, sheight, (sizeof sheight), height, + PNG_sCAL_PRECISION); + + png_set_sCAL_s(png_ptr, info_ptr, unit, swidth, sheight); + } +} +# endif + +# ifdef PNG_FIXED_POINT_SUPPORTED +void PNGAPI +png_set_sCAL_fixed(png_const_structrp png_ptr, png_inforp info_ptr, int unit, + png_fixed_point width, png_fixed_point height) +{ + png_debug1(1, "in %s storage function", "sCAL"); + + /* Check the arguments. */ + if (width <= 0) + png_warning(png_ptr, "Invalid sCAL width ignored"); + + else if (height <= 0) + png_warning(png_ptr, "Invalid sCAL height ignored"); + + else + { + /* Convert 'width' and 'height' to ASCII. */ + char swidth[PNG_sCAL_MAX_DIGITS+1]; + char sheight[PNG_sCAL_MAX_DIGITS+1]; + + png_ascii_from_fixed(png_ptr, swidth, (sizeof swidth), width); + png_ascii_from_fixed(png_ptr, sheight, (sizeof sheight), height); + + png_set_sCAL_s(png_ptr, info_ptr, unit, swidth, sheight); + } +} +# endif +#endif + +#ifdef PNG_pHYs_SUPPORTED +void PNGAPI +png_set_pHYs(png_const_structrp png_ptr, png_inforp info_ptr, + png_uint_32 res_x, png_uint_32 res_y, int unit_type) +{ + png_debug1(1, "in %s storage function", "pHYs"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + info_ptr->x_pixels_per_unit = res_x; + info_ptr->y_pixels_per_unit = res_y; + info_ptr->phys_unit_type = (png_byte)unit_type; + info_ptr->valid |= PNG_INFO_pHYs; +} +#endif + +void PNGAPI +png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr, + png_const_colorp palette, int num_palette) +{ + + png_debug1(1, "in %s storage function", "PLTE"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (num_palette < 0 || num_palette > PNG_MAX_PALETTE_LENGTH) + { + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_error(png_ptr, "Invalid palette length"); + + else + { + png_warning(png_ptr, "Invalid palette length"); + return; + } + } + + if ((num_palette > 0 && palette == NULL) || + (num_palette == 0 +# ifdef PNG_MNG_FEATURES_SUPPORTED + && (png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) == 0 +# endif + )) + { + png_chunk_report(png_ptr, "Invalid palette", PNG_CHUNK_ERROR); + return; + } + + /* It may not actually be necessary to set png_ptr->palette here; + * we do it for backward compatibility with the way the png_handle_tRNS + * function used to do the allocation. + * + * 1.6.0: the above statement appears to be incorrect; something has to set + * the palette inside png_struct on read. + */ + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0); + + /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead + * of num_palette entries, in case of an invalid PNG file that has + * too-large sample values. + */ + png_ptr->palette = png_voidcast(png_colorp, png_calloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_color)))); + + if (num_palette > 0) + memcpy(png_ptr->palette, palette, num_palette * (sizeof (png_color))); + info_ptr->palette = png_ptr->palette; + info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette; + + info_ptr->free_me |= PNG_FREE_PLTE; + + info_ptr->valid |= PNG_INFO_PLTE; +} + +#ifdef PNG_sBIT_SUPPORTED +void PNGAPI +png_set_sBIT(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_color_8p sig_bit) +{ + png_debug1(1, "in %s storage function", "sBIT"); + + if (png_ptr == NULL || info_ptr == NULL || sig_bit == NULL) + return; + + info_ptr->sig_bit = *sig_bit; + info_ptr->valid |= PNG_INFO_sBIT; +} +#endif + +#ifdef PNG_sRGB_SUPPORTED +void PNGAPI +png_set_sRGB(png_const_structrp png_ptr, png_inforp info_ptr, int srgb_intent) +{ + png_debug1(1, "in %s storage function", "sRGB"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + (void)png_colorspace_set_sRGB(png_ptr, &info_ptr->colorspace, srgb_intent); + png_colorspace_sync_info(png_ptr, info_ptr); +} + +void PNGAPI +png_set_sRGB_gAMA_and_cHRM(png_const_structrp png_ptr, png_inforp info_ptr, + int srgb_intent) +{ + png_debug1(1, "in %s storage function", "sRGB_gAMA_and_cHRM"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (png_colorspace_set_sRGB(png_ptr, &info_ptr->colorspace, srgb_intent)) + { + /* This causes the gAMA and cHRM to be written too */ + info_ptr->colorspace.flags |= + PNG_COLORSPACE_FROM_gAMA|PNG_COLORSPACE_FROM_cHRM; + } + + png_colorspace_sync_info(png_ptr, info_ptr); +} +#endif /* sRGB */ + + +#ifdef PNG_iCCP_SUPPORTED +void PNGAPI +png_set_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_charp name, int compression_type, + png_const_bytep profile, png_uint_32 proflen) +{ + png_charp new_iccp_name; + png_bytep new_iccp_profile; + png_size_t length; + + png_debug1(1, "in %s storage function", "iCCP"); + + if (png_ptr == NULL || info_ptr == NULL || name == NULL || profile == NULL) + return; + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + png_app_error(png_ptr, "Invalid iCCP compression method"); + + /* Set the colorspace first because this validates the profile; do not + * override previously set app cHRM or gAMA here (because likely as not the + * application knows better than libpng what the correct values are.) Pass + * the info_ptr color_type field to png_colorspace_set_ICC because in the + * write case it has not yet been stored in png_ptr. + */ + { + int result = png_colorspace_set_ICC(png_ptr, &info_ptr->colorspace, name, + proflen, profile, info_ptr->color_type); + + png_colorspace_sync_info(png_ptr, info_ptr); + + /* Don't do any of the copying if the profile was bad, or inconsistent. */ + if (!result) + return; + + /* But do write the gAMA and cHRM chunks from the profile. */ + info_ptr->colorspace.flags |= + PNG_COLORSPACE_FROM_gAMA|PNG_COLORSPACE_FROM_cHRM; + } + + length = strlen(name)+1; + new_iccp_name = png_voidcast(png_charp, png_malloc_warn(png_ptr, length)); + + if (new_iccp_name == NULL) + { + png_benign_error(png_ptr, "Insufficient memory to process iCCP chunk"); + return; + } + + memcpy(new_iccp_name, name, length); + new_iccp_profile = png_voidcast(png_bytep, + png_malloc_warn(png_ptr, proflen)); + + if (new_iccp_profile == NULL) + { + png_free(png_ptr, new_iccp_name); + png_benign_error(png_ptr, + "Insufficient memory to process iCCP profile"); + return; + } + + memcpy(new_iccp_profile, profile, proflen); + + png_free_data(png_ptr, info_ptr, PNG_FREE_ICCP, 0); + + info_ptr->iccp_proflen = proflen; + info_ptr->iccp_name = new_iccp_name; + info_ptr->iccp_profile = new_iccp_profile; + info_ptr->free_me |= PNG_FREE_ICCP; + info_ptr->valid |= PNG_INFO_iCCP; +} +#endif + +#ifdef PNG_TEXT_SUPPORTED +void PNGAPI +png_set_text(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_textp text_ptr, int num_text) +{ + int ret; + ret = png_set_text_2(png_ptr, info_ptr, text_ptr, num_text); + + if (ret) + png_error(png_ptr, "Insufficient memory to store text"); +} + +int /* PRIVATE */ +png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_textp text_ptr, int num_text) +{ + int i; + + png_debug1(1, "in %lx storage function", png_ptr == NULL ? "unexpected" : + (unsigned long)png_ptr->chunk_name); + + if (png_ptr == NULL || info_ptr == NULL || num_text <= 0 || text_ptr == NULL) + return(0); + + /* Make sure we have enough space in the "text" array in info_struct + * to hold all of the incoming text_ptr objects. This compare can't overflow + * because max_text >= num_text (anyway, subtract of two positive integers + * can't overflow in any case.) + */ + if (num_text > info_ptr->max_text - info_ptr->num_text) + { + int old_num_text = info_ptr->num_text; + int max_text; + png_textp new_text = NULL; + + /* Calculate an appropriate max_text, checking for overflow. */ + max_text = old_num_text; + if (num_text <= INT_MAX - max_text) + { + max_text += num_text; + + /* Round up to a multiple of 8 */ + if (max_text < INT_MAX-8) + max_text = (max_text + 8) & ~0x7; + + else + max_text = INT_MAX; + + /* Now allocate a new array and copy the old members in, this does all + * the overflow checks. + */ + new_text = png_voidcast(png_textp,png_realloc_array(png_ptr, + info_ptr->text, old_num_text, max_text-old_num_text, + sizeof *new_text)); + } + + if (new_text == NULL) + { + png_chunk_report(png_ptr, "too many text chunks", + PNG_CHUNK_WRITE_ERROR); + return 1; + } + + png_free(png_ptr, info_ptr->text); + + info_ptr->text = new_text; + info_ptr->free_me |= PNG_FREE_TEXT; + info_ptr->max_text = max_text; + /* num_text is adjusted below as the entries are copied in */ + + png_debug1(3, "allocated %d entries for info_ptr->text", max_text); + } + + for (i = 0; i < num_text; i++) + { + size_t text_length, key_len; + size_t lang_len, lang_key_len; + png_textp textp = &(info_ptr->text[info_ptr->num_text]); + + if (text_ptr[i].key == NULL) + continue; + + if (text_ptr[i].compression < PNG_TEXT_COMPRESSION_NONE || + text_ptr[i].compression >= PNG_TEXT_COMPRESSION_LAST) + { + png_chunk_report(png_ptr, "text compression mode is out of range", + PNG_CHUNK_WRITE_ERROR); + continue; + } + + key_len = strlen(text_ptr[i].key); + + if (text_ptr[i].compression <= 0) + { + lang_len = 0; + lang_key_len = 0; + } + + else +# ifdef PNG_iTXt_SUPPORTED + { + /* Set iTXt data */ + + if (text_ptr[i].lang != NULL) + lang_len = strlen(text_ptr[i].lang); + + else + lang_len = 0; + + if (text_ptr[i].lang_key != NULL) + lang_key_len = strlen(text_ptr[i].lang_key); + + else + lang_key_len = 0; + } +# else /* PNG_iTXt_SUPPORTED */ + { + png_chunk_report(png_ptr, "iTXt chunk not supported", + PNG_CHUNK_WRITE_ERROR); + continue; + } +# endif + + if (text_ptr[i].text == NULL || text_ptr[i].text[0] == '\0') + { + text_length = 0; +# ifdef PNG_iTXt_SUPPORTED + if (text_ptr[i].compression > 0) + textp->compression = PNG_ITXT_COMPRESSION_NONE; + + else +# endif + textp->compression = PNG_TEXT_COMPRESSION_NONE; + } + + else + { + text_length = strlen(text_ptr[i].text); + textp->compression = text_ptr[i].compression; + } + + textp->key = png_voidcast(png_charp,png_malloc_base(png_ptr, + key_len + text_length + lang_len + lang_key_len + 4)); + + if (textp->key == NULL) + { + png_chunk_report(png_ptr, "text chunk: out of memory", + PNG_CHUNK_WRITE_ERROR); + return 1; + } + + png_debug2(2, "Allocated %lu bytes at %p in png_set_text", + (unsigned long)(png_uint_32) + (key_len + lang_len + lang_key_len + text_length + 4), + textp->key); + + memcpy(textp->key, text_ptr[i].key, key_len); + *(textp->key + key_len) = '\0'; + + if (text_ptr[i].compression > 0) + { + textp->lang = textp->key + key_len + 1; + memcpy(textp->lang, text_ptr[i].lang, lang_len); + *(textp->lang + lang_len) = '\0'; + textp->lang_key = textp->lang + lang_len + 1; + memcpy(textp->lang_key, text_ptr[i].lang_key, lang_key_len); + *(textp->lang_key + lang_key_len) = '\0'; + textp->text = textp->lang_key + lang_key_len + 1; + } + + else + { + textp->lang=NULL; + textp->lang_key=NULL; + textp->text = textp->key + key_len + 1; + } + + if (text_length) + memcpy(textp->text, text_ptr[i].text, text_length); + + *(textp->text + text_length) = '\0'; + +# ifdef PNG_iTXt_SUPPORTED + if (textp->compression > 0) + { + textp->text_length = 0; + textp->itxt_length = text_length; + } + + else +# endif + { + textp->text_length = text_length; + textp->itxt_length = 0; + } + + info_ptr->num_text++; + png_debug1(3, "transferred text chunk %d", info_ptr->num_text); + } + + return(0); +} +#endif + +#ifdef PNG_tIME_SUPPORTED +void PNGAPI +png_set_tIME(png_const_structrp png_ptr, png_inforp info_ptr, + png_const_timep mod_time) +{ + png_debug1(1, "in %s storage function", "tIME"); + + if (png_ptr == NULL || info_ptr == NULL || mod_time == NULL || + (png_ptr->mode & PNG_WROTE_tIME)) + return; + + if (mod_time->month == 0 || mod_time->month > 12 || + mod_time->day == 0 || mod_time->day > 31 || + mod_time->hour > 23 || mod_time->minute > 59 || + mod_time->second > 60) + { + png_warning(png_ptr, "Ignoring invalid time value"); + return; + } + + info_ptr->mod_time = *mod_time; + info_ptr->valid |= PNG_INFO_tIME; +} +#endif + +#ifdef PNG_tRNS_SUPPORTED +void PNGAPI +png_set_tRNS(png_structrp png_ptr, png_inforp info_ptr, + png_const_bytep trans_alpha, int num_trans, png_const_color_16p trans_color) +{ + png_debug1(1, "in %s storage function", "tRNS"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (trans_alpha != NULL) + { + /* It may not actually be necessary to set png_ptr->trans_alpha here; + * we do it for backward compatibility with the way the png_handle_tRNS + * function used to do the allocation. + * + * 1.6.0: The above statement is incorrect; png_handle_tRNS effectively + * relies on png_set_tRNS storing the information in png_struct + * (otherwise it won't be there for the code in pngrtran.c). + */ + + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); + + /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */ + png_ptr->trans_alpha = info_ptr->trans_alpha = png_voidcast(png_bytep, + png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH)); + + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + memcpy(info_ptr->trans_alpha, trans_alpha, (png_size_t)num_trans); + } + + if (trans_color != NULL) + { + int sample_max = (1 << info_ptr->bit_depth); + + if ((info_ptr->color_type == PNG_COLOR_TYPE_GRAY && + trans_color->gray > sample_max) || + (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + (trans_color->red > sample_max || + trans_color->green > sample_max || + trans_color->blue > sample_max))) + png_warning(png_ptr, + "tRNS chunk has out-of-range samples for bit_depth"); + + info_ptr->trans_color = *trans_color; + + if (num_trans == 0) + num_trans = 1; + } + + info_ptr->num_trans = (png_uint_16)num_trans; + + if (num_trans != 0) + { + info_ptr->valid |= PNG_INFO_tRNS; + info_ptr->free_me |= PNG_FREE_TRNS; + } +} +#endif + +#ifdef PNG_sPLT_SUPPORTED +void PNGAPI +png_set_sPLT(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_sPLT_tp entries, int nentries) +/* + * entries - array of png_sPLT_t structures + * to be added to the list of palettes + * in the info structure. + * + * nentries - number of palette structures to be + * added. + */ +{ + png_sPLT_tp np; + + if (png_ptr == NULL || info_ptr == NULL || nentries <= 0 || entries == NULL) + return; + + /* Use the internal realloc function, which checks for all the possible + * overflows. Notice that the parameters are (int) and (size_t) + */ + np = png_voidcast(png_sPLT_tp,png_realloc_array(png_ptr, + info_ptr->splt_palettes, info_ptr->splt_palettes_num, nentries, + sizeof *np)); + + if (np == NULL) + { + /* Out of memory or too many chunks */ + png_chunk_report(png_ptr, "too many sPLT chunks", PNG_CHUNK_WRITE_ERROR); + return; + } + + png_free(png_ptr, info_ptr->splt_palettes); + info_ptr->splt_palettes = np; + info_ptr->free_me |= PNG_FREE_SPLT; + + np += info_ptr->splt_palettes_num; + + do + { + png_size_t length; + + /* Skip invalid input entries */ + if (entries->name == NULL || entries->entries == NULL) + { + /* png_handle_sPLT doesn't do this, so this is an app error */ + png_app_error(png_ptr, "png_set_sPLT: invalid sPLT"); + /* Just skip the invalid entry */ + continue; + } + + np->depth = entries->depth; + + /* In the even of out-of-memory just return - there's no point keeping on + * trying to add sPLT chunks. + */ + length = strlen(entries->name) + 1; + np->name = png_voidcast(png_charp, png_malloc_base(png_ptr, length)); + + if (np->name == NULL) + break; + + memcpy(np->name, entries->name, length); + + /* IMPORTANT: we have memory now that won't get freed if something else + * goes wrong, this code must free it. png_malloc_array produces no + * warnings, use a png_chunk_report (below) if there is an error. + */ + np->entries = png_voidcast(png_sPLT_entryp, png_malloc_array(png_ptr, + entries->nentries, sizeof (png_sPLT_entry))); + + if (np->entries == NULL) + { + png_free(png_ptr, np->name); + break; + } + + np->nentries = entries->nentries; + /* This multiply can't overflow because png_malloc_array has already + * checked it when doing the allocation. + */ + memcpy(np->entries, entries->entries, + entries->nentries * sizeof (png_sPLT_entry)); + + /* Note that 'continue' skips the advance of the out pointer and out + * count, so an invalid entry is not added. + */ + info_ptr->valid |= PNG_INFO_sPLT; + ++(info_ptr->splt_palettes_num); + ++np; + } + while (++entries, --nentries); + + if (nentries > 0) + png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR); +} +#endif /* PNG_sPLT_SUPPORTED */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +static png_byte +check_location(png_const_structrp png_ptr, int location) +{ + location &= (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT); + + /* New in 1.6.0; copy the location and check it. This is an API + * change, previously the app had to use the + * png_set_unknown_chunk_location API below for each chunk. + */ + if (location == 0 && !(png_ptr->mode & PNG_IS_READ_STRUCT)) + { + /* Write struct, so unknown chunks come from the app */ + png_app_warning(png_ptr, + "png_set_unknown_chunks now expects a valid location"); + /* Use the old behavior */ + location = (png_byte)(png_ptr->mode & + (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT)); + } + + /* This need not be an internal error - if the app calls + * png_set_unknown_chunks on a read pointer it must get the location right. + */ + if (location == 0) + png_error(png_ptr, "invalid location in png_set_unknown_chunks"); + + /* Now reduce the location to the top-most set bit by removing each least + * significant bit in turn. + */ + while (location != (location & -location)) + location &= ~(location & -location); + + /* The cast is safe because 'location' is a bit mask and only the low four + * bits are significant. + */ + return (png_byte)location; +} + +void PNGAPI +png_set_unknown_chunks(png_const_structrp png_ptr, + png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns) +{ + png_unknown_chunkp np; + + if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 || + unknowns == NULL) + return; + + /* Check for the failure cases where support has been disabled at compile + * time. This code is hardly ever compiled - it's here because + * STORE_UNKNOWN_CHUNKS is set by both read and write code (compiling in this + * code) but may be meaningless if the read or write handling of unknown + * chunks is not compiled in. + */ +# if !defined(PNG_READ_UNKNOWN_CHUNKS_SUPPORTED) && \ + defined(PNG_READ_SUPPORTED) + if (png_ptr->mode & PNG_IS_READ_STRUCT) + { + png_app_error(png_ptr, "no unknown chunk support on read"); + return; + } +# endif +# if !defined(PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED) && \ + defined(PNG_WRITE_SUPPORTED) + if (!(png_ptr->mode & PNG_IS_READ_STRUCT)) + { + png_app_error(png_ptr, "no unknown chunk support on write"); + return; + } +# endif + + /* Prior to 1.6.0 this code used png_malloc_warn; however, this meant that + * unknown critical chunks could be lost with just a warning resulting in + * undefined behavior. Now png_chunk_report is used to provide behavior + * appropriate to read or write. + */ + np = png_voidcast(png_unknown_chunkp, png_realloc_array(png_ptr, + info_ptr->unknown_chunks, info_ptr->unknown_chunks_num, num_unknowns, + sizeof *np)); + + if (np == NULL) + { + png_chunk_report(png_ptr, "too many unknown chunks", + PNG_CHUNK_WRITE_ERROR); + return; + } + + png_free(png_ptr, info_ptr->unknown_chunks); + info_ptr->unknown_chunks = np; /* safe because it is initialized */ + info_ptr->free_me |= PNG_FREE_UNKN; + + np += info_ptr->unknown_chunks_num; + + /* Increment unknown_chunks_num each time round the loop to protect the + * just-allocated chunk data. + */ + for (; num_unknowns > 0; --num_unknowns, ++unknowns) + { + memcpy(np->name, unknowns->name, (sizeof np->name)); + np->name[(sizeof np->name)-1] = '\0'; + np->location = check_location(png_ptr, unknowns->location); + + if (unknowns->size == 0) + { + np->data = NULL; + np->size = 0; + } + + else + { + np->data = png_voidcast(png_bytep, + png_malloc_base(png_ptr, unknowns->size)); + + if (np->data == NULL) + { + png_chunk_report(png_ptr, "unknown chunk: out of memory", + PNG_CHUNK_WRITE_ERROR); + /* But just skip storing the unknown chunk */ + continue; + } + + memcpy(np->data, unknowns->data, unknowns->size); + np->size = unknowns->size; + } + + /* These increments are skipped on out-of-memory for the data - the + * unknown chunk entry gets overwritten if the png_chunk_report returns. + * This is correct in the read case (the chunk is just dropped.) + */ + ++np; + ++(info_ptr->unknown_chunks_num); + } +} + +void PNGAPI +png_set_unknown_chunk_location(png_const_structrp png_ptr, png_inforp info_ptr, + int chunk, int location) +{ + /* This API is pretty pointless in 1.6.0 because the location can be set + * before the call to png_set_unknown_chunks. + * + * TODO: add a png_app_warning in 1.7 + */ + if (png_ptr != NULL && info_ptr != NULL && chunk >= 0 && + chunk < info_ptr->unknown_chunks_num) + { + if ((location & (PNG_HAVE_IHDR|PNG_HAVE_PLTE|PNG_AFTER_IDAT)) == 0) + { + png_app_error(png_ptr, "invalid unknown chunk location"); + /* Fake out the pre 1.6.0 behavior: */ + if ((location & PNG_HAVE_IDAT)) /* undocumented! */ + location = PNG_AFTER_IDAT; + + else + location = PNG_HAVE_IHDR; /* also undocumented */ + } + + info_ptr->unknown_chunks[chunk].location = + check_location(png_ptr, location); + } +} +#endif + + +#ifdef PNG_MNG_FEATURES_SUPPORTED +png_uint_32 PNGAPI +png_permit_mng_features (png_structrp png_ptr, png_uint_32 mng_features) +{ + png_debug(1, "in png_permit_mng_features"); + + if (png_ptr == NULL) + return 0; + + png_ptr->mng_features_permitted = mng_features & PNG_ALL_MNG_FEATURES; + + return png_ptr->mng_features_permitted; +} +#endif + +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static unsigned int +add_one_chunk(png_bytep list, unsigned int count, png_const_bytep add, int keep) +{ + unsigned int i; + + /* Utility function: update the 'keep' state of a chunk if it is already in + * the list, otherwise add it to the list. + */ + for (i=0; i= PNG_HANDLE_CHUNK_LAST) + { + png_app_error(png_ptr, "png_set_keep_unknown_chunks: invalid keep"); + return; + } + + if (num_chunks_in <= 0) + { + png_ptr->unknown_default = keep; + + /* '0' means just set the flags, so stop here */ + if (num_chunks_in == 0) + return; + } + + if (num_chunks_in < 0) + { + /* Ignore all unknown chunks and all chunks recognized by + * libpng except for IHDR, PLTE, tRNS, IDAT, and IEND + */ + static PNG_CONST png_byte chunks_to_ignore[] = { + 98, 75, 71, 68, '\0', /* bKGD */ + 99, 72, 82, 77, '\0', /* cHRM */ + 103, 65, 77, 65, '\0', /* gAMA */ + 104, 73, 83, 84, '\0', /* hIST */ + 105, 67, 67, 80, '\0', /* iCCP */ + 105, 84, 88, 116, '\0', /* iTXt */ + 111, 70, 70, 115, '\0', /* oFFs */ + 112, 67, 65, 76, '\0', /* pCAL */ + 112, 72, 89, 115, '\0', /* pHYs */ + 115, 66, 73, 84, '\0', /* sBIT */ + 115, 67, 65, 76, '\0', /* sCAL */ + 115, 80, 76, 84, '\0', /* sPLT */ + 115, 84, 69, 82, '\0', /* sTER */ + 115, 82, 71, 66, '\0', /* sRGB */ + 116, 69, 88, 116, '\0', /* tEXt */ + 116, 73, 77, 69, '\0', /* tIME */ + 122, 84, 88, 116, '\0' /* zTXt */ + }; + + chunk_list = chunks_to_ignore; + num_chunks = (sizeof chunks_to_ignore)/5; + } + + else /* num_chunks_in > 0 */ + { + if (chunk_list == NULL) + { + /* Prior to 1.6.0 this was silently ignored, now it is an app_error + * which can be switched off. + */ + png_app_error(png_ptr, "png_set_keep_unknown_chunks: no chunk list"); + return; + } + + num_chunks = num_chunks_in; + } + + old_num_chunks = png_ptr->num_chunk_list; + if (png_ptr->chunk_list == NULL) + old_num_chunks = 0; + + /* Since num_chunks is always restricted to UINT_MAX/5 this can't overflow. + */ + if (num_chunks + old_num_chunks > UINT_MAX/5) + { + png_app_error(png_ptr, "png_set_keep_unknown_chunks: too many chunks"); + return; + } + + /* If these chunks are being reset to the default then no more memory is + * required because add_one_chunk above doesn't extend the list if the 'keep' + * parameter is the default. + */ + if (keep) + { + new_list = png_voidcast(png_bytep, png_malloc(png_ptr, + 5 * (num_chunks + old_num_chunks))); + + if (old_num_chunks > 0) + memcpy(new_list, png_ptr->chunk_list, 5*old_num_chunks); + } + + else if (old_num_chunks > 0) + new_list = png_ptr->chunk_list; + + else + new_list = NULL; + + /* Add the new chunks together with each one's handling code. If the chunk + * already exists the code is updated, otherwise the chunk is added to the + * end. (In libpng 1.6.0 order no longer matters because this code enforces + * the earlier convention that the last setting is the one that is used.) + */ + if (new_list != NULL) + { + png_const_bytep inlist; + png_bytep outlist; + unsigned int i; + + for (i=0; ichunk_list != new_list) + png_free(png_ptr, new_list); + + new_list = NULL; + } + } + + else + num_chunks = 0; + + png_ptr->num_chunk_list = num_chunks; + + if (png_ptr->chunk_list != new_list) + { + if (png_ptr->chunk_list != NULL) + png_free(png_ptr, png_ptr->chunk_list); + + png_ptr->chunk_list = new_list; + } +} +#endif + +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED +void PNGAPI +png_set_read_user_chunk_fn(png_structrp png_ptr, png_voidp user_chunk_ptr, + png_user_chunk_ptr read_user_chunk_fn) +{ + png_debug(1, "in png_set_read_user_chunk_fn"); + + if (png_ptr == NULL) + return; + + png_ptr->read_user_chunk_fn = read_user_chunk_fn; + png_ptr->user_chunk_ptr = user_chunk_ptr; +} +#endif + +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_set_rows(png_const_structrp png_ptr, png_inforp info_ptr, + png_bytepp row_pointers) +{ + png_debug1(1, "in %s storage function", "rows"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (info_ptr->row_pointers && (info_ptr->row_pointers != row_pointers)) + png_free_data(png_ptr, info_ptr, PNG_FREE_ROWS, 0); + + info_ptr->row_pointers = row_pointers; + + if (row_pointers) + info_ptr->valid |= PNG_INFO_IDAT; +} +#endif + +void PNGAPI +png_set_compression_buffer_size(png_structrp png_ptr, png_size_t size) +{ + if (png_ptr == NULL) + return; + + if (size == 0 || size > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid compression buffer size"); + +# ifdef PNG_SEQUENTIAL_READ_SUPPORTED + if (png_ptr->mode & PNG_IS_READ_STRUCT) + { + png_ptr->IDAT_read_size = (png_uint_32)size; /* checked above */ + return; + } +# endif + +# ifdef PNG_WRITE_SUPPORTED + if (!(png_ptr->mode & PNG_IS_READ_STRUCT)) + { + if (png_ptr->zowner != 0) + { + png_warning(png_ptr, + "Compression buffer size cannot be changed because it is in use"); + return; + } + + if (size > ZLIB_IO_MAX) + { + png_warning(png_ptr, + "Compression buffer size limited to system maximum"); + size = ZLIB_IO_MAX; /* must fit */ + } + + else if (size < 6) + { + /* Deflate will potentially go into an infinite loop on a SYNC_FLUSH + * if this is permitted. + */ + png_warning(png_ptr, + "Compression buffer size cannot be reduced below 6"); + return; + } + + if (png_ptr->zbuffer_size != size) + { + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list); + png_ptr->zbuffer_size = (uInt)size; + } + } +# endif +} + +void PNGAPI +png_set_invalid(png_const_structrp png_ptr, png_inforp info_ptr, int mask) +{ + if (png_ptr && info_ptr) + info_ptr->valid &= ~mask; +} + + +#ifdef PNG_SET_USER_LIMITS_SUPPORTED +/* This function was added to libpng 1.2.6 */ +void PNGAPI +png_set_user_limits (png_structrp png_ptr, png_uint_32 user_width_max, + png_uint_32 user_height_max) +{ + /* Images with dimensions larger than these limits will be + * rejected by png_set_IHDR(). To accept any PNG datastream + * regardless of dimensions, set both limits to 0x7ffffffL. + */ + if (png_ptr == NULL) + return; + + png_ptr->user_width_max = user_width_max; + png_ptr->user_height_max = user_height_max; +} + +/* This function was added to libpng 1.4.0 */ +void PNGAPI +png_set_chunk_cache_max (png_structrp png_ptr, png_uint_32 user_chunk_cache_max) +{ + if (png_ptr) + png_ptr->user_chunk_cache_max = user_chunk_cache_max; +} + +/* This function was added to libpng 1.4.1 */ +void PNGAPI +png_set_chunk_malloc_max (png_structrp png_ptr, + png_alloc_size_t user_chunk_malloc_max) +{ + if (png_ptr) + png_ptr->user_chunk_malloc_max = user_chunk_malloc_max; +} +#endif /* ?PNG_SET_USER_LIMITS_SUPPORTED */ + + +#ifdef PNG_BENIGN_ERRORS_SUPPORTED +void PNGAPI +png_set_benign_errors(png_structrp png_ptr, int allowed) +{ + png_debug(1, "in png_set_benign_errors"); + + /* If allowed is 1, png_benign_error() is treated as a warning. + * + * If allowed is 0, png_benign_error() is treated as an error (which + * is the default behavior if png_set_benign_errors() is not called). + */ + + if (allowed) + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN | + PNG_FLAG_APP_WARNINGS_WARN | PNG_FLAG_APP_ERRORS_WARN; + + else + png_ptr->flags &= ~(PNG_FLAG_BENIGN_ERRORS_WARN | + PNG_FLAG_APP_WARNINGS_WARN | PNG_FLAG_APP_ERRORS_WARN); +} +#endif /* PNG_BENIGN_ERRORS_SUPPORTED */ + +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Whether to report invalid palette index; added at libng-1.5.10. + * It is possible for an indexed (color-type==3) PNG file to contain + * pixels with invalid (out-of-range) indexes if the PLTE chunk has + * fewer entries than the image's bit-depth would allow. We recover + * from this gracefully by filling any incomplete palette with zeroes + * (opaque black). By default, when this occurs libpng will issue + * a benign error. This API can be used to override that behavior. + */ +void PNGAPI +png_set_check_for_invalid_index(png_structrp png_ptr, int allowed) +{ + png_debug(1, "in png_set_check_for_invalid_index"); + + if (allowed > 0) + png_ptr->num_palette_max = 0; + + else + png_ptr->num_palette_max = -1; +} +#endif +#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngstruct.h b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngstruct.h new file mode 100644 index 0000000..89605b1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngstruct.h @@ -0,0 +1,489 @@ + +/* pngstruct.h - header file for PNG reference library + * + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +/* The structure that holds the information to read and write PNG files. + * The only people who need to care about what is inside of this are the + * people who will be modifying the library for their own special needs. + * It should NOT be accessed directly by an application. + */ + +#ifndef PNGSTRUCT_H +#define PNGSTRUCT_H +/* zlib.h defines the structure z_stream, an instance of which is included + * in this structure and is required for decompressing the LZ compressed + * data in PNG files. + */ +#ifndef ZLIB_CONST + /* We must ensure that zlib uses 'const' in declarations. */ +# define ZLIB_CONST +#endif +#include "../../../juce_core/zip/zlib/zlib.h" +#ifdef const + /* zlib.h sometimes #defines const to nothing, undo this. */ +# undef const +#endif + +/* zlib.h has mediocre z_const use before 1.2.6, this stuff is for compatibility + * with older builds. + */ +#if ZLIB_VERNUM < 0x1260 +# define PNGZ_MSG_CAST(s) png_constcast(char*,s) +# define PNGZ_INPUT_CAST(b) png_constcast(png_bytep,b) +#else +# define PNGZ_MSG_CAST(s) (s) +# define PNGZ_INPUT_CAST(b) (b) +#endif + +/* zlib.h declares a magic type 'uInt' that limits the amount of data that zlib + * can handle at once. This type need be no larger than 16 bits (so maximum of + * 65535), this define allows us to discover how big it is, but limited by the + * maximuum for png_size_t. The value can be overriden in a library build + * (pngusr.h, or set it in CPPFLAGS) and it works to set it to a considerably + * lower value (e.g. 255 works). A lower value may help memory usage (slightly) + * and may even improve performance on some systems (and degrade it on others.) + */ +#ifndef ZLIB_IO_MAX +# define ZLIB_IO_MAX ((uInt)-1) +#endif + +#ifdef PNG_WRITE_SUPPORTED +/* The type of a compression buffer list used by the write code. */ +typedef struct png_compression_buffer +{ + struct png_compression_buffer *next; + png_byte output[1]; /* actually zbuf_size */ +} png_compression_buffer, *png_compression_bufferp; + +#define PNG_COMPRESSION_BUFFER_SIZE(pp)\ + (offsetof(png_compression_buffer, output) + (pp)->zbuffer_size) +#endif + +/* Colorspace support; structures used in png_struct, png_info and in internal + * functions to hold and communicate information about the color space. + * + * PNG_COLORSPACE_SUPPORTED is only required if the application will perform + * colorspace corrections, otherwise all the colorspace information can be + * skipped and the size of libpng can be reduced (significantly) by compiling + * out the colorspace support. + */ +#ifdef PNG_COLORSPACE_SUPPORTED +/* The chromaticities of the red, green and blue colorants and the chromaticity + * of the corresponding white point (i.e. of rgb(1.0,1.0,1.0)). + */ +typedef struct png_xy +{ + png_fixed_point redx, redy; + png_fixed_point greenx, greeny; + png_fixed_point bluex, bluey; + png_fixed_point whitex, whitey; +} png_xy; + +/* The same data as above but encoded as CIE XYZ values. When this data comes + * from chromaticities the sum of the Y values is assumed to be 1.0 + */ +typedef struct png_XYZ +{ + png_fixed_point red_X, red_Y, red_Z; + png_fixed_point green_X, green_Y, green_Z; + png_fixed_point blue_X, blue_Y, blue_Z; +} png_XYZ; +#endif /* COLORSPACE */ + +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) +/* A colorspace is all the above plus, potentially, profile information, + * however at present libpng does not use the profile internally so it is only + * stored in the png_info struct (if iCCP is supported.) The rendering intent + * is retained here and is checked. + * + * The file gamma encoding information is also stored here and gamma correction + * is done by libpng, whereas color correction must currently be done by the + * application. + */ +typedef struct png_colorspace +{ +#ifdef PNG_GAMMA_SUPPORTED + png_fixed_point gamma; /* File gamma */ +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED + png_xy end_points_xy; /* End points as chromaticities */ + png_XYZ end_points_XYZ; /* End points as CIE XYZ colorant values */ + png_uint_16 rendering_intent; /* Rendering intent of a profile */ +#endif + + /* Flags are always defined to simplify the code. */ + png_uint_16 flags; /* As defined below */ +} png_colorspace, * PNG_RESTRICT png_colorspacerp; + +typedef const png_colorspace * PNG_RESTRICT png_const_colorspacerp; + +/* General flags for the 'flags' field */ +#define PNG_COLORSPACE_HAVE_GAMMA 0x0001 +#define PNG_COLORSPACE_HAVE_ENDPOINTS 0x0002 +#define PNG_COLORSPACE_HAVE_INTENT 0x0004 +#define PNG_COLORSPACE_FROM_gAMA 0x0008 +#define PNG_COLORSPACE_FROM_cHRM 0x0010 +#define PNG_COLORSPACE_FROM_sRGB 0x0020 +#define PNG_COLORSPACE_ENDPOINTS_MATCH_sRGB 0x0040 +#define PNG_COLORSPACE_MATCHES_sRGB 0x0080 /* exact match on profile */ +#define PNG_COLORSPACE_INVALID 0x8000 +#define PNG_COLORSPACE_CANCEL(flags) (0xffff ^ (flags)) +#endif /* COLORSPACE || GAMMA */ + +struct png_struct_def +{ +#ifdef PNG_SETJMP_SUPPORTED + jmp_buf jmp_buf_local; /* New name in 1.6.0 for jmp_buf in png_struct */ + png_longjmp_ptr longjmp_fn;/* setjmp non-local goto function. */ + jmp_buf *jmp_buf_ptr; /* passed to longjmp_fn */ + size_t jmp_buf_size; /* size of the above, if allocated */ +#endif + png_error_ptr error_fn; /* function for printing errors and aborting */ +#ifdef PNG_WARNINGS_SUPPORTED + png_error_ptr warning_fn; /* function for printing warnings */ +#endif + png_voidp error_ptr; /* user supplied struct for error functions */ + png_rw_ptr write_data_fn; /* function for writing output data */ + png_rw_ptr read_data_fn; /* function for reading input data */ + png_voidp io_ptr; /* ptr to application struct for I/O functions */ + +#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED + png_user_transform_ptr read_user_transform_fn; /* user read transform */ +#endif + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + png_user_transform_ptr write_user_transform_fn; /* user write transform */ +#endif + +/* These were added in libpng-1.0.2 */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) + png_voidp user_transform_ptr; /* user supplied struct for user transform */ + png_byte user_transform_depth; /* bit depth of user transformed pixels */ + png_byte user_transform_channels; /* channels in user transformed pixels */ +#endif +#endif + + png_uint_32 mode; /* tells us where we are in the PNG file */ + png_uint_32 flags; /* flags indicating various things to libpng */ + png_uint_32 transformations; /* which transformations to perform */ + + png_uint_32 zowner; /* ID (chunk type) of zstream owner, 0 if none */ + z_stream zstream; /* decompression structure */ + +#ifdef PNG_WRITE_SUPPORTED + png_compression_bufferp zbuffer_list; /* Created on demand during write */ + uInt zbuffer_size; /* size of the actual buffer */ + + int zlib_level; /* holds zlib compression level */ + int zlib_method; /* holds zlib compression method */ + int zlib_window_bits; /* holds zlib compression window bits */ + int zlib_mem_level; /* holds zlib compression memory level */ + int zlib_strategy; /* holds zlib compression strategy */ +#endif +/* Added at libpng 1.5.4 */ +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + int zlib_text_level; /* holds zlib compression level */ + int zlib_text_method; /* holds zlib compression method */ + int zlib_text_window_bits; /* holds zlib compression window bits */ + int zlib_text_mem_level; /* holds zlib compression memory level */ + int zlib_text_strategy; /* holds zlib compression strategy */ +#endif +/* End of material added at libpng 1.5.4 */ +/* Added at libpng 1.6.0 */ +#ifdef PNG_WRITE_SUPPORTED + int zlib_set_level; /* Actual values set into the zstream on write */ + int zlib_set_method; + int zlib_set_window_bits; + int zlib_set_mem_level; + int zlib_set_strategy; +#endif + + png_uint_32 width; /* width of image in pixels */ + png_uint_32 height; /* height of image in pixels */ + png_uint_32 num_rows; /* number of rows in current pass */ + png_uint_32 usr_width; /* width of row at start of write */ + png_size_t rowbytes; /* size of row in bytes */ + png_uint_32 iwidth; /* width of current interlaced row in pixels */ + png_uint_32 row_number; /* current row in interlace pass */ + png_uint_32 chunk_name; /* PNG_CHUNK() id of current chunk */ + png_bytep prev_row; /* buffer to save previous (unfiltered) row. + * This is a pointer into big_prev_row + */ + png_bytep row_buf; /* buffer to save current (unfiltered) row. + * This is a pointer into big_row_buf + */ +#ifdef PNG_WRITE_SUPPORTED + png_bytep sub_row; /* buffer to save "sub" row when filtering */ + png_bytep up_row; /* buffer to save "up" row when filtering */ + png_bytep avg_row; /* buffer to save "avg" row when filtering */ + png_bytep paeth_row; /* buffer to save "Paeth" row when filtering */ +#endif + png_size_t info_rowbytes; /* Added in 1.5.4: cache of updated row bytes */ + + png_uint_32 idat_size; /* current IDAT size for read */ + png_uint_32 crc; /* current chunk CRC value */ + png_colorp palette; /* palette from the input file */ + png_uint_16 num_palette; /* number of color entries in palette */ + +/* Added at libpng-1.5.10 */ +#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED + int num_palette_max; /* maximum palette index found in IDAT */ +#endif + + png_uint_16 num_trans; /* number of transparency values */ + png_byte compression; /* file compression type (always 0) */ + png_byte filter; /* file filter type (always 0) */ + png_byte interlaced; /* PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */ + png_byte pass; /* current interlace pass (0 - 6) */ + png_byte do_filter; /* row filter flags (see PNG_FILTER_ below ) */ + png_byte color_type; /* color type of file */ + png_byte bit_depth; /* bit depth of file */ + png_byte usr_bit_depth; /* bit depth of users row: write only */ + png_byte pixel_depth; /* number of bits per pixel */ + png_byte channels; /* number of channels in file */ +#ifdef PNG_WRITE_SUPPORTED + png_byte usr_channels; /* channels at start of write: write only */ +#endif + png_byte sig_bytes; /* magic bytes read/written from start of file */ + png_byte maximum_pixel_depth; + /* pixel depth used for the row buffers */ + png_byte transformed_pixel_depth; + /* pixel depth after read/write transforms */ +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) + png_uint_16 filler; /* filler bytes for pixel expansion */ +#endif + +#if defined(PNG_bKGD_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) ||\ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) + png_byte background_gamma_type; + png_fixed_point background_gamma; + png_color_16 background; /* background color in screen gamma space */ +#ifdef PNG_READ_GAMMA_SUPPORTED + png_color_16 background_1; /* background normalized to gamma 1.0 */ +#endif +#endif /* PNG_bKGD_SUPPORTED */ + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_flush_ptr output_flush_fn; /* Function for flushing output */ + png_uint_32 flush_dist; /* how many rows apart to flush, 0 - no flush */ + png_uint_32 flush_rows; /* number of rows written since last flush */ +#endif + +#ifdef PNG_READ_GAMMA_SUPPORTED + int gamma_shift; /* number of "insignificant" bits in 16-bit gamma */ + png_fixed_point screen_gamma; /* screen gamma value (display_exponent) */ + + png_bytep gamma_table; /* gamma table for 8-bit depth files */ + png_uint_16pp gamma_16_table; /* gamma table for 16-bit depth files */ +#if defined(PNG_READ_BACKGROUND_SUPPORTED) || \ + defined(PNG_READ_ALPHA_MODE_SUPPORTED) || \ + defined(PNG_READ_RGB_TO_GRAY_SUPPORTED) + png_bytep gamma_from_1; /* converts from 1.0 to screen */ + png_bytep gamma_to_1; /* converts from file to 1.0 */ + png_uint_16pp gamma_16_from_1; /* converts from 1.0 to screen */ + png_uint_16pp gamma_16_to_1; /* converts from file to 1.0 */ +#endif /* READ_BACKGROUND || READ_ALPHA_MODE || RGB_TO_GRAY */ +#endif + +#if defined(PNG_READ_GAMMA_SUPPORTED) || defined(PNG_sBIT_SUPPORTED) + png_color_8 sig_bit; /* significant bits in each available channel */ +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) + png_color_8 shift; /* shift for significant bit tranformation */ +#endif + +#if defined(PNG_tRNS_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) \ + || defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + png_bytep trans_alpha; /* alpha values for paletted files */ + png_color_16 trans_color; /* transparent color for non-paletted files */ +#endif + + png_read_status_ptr read_row_fn; /* called after each row is decoded */ + png_write_status_ptr write_row_fn; /* called after each row is encoded */ +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + png_progressive_info_ptr info_fn; /* called after header data fully read */ + png_progressive_row_ptr row_fn; /* called after a prog. row is decoded */ + png_progressive_end_ptr end_fn; /* called after image is complete */ + png_bytep save_buffer_ptr; /* current location in save_buffer */ + png_bytep save_buffer; /* buffer for previously read data */ + png_bytep current_buffer_ptr; /* current location in current_buffer */ + png_bytep current_buffer; /* buffer for recently used data */ + png_uint_32 push_length; /* size of current input chunk */ + png_uint_32 skip_length; /* bytes to skip in input data */ + png_size_t save_buffer_size; /* amount of data now in save_buffer */ + png_size_t save_buffer_max; /* total size of save_buffer */ + png_size_t buffer_size; /* total amount of available input data */ + png_size_t current_buffer_size; /* amount of data now in current_buffer */ + int process_mode; /* what push library is currently doing */ + int cur_palette; /* current push library palette index */ + +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ + +#if defined(__TURBOC__) && !defined(_Windows) && !defined(__FLAT__) +/* For the Borland special 64K segment handler */ + png_bytepp offset_table_ptr; + png_bytep offset_table; + png_uint_16 offset_table_number; + png_uint_16 offset_table_count; + png_uint_16 offset_table_count_free; +#endif + +#ifdef PNG_READ_QUANTIZE_SUPPORTED + png_bytep palette_lookup; /* lookup table for quantizing */ + png_bytep quantize_index; /* index translation for palette files */ +#endif + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + png_byte heuristic_method; /* heuristic for row filter selection */ + png_byte num_prev_filters; /* number of weights for previous rows */ + png_bytep prev_filters; /* filter type(s) of previous row(s) */ + png_uint_16p filter_weights; /* weight(s) for previous line(s) */ + png_uint_16p inv_filter_weights; /* 1/weight(s) for previous line(s) */ + png_uint_16p filter_costs; /* relative filter calculation cost */ + png_uint_16p inv_filter_costs; /* 1/relative filter calculation cost */ +#endif + + /* Options */ +#ifdef PNG_SET_OPTION_SUPPORTED + png_byte options; /* On/off state (up to 4 options) */ +#endif + +#if PNG_LIBPNG_VER < 10700 +/* To do: remove this from libpng-1.7 */ +#ifdef PNG_TIME_RFC1123_SUPPORTED + char time_buffer[29]; /* String to hold RFC 1123 time text */ +#endif +#endif + +/* New members added in libpng-1.0.6 */ + + png_uint_32 free_me; /* flags items libpng is responsible for freeing */ + +#ifdef PNG_USER_CHUNKS_SUPPORTED + png_voidp user_chunk_ptr; +#ifdef PNG_READ_USER_CHUNKS_SUPPORTED + png_user_chunk_ptr read_user_chunk_fn; /* user read chunk handler */ +#endif +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + int unknown_default; /* As PNG_HANDLE_* */ + unsigned int num_chunk_list; /* Number of entries in the list */ + png_bytep chunk_list; /* List of png_byte[5]; the textual chunk name + * followed by a PNG_HANDLE_* byte */ +#endif + +/* New members added in libpng-1.0.3 */ +#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED + png_byte rgb_to_gray_status; + /* Added in libpng 1.5.5 to record setting of coefficients: */ + png_byte rgb_to_gray_coefficients_set; + /* These were changed from png_byte in libpng-1.0.6 */ + png_uint_16 rgb_to_gray_red_coeff; + png_uint_16 rgb_to_gray_green_coeff; + /* deleted in 1.5.5: rgb_to_gray_blue_coeff; */ +#endif + +/* New member added in libpng-1.0.4 (renamed in 1.0.9) */ +#if defined(PNG_MNG_FEATURES_SUPPORTED) +/* Changed from png_byte to png_uint_32 at version 1.2.0 */ + png_uint_32 mng_features_permitted; +#endif + +/* New member added in libpng-1.0.9, ifdef'ed out in 1.0.12, enabled in 1.2.0 */ +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_byte filter_type; +#endif + +/* New members added in libpng-1.2.0 */ + +/* New members added in libpng-1.0.2 but first enabled by default in 1.2.0 */ +#ifdef PNG_USER_MEM_SUPPORTED + png_voidp mem_ptr; /* user supplied struct for mem functions */ + png_malloc_ptr malloc_fn; /* function for allocating memory */ + png_free_ptr free_fn; /* function for freeing memory */ +#endif + +/* New member added in libpng-1.0.13 and 1.2.0 */ + png_bytep big_row_buf; /* buffer to save current (unfiltered) row */ + +#ifdef PNG_READ_QUANTIZE_SUPPORTED +/* The following three members were added at version 1.0.14 and 1.2.4 */ + png_bytep quantize_sort; /* working sort array */ + png_bytep index_to_palette; /* where the original index currently is + in the palette */ + png_bytep palette_to_index; /* which original index points to this + palette color */ +#endif + +/* New members added in libpng-1.0.16 and 1.2.6 */ + png_byte compression_type; + +#ifdef PNG_USER_LIMITS_SUPPORTED + png_uint_32 user_width_max; + png_uint_32 user_height_max; + + /* Added in libpng-1.4.0: Total number of sPLT, text, and unknown + * chunks that can be stored (0 means unlimited). + */ + png_uint_32 user_chunk_cache_max; + + /* Total memory that a zTXt, sPLT, iTXt, iCCP, or unknown chunk + * can occupy when decompressed. 0 means unlimited. + */ + png_alloc_size_t user_chunk_malloc_max; +#endif + +/* New member added in libpng-1.0.25 and 1.2.17 */ +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* Temporary storage for unknown chunk that the library doesn't recognize, + * used while reading the chunk. + */ + png_unknown_chunk unknown_chunk; +#endif + +/* New member added in libpng-1.2.26 */ + png_size_t old_big_row_buf_size; + +#ifdef PNG_READ_SUPPORTED +/* New member added in libpng-1.2.30 */ + png_bytep read_buffer; /* buffer for reading chunk data */ + png_alloc_size_t read_buffer_size; /* current size of the buffer */ +#endif +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED + uInt IDAT_read_size; /* limit on read buffer size for IDAT */ +#endif + +#ifdef PNG_IO_STATE_SUPPORTED +/* New member added in libpng-1.4.0 */ + png_uint_32 io_state; +#endif + +/* New member added in libpng-1.5.6 */ + png_bytep big_prev_row; + +/* New member added in libpng-1.5.7 */ + void (*read_filter[PNG_FILTER_VALUE_LAST-1])(png_row_infop row_info, + png_bytep row, png_const_bytep prev_row); + +#ifdef PNG_READ_SUPPORTED +#if defined(PNG_COLORSPACE_SUPPORTED) || defined(PNG_GAMMA_SUPPORTED) + png_colorspace colorspace; +#endif +#endif +}; +#endif /* PNGSTRUCT_H */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngtrans.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngtrans.c new file mode 100644 index 0000000..20558af --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngtrans.c @@ -0,0 +1,830 @@ + +/* pngtrans.c - transforms the data in a row (used by both readers and writers) + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#if defined(PNG_READ_SUPPORTED) || defined(PNG_WRITE_SUPPORTED) + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Turn on BGR-to-RGB mapping */ +void PNGAPI +png_set_bgr(png_structrp png_ptr) +{ + png_debug(1, "in png_set_bgr"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_BGR; +} +#endif + +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Turn on 16 bit byte swapping */ +void PNGAPI +png_set_swap(png_structrp png_ptr) +{ + png_debug(1, "in png_set_swap"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth == 16) + png_ptr->transformations |= PNG_SWAP_BYTES; +} +#endif + +#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED) +/* Turn on pixel packing */ +void PNGAPI +png_set_packing(png_structrp png_ptr) +{ + png_debug(1, "in png_set_packing"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth < 8) + { + png_ptr->transformations |= PNG_PACK; + png_ptr->usr_bit_depth = 8; + } +} +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED) +/* Turn on packed pixel swapping */ +void PNGAPI +png_set_packswap(png_structrp png_ptr) +{ + png_debug(1, "in png_set_packswap"); + + if (png_ptr == NULL) + return; + + if (png_ptr->bit_depth < 8) + png_ptr->transformations |= PNG_PACKSWAP; +} +#endif + +#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED) +void PNGAPI +png_set_shift(png_structrp png_ptr, png_const_color_8p true_bits) +{ + png_debug(1, "in png_set_shift"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_SHIFT; + png_ptr->shift = *true_bits; +} +#endif + +#if defined(PNG_READ_INTERLACING_SUPPORTED) || \ + defined(PNG_WRITE_INTERLACING_SUPPORTED) +int PNGAPI +png_set_interlace_handling(png_structrp png_ptr) +{ + png_debug(1, "in png_set_interlace handling"); + + if (png_ptr && png_ptr->interlaced) + { + png_ptr->transformations |= PNG_INTERLACE; + return (7); + } + + return (1); +} +#endif + +#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED) +/* Add a filler byte on read, or remove a filler or alpha byte on write. + * The filler type has changed in v0.95 to allow future 2-byte fillers + * for 48-bit input data, as well as to avoid problems with some compilers + * that don't like bytes as parameters. + */ +void PNGAPI +png_set_filler(png_structrp png_ptr, png_uint_32 filler, int filler_loc) +{ + png_debug(1, "in png_set_filler"); + + if (png_ptr == NULL) + return; + + /* In libpng 1.6 it is possible to determine whether this is a read or write + * operation and therefore to do more checking here for a valid call. + */ + if (png_ptr->mode & PNG_IS_READ_STRUCT) + { +# ifdef PNG_READ_FILLER_SUPPORTED + /* On read png_set_filler is always valid, regardless of the base PNG + * format, because other transformations can give a format where the + * filler code can execute (basically an 8 or 16-bit component RGB or G + * format.) + * + * NOTE: usr_channels is not used by the read code! (This has led to + * confusion in the past.) The filler is only used in the read code. + */ + png_ptr->filler = (png_uint_16)filler; +# else + png_app_error(png_ptr, "png_set_filler not supported on read"); + PNG_UNUSED(filler) /* not used in the write case */ + return; +# endif + } + + else /* write */ + { +# ifdef PNG_WRITE_FILLER_SUPPORTED + /* On write the usr_channels parameter must be set correctly at the + * start to record the number of channels in the app-supplied data. + */ + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_RGB: + png_ptr->usr_channels = 4; + break; + + case PNG_COLOR_TYPE_GRAY: + if (png_ptr->bit_depth >= 8) + { + png_ptr->usr_channels = 2; + break; + } + + else + { + /* There simply isn't any code in libpng to strip out bits + * from bytes when the components are less than a byte in + * size! + */ + png_app_error(png_ptr, + "png_set_filler is invalid for low bit depth gray output"); + return; + } + + default: + png_app_error(png_ptr, + "png_set_filler: inappropriate color type"); + return; + } +# else + png_app_error(png_ptr, "png_set_filler not supported on write"); + return; +# endif + } + + /* Here on success - libpng supports the operation, set the transformation + * and the flag to say where the filler channel is. + */ + png_ptr->transformations |= PNG_FILLER; + + if (filler_loc == PNG_FILLER_AFTER) + png_ptr->flags |= PNG_FLAG_FILLER_AFTER; + + else + png_ptr->flags &= ~PNG_FLAG_FILLER_AFTER; +} + +/* Added to libpng-1.2.7 */ +void PNGAPI +png_set_add_alpha(png_structrp png_ptr, png_uint_32 filler, int filler_loc) +{ + png_debug(1, "in png_set_add_alpha"); + + if (png_ptr == NULL) + return; + + png_set_filler(png_ptr, filler, filler_loc); + /* The above may fail to do anything. */ + if (png_ptr->transformations & PNG_FILLER) + png_ptr->transformations |= PNG_ADD_ALPHA; +} + +#endif + +#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED) +void PNGAPI +png_set_swap_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_swap_alpha"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_SWAP_ALPHA; +} +#endif + +#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \ + defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED) +void PNGAPI +png_set_invert_alpha(png_structrp png_ptr) +{ + png_debug(1, "in png_set_invert_alpha"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_INVERT_ALPHA; +} +#endif + +#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED) +void PNGAPI +png_set_invert_mono(png_structrp png_ptr) +{ + png_debug(1, "in png_set_invert_mono"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_INVERT_MONO; +} + +/* Invert monochrome grayscale data */ +void /* PRIVATE */ +png_do_invert(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_invert"); + + /* This test removed from libpng version 1.0.13 and 1.2.0: + * if (row_info->bit_depth == 1 && + */ + if (row_info->color_type == PNG_COLOR_TYPE_GRAY) + { + png_bytep rp = row; + png_size_t i; + png_size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i++) + { + *rp = (png_byte)(~(*rp)); + rp++; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + row_info->bit_depth == 8) + { + png_bytep rp = row; + png_size_t i; + png_size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i += 2) + { + *rp = (png_byte)(~(*rp)); + rp += 2; + } + } + +#ifdef PNG_16BIT_SUPPORTED + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + row_info->bit_depth == 16) + { + png_bytep rp = row; + png_size_t i; + png_size_t istop = row_info->rowbytes; + + for (i = 0; i < istop; i += 4) + { + *rp = (png_byte)(~(*rp)); + *(rp + 1) = (png_byte)(~(*(rp + 1))); + rp += 4; + } + } +#endif +} +#endif + +#ifdef PNG_16BIT_SUPPORTED +#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED) +/* Swaps byte order on 16 bit depth images */ +void /* PRIVATE */ +png_do_swap(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_swap"); + + if (row_info->bit_depth == 16) + { + png_bytep rp = row; + png_uint_32 i; + png_uint_32 istop= row_info->width * row_info->channels; + + for (i = 0; i < istop; i++, rp += 2) + { + png_byte t = *rp; + *rp = *(rp + 1); + *(rp + 1) = t; + } + } +} +#endif +#endif + +#if defined(PNG_READ_PACKSWAP_SUPPORTED)||defined(PNG_WRITE_PACKSWAP_SUPPORTED) +static PNG_CONST png_byte onebppswaptable[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, + 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, + 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, + 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, + 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, + 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, + 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, + 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, + 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, + 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, + 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, + 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF +}; + +static PNG_CONST png_byte twobppswaptable[256] = { + 0x00, 0x40, 0x80, 0xC0, 0x10, 0x50, 0x90, 0xD0, + 0x20, 0x60, 0xA0, 0xE0, 0x30, 0x70, 0xB0, 0xF0, + 0x04, 0x44, 0x84, 0xC4, 0x14, 0x54, 0x94, 0xD4, + 0x24, 0x64, 0xA4, 0xE4, 0x34, 0x74, 0xB4, 0xF4, + 0x08, 0x48, 0x88, 0xC8, 0x18, 0x58, 0x98, 0xD8, + 0x28, 0x68, 0xA8, 0xE8, 0x38, 0x78, 0xB8, 0xF8, + 0x0C, 0x4C, 0x8C, 0xCC, 0x1C, 0x5C, 0x9C, 0xDC, + 0x2C, 0x6C, 0xAC, 0xEC, 0x3C, 0x7C, 0xBC, 0xFC, + 0x01, 0x41, 0x81, 0xC1, 0x11, 0x51, 0x91, 0xD1, + 0x21, 0x61, 0xA1, 0xE1, 0x31, 0x71, 0xB1, 0xF1, + 0x05, 0x45, 0x85, 0xC5, 0x15, 0x55, 0x95, 0xD5, + 0x25, 0x65, 0xA5, 0xE5, 0x35, 0x75, 0xB5, 0xF5, + 0x09, 0x49, 0x89, 0xC9, 0x19, 0x59, 0x99, 0xD9, + 0x29, 0x69, 0xA9, 0xE9, 0x39, 0x79, 0xB9, 0xF9, + 0x0D, 0x4D, 0x8D, 0xCD, 0x1D, 0x5D, 0x9D, 0xDD, + 0x2D, 0x6D, 0xAD, 0xED, 0x3D, 0x7D, 0xBD, 0xFD, + 0x02, 0x42, 0x82, 0xC2, 0x12, 0x52, 0x92, 0xD2, + 0x22, 0x62, 0xA2, 0xE2, 0x32, 0x72, 0xB2, 0xF2, + 0x06, 0x46, 0x86, 0xC6, 0x16, 0x56, 0x96, 0xD6, + 0x26, 0x66, 0xA6, 0xE6, 0x36, 0x76, 0xB6, 0xF6, + 0x0A, 0x4A, 0x8A, 0xCA, 0x1A, 0x5A, 0x9A, 0xDA, + 0x2A, 0x6A, 0xAA, 0xEA, 0x3A, 0x7A, 0xBA, 0xFA, + 0x0E, 0x4E, 0x8E, 0xCE, 0x1E, 0x5E, 0x9E, 0xDE, + 0x2E, 0x6E, 0xAE, 0xEE, 0x3E, 0x7E, 0xBE, 0xFE, + 0x03, 0x43, 0x83, 0xC3, 0x13, 0x53, 0x93, 0xD3, + 0x23, 0x63, 0xA3, 0xE3, 0x33, 0x73, 0xB3, 0xF3, + 0x07, 0x47, 0x87, 0xC7, 0x17, 0x57, 0x97, 0xD7, + 0x27, 0x67, 0xA7, 0xE7, 0x37, 0x77, 0xB7, 0xF7, + 0x0B, 0x4B, 0x8B, 0xCB, 0x1B, 0x5B, 0x9B, 0xDB, + 0x2B, 0x6B, 0xAB, 0xEB, 0x3B, 0x7B, 0xBB, 0xFB, + 0x0F, 0x4F, 0x8F, 0xCF, 0x1F, 0x5F, 0x9F, 0xDF, + 0x2F, 0x6F, 0xAF, 0xEF, 0x3F, 0x7F, 0xBF, 0xFF +}; + +static PNG_CONST png_byte fourbppswaptable[256] = { + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, + 0x01, 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, + 0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1, + 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, + 0x82, 0x92, 0xA2, 0xB2, 0xC2, 0xD2, 0xE2, 0xF2, + 0x03, 0x13, 0x23, 0x33, 0x43, 0x53, 0x63, 0x73, + 0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, + 0x04, 0x14, 0x24, 0x34, 0x44, 0x54, 0x64, 0x74, + 0x84, 0x94, 0xA4, 0xB4, 0xC4, 0xD4, 0xE4, 0xF4, + 0x05, 0x15, 0x25, 0x35, 0x45, 0x55, 0x65, 0x75, + 0x85, 0x95, 0xA5, 0xB5, 0xC5, 0xD5, 0xE5, 0xF5, + 0x06, 0x16, 0x26, 0x36, 0x46, 0x56, 0x66, 0x76, + 0x86, 0x96, 0xA6, 0xB6, 0xC6, 0xD6, 0xE6, 0xF6, + 0x07, 0x17, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, + 0x87, 0x97, 0xA7, 0xB7, 0xC7, 0xD7, 0xE7, 0xF7, + 0x08, 0x18, 0x28, 0x38, 0x48, 0x58, 0x68, 0x78, + 0x88, 0x98, 0xA8, 0xB8, 0xC8, 0xD8, 0xE8, 0xF8, + 0x09, 0x19, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, + 0x89, 0x99, 0xA9, 0xB9, 0xC9, 0xD9, 0xE9, 0xF9, + 0x0A, 0x1A, 0x2A, 0x3A, 0x4A, 0x5A, 0x6A, 0x7A, + 0x8A, 0x9A, 0xAA, 0xBA, 0xCA, 0xDA, 0xEA, 0xFA, + 0x0B, 0x1B, 0x2B, 0x3B, 0x4B, 0x5B, 0x6B, 0x7B, + 0x8B, 0x9B, 0xAB, 0xBB, 0xCB, 0xDB, 0xEB, 0xFB, + 0x0C, 0x1C, 0x2C, 0x3C, 0x4C, 0x5C, 0x6C, 0x7C, + 0x8C, 0x9C, 0xAC, 0xBC, 0xCC, 0xDC, 0xEC, 0xFC, + 0x0D, 0x1D, 0x2D, 0x3D, 0x4D, 0x5D, 0x6D, 0x7D, + 0x8D, 0x9D, 0xAD, 0xBD, 0xCD, 0xDD, 0xED, 0xFD, + 0x0E, 0x1E, 0x2E, 0x3E, 0x4E, 0x5E, 0x6E, 0x7E, + 0x8E, 0x9E, 0xAE, 0xBE, 0xCE, 0xDE, 0xEE, 0xFE, + 0x0F, 0x1F, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F, + 0x8F, 0x9F, 0xAF, 0xBF, 0xCF, 0xDF, 0xEF, 0xFF +}; + +/* Swaps pixel packing order within bytes */ +void /* PRIVATE */ +png_do_packswap(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_packswap"); + + if (row_info->bit_depth < 8) + { + png_bytep rp; + png_const_bytep end, table; + + end = row + row_info->rowbytes; + + if (row_info->bit_depth == 1) + table = onebppswaptable; + + else if (row_info->bit_depth == 2) + table = twobppswaptable; + + else if (row_info->bit_depth == 4) + table = fourbppswaptable; + + else + return; + + for (rp = row; rp < end; rp++) + *rp = table[*rp]; + } +} +#endif /* PNG_READ_PACKSWAP_SUPPORTED or PNG_WRITE_PACKSWAP_SUPPORTED */ + +#if defined(PNG_WRITE_FILLER_SUPPORTED) || \ + defined(PNG_READ_STRIP_ALPHA_SUPPORTED) +/* Remove a channel - this used to be 'png_do_strip_filler' but it used a + * somewhat weird combination of flags to determine what to do. All the calls + * to png_do_strip_filler are changed in 1.5.2 to call this instead with the + * correct arguments. + * + * The routine isn't general - the channel must be the channel at the start or + * end (not in the middle) of each pixel. + */ +void /* PRIVATE */ +png_do_strip_channel(png_row_infop row_info, png_bytep row, int at_start) +{ + png_bytep sp = row; /* source pointer */ + png_bytep dp = row; /* destination pointer */ + png_bytep ep = row + row_info->rowbytes; /* One beyond end of row */ + + /* At the start sp will point to the first byte to copy and dp to where + * it is copied to. ep always points just beyond the end of the row, so + * the loop simply copies (channels-1) channels until sp reaches ep. + * + * at_start: 0 -- convert AG, XG, ARGB, XRGB, AAGG, XXGG, etc. + * nonzero -- convert GA, GX, RGBA, RGBX, GGAA, RRGGBBXX, etc. + */ + + /* GA, GX, XG cases */ + if (row_info->channels == 2) + { + if (row_info->bit_depth == 8) + { + if (at_start) /* Skip initial filler */ + ++sp; + else /* Skip initial channel and, for sp, the filler */ + sp += 2, ++dp; + + /* For a 1 pixel wide image there is nothing to do */ + while (sp < ep) + *dp++ = *sp, sp += 2; + + row_info->pixel_depth = 8; + } + + else if (row_info->bit_depth == 16) + { + if (at_start) /* Skip initial filler */ + sp += 2; + else /* Skip initial channel and, for sp, the filler */ + sp += 4, dp += 2; + + while (sp < ep) + *dp++ = *sp++, *dp++ = *sp, sp += 3; + + row_info->pixel_depth = 16; + } + + else + return; /* bad bit depth */ + + row_info->channels = 1; + + /* Finally fix the color type if it records an alpha channel */ + if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + row_info->color_type = PNG_COLOR_TYPE_GRAY; + } + + /* RGBA, RGBX, XRGB cases */ + else if (row_info->channels == 4) + { + if (row_info->bit_depth == 8) + { + if (at_start) /* Skip initial filler */ + ++sp; + else /* Skip initial channels and, for sp, the filler */ + sp += 4, dp += 3; + + /* Note that the loop adds 3 to dp and 4 to sp each time. */ + while (sp < ep) + *dp++ = *sp++, *dp++ = *sp++, *dp++ = *sp, sp += 2; + + row_info->pixel_depth = 24; + } + + else if (row_info->bit_depth == 16) + { + if (at_start) /* Skip initial filler */ + sp += 2; + else /* Skip initial channels and, for sp, the filler */ + sp += 8, dp += 6; + + while (sp < ep) + { + /* Copy 6 bytes, skip 2 */ + *dp++ = *sp++, *dp++ = *sp++; + *dp++ = *sp++, *dp++ = *sp++; + *dp++ = *sp++, *dp++ = *sp, sp += 3; + } + + row_info->pixel_depth = 48; + } + + else + return; /* bad bit depth */ + + row_info->channels = 3; + + /* Finally fix the color type if it records an alpha channel */ + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + row_info->color_type = PNG_COLOR_TYPE_RGB; + } + + else + return; /* The filler channel has gone already */ + + /* Fix the rowbytes value. */ + row_info->rowbytes = dp-row; +} +#endif + +#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED) +/* Swaps red and blue bytes within a pixel */ +void /* PRIVATE */ +png_do_bgr(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_bgr"); + + if ((row_info->color_type & PNG_COLOR_MASK_COLOR)) + { + png_uint_32 row_width = row_info->width; + if (row_info->bit_depth == 8) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 3) + { + png_byte save = *rp; + *rp = *(rp + 2); + *(rp + 2) = save; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 4) + { + png_byte save = *rp; + *rp = *(rp + 2); + *(rp + 2) = save; + } + } + } + +#ifdef PNG_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 6) + { + png_byte save = *rp; + *rp = *(rp + 4); + *(rp + 4) = save; + save = *(rp + 1); + *(rp + 1) = *(rp + 5); + *(rp + 5) = save; + } + } + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + png_bytep rp; + png_uint_32 i; + + for (i = 0, rp = row; i < row_width; i++, rp += 8) + { + png_byte save = *rp; + *rp = *(rp + 4); + *(rp + 4) = save; + save = *(rp + 1); + *(rp + 1) = *(rp + 5); + *(rp + 5) = save; + } + } + } +#endif + } +} +#endif /* PNG_READ_BGR_SUPPORTED or PNG_WRITE_BGR_SUPPORTED */ + +#if defined(PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED) || \ + defined(PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED) +/* Added at libpng-1.5.10 */ +void /* PRIVATE */ +png_do_check_palette_indexes(png_structrp png_ptr, png_row_infop row_info) +{ + if (png_ptr->num_palette < (1 << row_info->bit_depth) && + png_ptr->num_palette > 0) /* num_palette can be 0 in MNG files */ + { + /* Calculations moved outside switch in an attempt to stop different + * compiler warnings. 'padding' is in *bits* within the last byte, it is + * an 'int' because pixel_depth becomes an 'int' in the expression below, + * and this calculation is used because it avoids warnings that other + * forms produced on either GCC or MSVC. + */ + int padding = (-row_info->pixel_depth * row_info->width) & 7; + png_bytep rp = png_ptr->row_buf + row_info->rowbytes; + + switch (row_info->bit_depth) + { + case 1: + { + /* in this case, all bytes must be 0 so we don't need + * to unpack the pixels except for the rightmost one. + */ + for (; rp > png_ptr->row_buf; rp--) + { + if (*rp >> padding != 0) + png_ptr->num_palette_max = 1; + padding = 0; + } + + break; + } + + case 2: + { + for (; rp > png_ptr->row_buf; rp--) + { + int i = ((*rp >> padding) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 2) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 4) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 6) & 0x03); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + padding = 0; + } + + break; + } + + case 4: + { + for (; rp > png_ptr->row_buf; rp--) + { + int i = ((*rp >> padding) & 0x0f); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + i = (((*rp >> padding) >> 4) & 0x0f); + + if (i > png_ptr->num_palette_max) + png_ptr->num_palette_max = i; + + padding = 0; + } + + break; + } + + case 8: + { + for (; rp > png_ptr->row_buf; rp--) + { + if (*rp > png_ptr->num_palette_max) + png_ptr->num_palette_max = (int) *rp; + } + + break; + } + + default: + break; + } + } +} +#endif /* PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED */ + +#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \ + defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED) +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +void PNGAPI +png_set_user_transform_info(png_structrp png_ptr, png_voidp + user_transform_ptr, int user_transform_depth, int user_transform_channels) +{ + png_debug(1, "in png_set_user_transform_info"); + + if (png_ptr == NULL) + return; + png_ptr->user_transform_ptr = user_transform_ptr; + png_ptr->user_transform_depth = (png_byte)user_transform_depth; + png_ptr->user_transform_channels = (png_byte)user_transform_channels; +} +#endif + +/* This function returns a pointer to the user_transform_ptr associated with + * the user transform functions. The application should free any memory + * associated with this pointer before png_write_destroy and png_read_destroy + * are called. + */ +#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED +png_voidp PNGAPI +png_get_user_transform_ptr(png_const_structrp png_ptr) +{ + if (png_ptr == NULL) + return (NULL); + + return png_ptr->user_transform_ptr; +} +#endif + +#ifdef PNG_USER_TRANSFORM_INFO_SUPPORTED +png_uint_32 PNGAPI +png_get_current_row_number(png_const_structrp png_ptr) +{ + /* See the comments in png.h - this is the sub-image row when reading and + * interlaced image. + */ + if (png_ptr != NULL) + return png_ptr->row_number; + + return PNG_UINT_32_MAX; /* help the app not to fail silently */ +} + +png_byte PNGAPI +png_get_current_pass_number(png_const_structrp png_ptr) +{ + if (png_ptr != NULL) + return png_ptr->pass; + return 8; /* invalid */ +} +#endif /* PNG_USER_TRANSFORM_INFO_SUPPORTED */ +#endif /* PNG_READ_USER_TRANSFORM_SUPPORTED || + PNG_WRITE_USER_TRANSFORM_SUPPORTED */ +#endif /* PNG_READ_SUPPORTED || PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwio.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwio.c new file mode 100644 index 0000000..c5fca98 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwio.c @@ -0,0 +1,164 @@ + +/* pngwio.c - functions for data output + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * This file provides a location for all output. Users who need + * special handling are expected to write functions that have the same + * arguments as these and perform similar functions, but that possibly + * use different output methods. Note that you shouldn't change these + * functions, but rather write replacement functions and then change + * them at run time with png_set_write_fn(...). + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED + +/* Write the data to whatever output you are using. The default routine + * writes to a file pointer. Note that this routine sometimes gets called + * with very small lengths, so you should implement some kind of simple + * buffering if you are using unbuffered writes. This should never be asked + * to write more than 64K on a 16 bit machine. + */ + +void /* PRIVATE */ +png_write_data(png_structrp png_ptr, png_const_bytep data, png_size_t length) +{ + /* NOTE: write_data_fn must not change the buffer! */ + if (png_ptr->write_data_fn != NULL ) + (*(png_ptr->write_data_fn))(png_ptr, png_constcast(png_bytep,data), + length); + + else + png_error(png_ptr, "Call to NULL write function"); +} + +#ifdef PNG_STDIO_SUPPORTED +/* This is the function that does the actual writing of data. If you are + * not writing to a standard C stream, you should create a replacement + * write_data function and use it at run time with png_set_write_fn(), rather + * than changing the library. + */ +void PNGCBAPI +png_default_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + png_size_t check; + + if (png_ptr == NULL) + return; + + check = fwrite(data, 1, length, (png_FILE_p)(png_ptr->io_ptr)); + + if (check != length) + png_error(png_ptr, "Write Error"); +} +#endif + +/* This function is called to output any data pending writing (normally + * to disk). After png_flush is called, there should be no data pending + * writing in any buffers. + */ +#ifdef PNG_WRITE_FLUSH_SUPPORTED +void /* PRIVATE */ +png_flush(png_structrp png_ptr) +{ + if (png_ptr->output_flush_fn != NULL) + (*(png_ptr->output_flush_fn))(png_ptr); +} + +# ifdef PNG_STDIO_SUPPORTED +void PNGCBAPI +png_default_flush(png_structp png_ptr) +{ + png_FILE_p io_ptr; + + if (png_ptr == NULL) + return; + + io_ptr = png_voidcast(png_FILE_p, (png_ptr->io_ptr)); + fflush(io_ptr); +} +# endif +#endif + +/* This function allows the application to supply new output functions for + * libpng if standard C streams aren't being used. + * + * This function takes as its arguments: + * png_ptr - pointer to a png output data structure + * io_ptr - pointer to user supplied structure containing info about + * the output functions. May be NULL. + * write_data_fn - pointer to a new output function that takes as its + * arguments a pointer to a png_struct, a pointer to + * data to be written, and a 32-bit unsigned int that is + * the number of bytes to be written. The new write + * function should call png_error(png_ptr, "Error msg") + * to exit and output any fatal error messages. May be + * NULL, in which case libpng's default function will + * be used. + * flush_data_fn - pointer to a new flush function that takes as its + * arguments a pointer to a png_struct. After a call to + * the flush function, there should be no data in any buffers + * or pending transmission. If the output method doesn't do + * any buffering of output, a function prototype must still be + * supplied although it doesn't have to do anything. If + * PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile + * time, output_flush_fn will be ignored, although it must be + * supplied for compatibility. May be NULL, in which case + * libpng's default function will be used, if + * PNG_WRITE_FLUSH_SUPPORTED is defined. This is not + * a good idea if io_ptr does not point to a standard + * *FILE structure. + */ +void PNGAPI +png_set_write_fn(png_structrp png_ptr, png_voidp io_ptr, + png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->io_ptr = io_ptr; + +#ifdef PNG_STDIO_SUPPORTED + if (write_data_fn != NULL) + png_ptr->write_data_fn = write_data_fn; + + else + png_ptr->write_data_fn = png_default_write_data; +#else + png_ptr->write_data_fn = write_data_fn; +#endif + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_STDIO_SUPPORTED + + if (output_flush_fn != NULL) + png_ptr->output_flush_fn = output_flush_fn; + + else + png_ptr->output_flush_fn = png_default_flush; + +# else + png_ptr->output_flush_fn = output_flush_fn; +# endif +#endif /* PNG_WRITE_FLUSH_SUPPORTED */ + + /* It is an error to read while writing a png file */ + if (png_ptr->read_data_fn != NULL) + { + png_ptr->read_data_fn = NULL; + + png_warning(png_ptr, + "Can't set both read_data_fn and write_data_fn in the" + " same structure"); + } +} +#endif /* PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwrite.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwrite.c new file mode 100644 index 0000000..4c68682 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwrite.c @@ -0,0 +1,2331 @@ + +/* pngwrite.c - general routines to write a PNG file + * + * Last changed in libpng 1.6.1 [March 28, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" +#if defined(PNG_SIMPLIFIED_WRITE_SUPPORTED) && defined(PNG_STDIO_SUPPORTED) +# include +#endif + +#ifdef PNG_WRITE_SUPPORTED + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +/* Write out all the unknown chunks for the current given location */ +static void +write_unknown_chunks(png_structrp png_ptr, png_const_inforp info_ptr, + unsigned int where) +{ + if (info_ptr->unknown_chunks_num) + { + png_const_unknown_chunkp up; + + png_debug(5, "writing extra chunks"); + + for (up = info_ptr->unknown_chunks; + up < info_ptr->unknown_chunks + info_ptr->unknown_chunks_num; + ++up) + if (up->location & where) + { + /* If per-chunk unknown chunk handling is enabled use it, otherwise + * just write the chunks the application has set. + */ +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + int keep = png_handle_as_unknown(png_ptr, up->name); + + /* NOTE: this code is radically different from the read side in the + * matter of handling an ancillary unknown chunk. In the read side + * the default behavior is to discard it, in the code below the default + * behavior is to write it. Critical chunks are, however, only + * written if explicitly listed or if the default is set to write all + * unknown chunks. + * + * The default handling is also slightly weird - it is not possible to + * stop the writing of all unsafe-to-copy chunks! + * + * TODO: REVIEW: this would seem to be a bug. + */ + if (keep != PNG_HANDLE_CHUNK_NEVER && + ((up->name[3] & 0x20) /* safe-to-copy overrides everything */ || + keep == PNG_HANDLE_CHUNK_ALWAYS || + (keep == PNG_HANDLE_CHUNK_AS_DEFAULT && + png_ptr->unknown_default == PNG_HANDLE_CHUNK_ALWAYS))) +#endif + { + /* TODO: review, what is wrong with a zero length unknown chunk? */ + if (up->size == 0) + png_warning(png_ptr, "Writing zero-length unknown chunk"); + + png_write_chunk(png_ptr, up->name, up->data, up->size); + } + } + } +} +#endif /* PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED */ + +/* Writes all the PNG information. This is the suggested way to use the + * library. If you have a new chunk to add, make a function to write it, + * and put it in the correct location here. If you want the chunk written + * after the image data, put it in png_write_end(). I strongly encourage + * you to supply a PNG_INFO_ flag, and check info_ptr->valid before writing + * the chunk, as that will keep the code from breaking if you want to just + * write a plain PNG file. If you have long comments, I suggest writing + * them in png_write_end(), and compressing them. + */ +void PNGAPI +png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) +{ + png_debug(1, "in png_write_info_before_PLTE"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + if (!(png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE)) + { + /* Write PNG signature */ + png_write_sig(png_ptr); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) && \ + (png_ptr->mng_features_permitted)) + { + png_warning(png_ptr, "MNG features are not allowed in a PNG datastream"); + png_ptr->mng_features_permitted = 0; + } +#endif + + /* Write IHDR information. */ + png_write_IHDR(png_ptr, info_ptr->width, info_ptr->height, + info_ptr->bit_depth, info_ptr->color_type, info_ptr->compression_type, + info_ptr->filter_type, +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + info_ptr->interlace_type +#else + 0 +#endif + ); + + /* The rest of these check to see if the valid field has the appropriate + * flag set, and if it does, writes the chunk. + * + * 1.6.0: COLORSPACE support controls the writing of these chunks too, and + * the chunks will be written if the WRITE routine is there and information + * is available in the COLORSPACE. (See png_colorspace_sync_info in png.c + * for where the valid flags get set.) + * + * Under certain circumstances the colorspace can be invalidated without + * syncing the info_struct 'valid' flags; this happens if libpng detects and + * error and calls png_error while the color space is being set, yet the + * application continues writing the PNG. So check the 'invalid' flag here + * too. + */ +#ifdef PNG_GAMMA_SUPPORTED +# ifdef PNG_WRITE_gAMA_SUPPORTED + if (!(info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) && + (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_gAMA) && + (info_ptr->valid & PNG_INFO_gAMA)) + png_write_gAMA_fixed(png_ptr, info_ptr->colorspace.gamma); +# endif +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED + /* Write only one of sRGB or an ICC profile. If a profile was supplied + * and it matches one of the known sRGB ones issue a warning. + */ +# ifdef PNG_WRITE_iCCP_SUPPORTED + if (!(info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) && + (info_ptr->valid & PNG_INFO_iCCP)) + { +# ifdef PNG_WRITE_sRGB_SUPPORTED + if (info_ptr->valid & PNG_INFO_sRGB) + png_app_warning(png_ptr, + "profile matches sRGB but writing iCCP instead"); +# endif + + png_write_iCCP(png_ptr, info_ptr->iccp_name, + info_ptr->iccp_profile); + } +# ifdef PNG_WRITE_sRGB_SUPPORTED + else +# endif +# endif + +# ifdef PNG_WRITE_sRGB_SUPPORTED + if (!(info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) && + (info_ptr->valid & PNG_INFO_sRGB)) + png_write_sRGB(png_ptr, info_ptr->colorspace.rendering_intent); +# endif /* WRITE_sRGB */ +#endif /* COLORSPACE */ + +#ifdef PNG_WRITE_sBIT_SUPPORTED + if (info_ptr->valid & PNG_INFO_sBIT) + png_write_sBIT(png_ptr, &(info_ptr->sig_bit), info_ptr->color_type); +#endif + +#ifdef PNG_COLORSPACE_SUPPORTED +# ifdef PNG_WRITE_cHRM_SUPPORTED + if (!(info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) && + (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_cHRM) && + (info_ptr->valid & PNG_INFO_cHRM)) + png_write_cHRM_fixed(png_ptr, &info_ptr->colorspace.end_points_xy); +# endif +#endif + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_IHDR); +#endif + + png_ptr->mode |= PNG_WROTE_INFO_BEFORE_PLTE; + } +} + +void PNGAPI +png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) +{ +#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED) + int i; +#endif + + png_debug(1, "in png_write_info"); + + if (png_ptr == NULL || info_ptr == NULL) + return; + + png_write_info_before_PLTE(png_ptr, info_ptr); + + if (info_ptr->valid & PNG_INFO_PLTE) + png_write_PLTE(png_ptr, info_ptr->palette, + (png_uint_32)info_ptr->num_palette); + + else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + png_error(png_ptr, "Valid palette required for paletted images"); + +#ifdef PNG_WRITE_tRNS_SUPPORTED + if (info_ptr->valid & PNG_INFO_tRNS) + { +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + /* Invert the alpha channel (in tRNS) */ + if ((png_ptr->transformations & PNG_INVERT_ALPHA) && + info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + int j; + for (j = 0; j<(int)info_ptr->num_trans; j++) + info_ptr->trans_alpha[j] = + (png_byte)(255 - info_ptr->trans_alpha[j]); + } +#endif + png_write_tRNS(png_ptr, info_ptr->trans_alpha, &(info_ptr->trans_color), + info_ptr->num_trans, info_ptr->color_type); + } +#endif +#ifdef PNG_WRITE_bKGD_SUPPORTED + if (info_ptr->valid & PNG_INFO_bKGD) + png_write_bKGD(png_ptr, &(info_ptr->background), info_ptr->color_type); +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED + if (info_ptr->valid & PNG_INFO_hIST) + png_write_hIST(png_ptr, info_ptr->hist, info_ptr->num_palette); +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED + if (info_ptr->valid & PNG_INFO_oFFs) + png_write_oFFs(png_ptr, info_ptr->x_offset, info_ptr->y_offset, + info_ptr->offset_unit_type); +#endif + +#ifdef PNG_WRITE_pCAL_SUPPORTED + if (info_ptr->valid & PNG_INFO_pCAL) + png_write_pCAL(png_ptr, info_ptr->pcal_purpose, info_ptr->pcal_X0, + info_ptr->pcal_X1, info_ptr->pcal_type, info_ptr->pcal_nparams, + info_ptr->pcal_units, info_ptr->pcal_params); +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED + if (info_ptr->valid & PNG_INFO_sCAL) + png_write_sCAL_s(png_ptr, (int)info_ptr->scal_unit, + info_ptr->scal_s_width, info_ptr->scal_s_height); +#endif /* sCAL */ + +#ifdef PNG_WRITE_pHYs_SUPPORTED + if (info_ptr->valid & PNG_INFO_pHYs) + png_write_pHYs(png_ptr, info_ptr->x_pixels_per_unit, + info_ptr->y_pixels_per_unit, info_ptr->phys_unit_type); +#endif /* pHYs */ + +#ifdef PNG_WRITE_tIME_SUPPORTED + if (info_ptr->valid & PNG_INFO_tIME) + { + png_write_tIME(png_ptr, &(info_ptr->mod_time)); + png_ptr->mode |= PNG_WROTE_tIME; + } +#endif /* tIME */ + +#ifdef PNG_WRITE_sPLT_SUPPORTED + if (info_ptr->valid & PNG_INFO_sPLT) + for (i = 0; i < (int)info_ptr->splt_palettes_num; i++) + png_write_sPLT(png_ptr, info_ptr->splt_palettes + i); +#endif /* sPLT */ + +#ifdef PNG_WRITE_TEXT_SUPPORTED + /* Check to see if we need to write text chunks */ + for (i = 0; i < info_ptr->num_text; i++) + { + png_debug2(2, "Writing header text chunk %d, type %d", i, + info_ptr->text[i].compression); + /* An internationalized chunk? */ + if (info_ptr->text[i].compression > 0) + { +#ifdef PNG_WRITE_iTXt_SUPPORTED + /* Write international chunk */ + png_write_iTXt(png_ptr, + info_ptr->text[i].compression, + info_ptr->text[i].key, + info_ptr->text[i].lang, + info_ptr->text[i].lang_key, + info_ptr->text[i].text); +#else + png_warning(png_ptr, "Unable to write international text"); +#endif + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + } + + /* If we want a compressed text chunk */ + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_zTXt) + { +#ifdef PNG_WRITE_zTXt_SUPPORTED + /* Write compressed chunk */ + png_write_zTXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, 0, + info_ptr->text[i].compression); +#else + png_warning(png_ptr, "Unable to write compressed text"); +#endif + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; + } + + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + { +#ifdef PNG_WRITE_tEXt_SUPPORTED + /* Write uncompressed chunk */ + png_write_tEXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, + 0); + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; +#else + /* Can't get here */ + png_warning(png_ptr, "Unable to write uncompressed text"); +#endif + } + } +#endif /* tEXt */ + +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_HAVE_PLTE); +#endif +} + +/* Writes the end of the PNG file. If you don't want to write comments or + * time information, you can pass NULL for info. If you already wrote these + * in png_write_info(), do not write them again here. If you have long + * comments, I suggest writing them here, and compressing them. + */ +void PNGAPI +png_write_end(png_structrp png_ptr, png_inforp info_ptr) +{ + png_debug(1, "in png_write_end"); + + if (png_ptr == NULL) + return; + + if (!(png_ptr->mode & PNG_HAVE_IDAT)) + png_error(png_ptr, "No IDATs written into file"); + +#ifdef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED + if (png_ptr->num_palette_max > png_ptr->num_palette) + png_benign_error(png_ptr, "Wrote palette index exceeding num_palette"); +#endif + + /* See if user wants us to write information chunks */ + if (info_ptr != NULL) + { +#ifdef PNG_WRITE_TEXT_SUPPORTED + int i; /* local index variable */ +#endif +#ifdef PNG_WRITE_tIME_SUPPORTED + /* Check to see if user has supplied a time chunk */ + if ((info_ptr->valid & PNG_INFO_tIME) && + !(png_ptr->mode & PNG_WROTE_tIME)) + png_write_tIME(png_ptr, &(info_ptr->mod_time)); + +#endif +#ifdef PNG_WRITE_TEXT_SUPPORTED + /* Loop through comment chunks */ + for (i = 0; i < info_ptr->num_text; i++) + { + png_debug2(2, "Writing trailer text chunk %d, type %d", i, + info_ptr->text[i].compression); + /* An internationalized chunk? */ + if (info_ptr->text[i].compression > 0) + { +#ifdef PNG_WRITE_iTXt_SUPPORTED + /* Write international chunk */ + png_write_iTXt(png_ptr, + info_ptr->text[i].compression, + info_ptr->text[i].key, + info_ptr->text[i].lang, + info_ptr->text[i].lang_key, + info_ptr->text[i].text); +#else + png_warning(png_ptr, "Unable to write international text"); +#endif + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + } + + else if (info_ptr->text[i].compression >= PNG_TEXT_COMPRESSION_zTXt) + { +#ifdef PNG_WRITE_zTXt_SUPPORTED + /* Write compressed chunk */ + png_write_zTXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, 0, + info_ptr->text[i].compression); +#else + png_warning(png_ptr, "Unable to write compressed text"); +#endif + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; + } + + else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) + { +#ifdef PNG_WRITE_tEXt_SUPPORTED + /* Write uncompressed chunk */ + png_write_tEXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, 0); +#else + png_warning(png_ptr, "Unable to write uncompressed text"); +#endif + + /* Mark this chunk as written */ + info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; + } + } +#endif +#ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + write_unknown_chunks(png_ptr, info_ptr, PNG_AFTER_IDAT); +#endif + } + + png_ptr->mode |= PNG_AFTER_IDAT; + + /* Write end of PNG file */ + png_write_IEND(png_ptr); + /* This flush, added in libpng-1.0.8, removed from libpng-1.0.9beta03, + * and restored again in libpng-1.2.30, may cause some applications that + * do not set png_ptr->output_flush_fn to crash. If your application + * experiences a problem, please try building libpng with + * PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED defined, and report the event to + * png-mng-implement at lists.sf.net . + */ +#ifdef PNG_WRITE_FLUSH_SUPPORTED +# ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED + png_flush(png_ptr); +# endif +#endif +} + +#ifdef PNG_CONVERT_tIME_SUPPORTED +void PNGAPI +png_convert_from_struct_tm(png_timep ptime, PNG_CONST struct tm * ttime) +{ + png_debug(1, "in png_convert_from_struct_tm"); + + ptime->year = (png_uint_16)(1900 + ttime->tm_year); + ptime->month = (png_byte)(ttime->tm_mon + 1); + ptime->day = (png_byte)ttime->tm_mday; + ptime->hour = (png_byte)ttime->tm_hour; + ptime->minute = (png_byte)ttime->tm_min; + ptime->second = (png_byte)ttime->tm_sec; +} + +void PNGAPI +png_convert_from_time_t(png_timep ptime, time_t ttime) +{ + struct tm *tbuf; + + png_debug(1, "in png_convert_from_time_t"); + + tbuf = gmtime(&ttime); + png_convert_from_struct_tm(ptime, tbuf); +} +#endif + +/* Initialize png_ptr structure, and allocate any memory needed */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_write_struct,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn),PNG_ALLOCATED) +{ +#ifndef PNG_USER_MEM_SUPPORTED + png_structrp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, NULL, NULL, NULL); +#else + return png_create_write_struct_2(user_png_ver, error_ptr, error_fn, + warn_fn, NULL, NULL, NULL); +} + +/* Alternate initialize png_ptr structure, and allocate any memory needed */ +PNG_FUNCTION(png_structp,PNGAPI +png_create_write_struct_2,(png_const_charp user_png_ver, png_voidp error_ptr, + png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr, + png_malloc_ptr malloc_fn, png_free_ptr free_fn),PNG_ALLOCATED) +{ + png_structrp png_ptr = png_create_png_struct(user_png_ver, error_ptr, + error_fn, warn_fn, mem_ptr, malloc_fn, free_fn); +#endif /* PNG_USER_MEM_SUPPORTED */ + + /* Set the zlib control values to defaults; they can be overridden by the + * application after the struct has been created. + */ + png_ptr->zbuffer_size = PNG_ZBUF_SIZE; + + /* The 'zlib_strategy' setting is irrelevant because png_default_claim in + * pngwutil.c defaults it according to whether or not filters will be used, + * and ignores this setting. + */ + png_ptr->zlib_strategy = PNG_Z_DEFAULT_STRATEGY; + png_ptr->zlib_level = PNG_Z_DEFAULT_COMPRESSION; + png_ptr->zlib_mem_level = 8; + png_ptr->zlib_window_bits = 15; + png_ptr->zlib_method = 8; + +#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED + png_ptr->zlib_text_strategy = PNG_TEXT_Z_DEFAULT_STRATEGY; + png_ptr->zlib_text_level = PNG_TEXT_Z_DEFAULT_COMPRESSION; + png_ptr->zlib_text_mem_level = 8; + png_ptr->zlib_text_window_bits = 15; + png_ptr->zlib_text_method = 8; +#endif /* PNG_WRITE_COMPRESSED_TEXT_SUPPORTED */ + + /* This is a highly dubious configuration option; by default it is off, but + * it may be appropriate for private builds that are testing extensions not + * conformant to the current specification, or of applications that must not + * fail to write at all costs! + */ +# ifdef PNG_BENIGN_WRITE_ERRORS_SUPPORTED + png_ptr->flags |= PNG_FLAG_BENIGN_ERRORS_WARN; + /* In stable builds only warn if an application error can be completely + * handled. + */ +# endif + + /* App warnings are warnings in release (or release candidate) builds but + * are errors during development. + */ +# if PNG_LIBPNG_BUILD_BASE_TYPE >= PNG_LIBPNG_BUILD_RC + png_ptr->flags |= PNG_FLAG_APP_WARNINGS_WARN; +# endif + + if (png_ptr != NULL) + { + /* TODO: delay this, it can be done in png_init_io() (if the app doesn't + * do it itself) avoiding setting the default function if it is not + * required. + */ + png_set_write_fn(png_ptr, NULL, NULL, NULL); + } + + return png_ptr; +} + + +/* Write a few rows of image data. If the image is interlaced, + * either you will have to write the 7 sub images, or, if you + * have called png_set_interlace_handling(), you will have to + * "write" the image seven times. + */ +void PNGAPI +png_write_rows(png_structrp png_ptr, png_bytepp row, + png_uint_32 num_rows) +{ + png_uint_32 i; /* row counter */ + png_bytepp rp; /* row pointer */ + + png_debug(1, "in png_write_rows"); + + if (png_ptr == NULL) + return; + + /* Loop through the rows */ + for (i = 0, rp = row; i < num_rows; i++, rp++) + { + png_write_row(png_ptr, *rp); + } +} + +/* Write the image. You only need to call this function once, even + * if you are writing an interlaced image. + */ +void PNGAPI +png_write_image(png_structrp png_ptr, png_bytepp image) +{ + png_uint_32 i; /* row index */ + int pass, num_pass; /* pass variables */ + png_bytepp rp; /* points to current row */ + + if (png_ptr == NULL) + return; + + png_debug(1, "in png_write_image"); + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Initialize interlace handling. If image is not interlaced, + * this will set pass to 1 + */ + num_pass = png_set_interlace_handling(png_ptr); +#else + num_pass = 1; +#endif + /* Loop through passes */ + for (pass = 0; pass < num_pass; pass++) + { + /* Loop through image */ + for (i = 0, rp = image; i < png_ptr->height; i++, rp++) + { + png_write_row(png_ptr, *rp); + } + } +} + +/* Called by user to write a row of image data */ +void PNGAPI +png_write_row(png_structrp png_ptr, png_const_bytep row) +{ + /* 1.5.6: moved from png_struct to be a local structure: */ + png_row_info row_info; + + if (png_ptr == NULL) + return; + + png_debug2(1, "in png_write_row (row %u, pass %d)", + png_ptr->row_number, png_ptr->pass); + + /* Initialize transformations and other stuff if first time */ + if (png_ptr->row_number == 0 && png_ptr->pass == 0) + { + /* Make sure we wrote the header info */ + if (!(png_ptr->mode & PNG_WROTE_INFO_BEFORE_PLTE)) + png_error(png_ptr, + "png_write_info was never called before png_write_row"); + + /* Check for transforms that have been set but were defined out */ +#if !defined(PNG_WRITE_INVERT_SUPPORTED) && defined(PNG_READ_INVERT_SUPPORTED) + if (png_ptr->transformations & PNG_INVERT_MONO) + png_warning(png_ptr, "PNG_WRITE_INVERT_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_FILLER_SUPPORTED) && defined(PNG_READ_FILLER_SUPPORTED) + if (png_ptr->transformations & PNG_FILLER) + png_warning(png_ptr, "PNG_WRITE_FILLER_SUPPORTED is not defined"); +#endif +#if !defined(PNG_WRITE_PACKSWAP_SUPPORTED) && \ + defined(PNG_READ_PACKSWAP_SUPPORTED) + if (png_ptr->transformations & PNG_PACKSWAP) + png_warning(png_ptr, + "PNG_WRITE_PACKSWAP_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_PACK_SUPPORTED) && defined(PNG_READ_PACK_SUPPORTED) + if (png_ptr->transformations & PNG_PACK) + png_warning(png_ptr, "PNG_WRITE_PACK_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_SHIFT_SUPPORTED) && defined(PNG_READ_SHIFT_SUPPORTED) + if (png_ptr->transformations & PNG_SHIFT) + png_warning(png_ptr, "PNG_WRITE_SHIFT_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_BGR_SUPPORTED) && defined(PNG_READ_BGR_SUPPORTED) + if (png_ptr->transformations & PNG_BGR) + png_warning(png_ptr, "PNG_WRITE_BGR_SUPPORTED is not defined"); +#endif + +#if !defined(PNG_WRITE_SWAP_SUPPORTED) && defined(PNG_READ_SWAP_SUPPORTED) + if (png_ptr->transformations & PNG_SWAP_BYTES) + png_warning(png_ptr, "PNG_WRITE_SWAP_SUPPORTED is not defined"); +#endif + + png_write_start_row(png_ptr); + } + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced and not interested in row, return */ + if (png_ptr->interlaced && (png_ptr->transformations & PNG_INTERLACE)) + { + switch (png_ptr->pass) + { + case 0: + if (png_ptr->row_number & 0x07) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 1: + if ((png_ptr->row_number & 0x07) || png_ptr->width < 5) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 2: + if ((png_ptr->row_number & 0x07) != 4) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 3: + if ((png_ptr->row_number & 0x03) || png_ptr->width < 3) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 4: + if ((png_ptr->row_number & 0x03) != 2) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 5: + if ((png_ptr->row_number & 0x01) || png_ptr->width < 2) + { + png_write_finish_row(png_ptr); + return; + } + break; + + case 6: + if (!(png_ptr->row_number & 0x01)) + { + png_write_finish_row(png_ptr); + return; + } + break; + + default: /* error: ignore it */ + break; + } + } +#endif + + /* Set up row info for transformations */ + row_info.color_type = png_ptr->color_type; + row_info.width = png_ptr->usr_width; + row_info.channels = png_ptr->usr_channels; + row_info.bit_depth = png_ptr->usr_bit_depth; + row_info.pixel_depth = (png_byte)(row_info.bit_depth * row_info.channels); + row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width); + + png_debug1(3, "row_info->color_type = %d", row_info.color_type); + png_debug1(3, "row_info->width = %u", row_info.width); + png_debug1(3, "row_info->channels = %d", row_info.channels); + png_debug1(3, "row_info->bit_depth = %d", row_info.bit_depth); + png_debug1(3, "row_info->pixel_depth = %d", row_info.pixel_depth); + png_debug1(3, "row_info->rowbytes = %lu", (unsigned long)row_info.rowbytes); + + /* Copy user's row into buffer, leaving room for filter byte. */ + memcpy(png_ptr->row_buf + 1, row, row_info.rowbytes); + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Handle interlacing */ + if (png_ptr->interlaced && png_ptr->pass < 6 && + (png_ptr->transformations & PNG_INTERLACE)) + { + png_do_write_interlace(&row_info, png_ptr->row_buf + 1, png_ptr->pass); + /* This should always get caught above, but still ... */ + if (!(row_info.width)) + { + png_write_finish_row(png_ptr); + return; + } + } +#endif + +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED + /* Handle other transformations */ + if (png_ptr->transformations) + png_do_write_transformations(png_ptr, &row_info); +#endif + + /* At this point the row_info pixel depth must match the 'transformed' depth, + * which is also the output depth. + */ + if (row_info.pixel_depth != png_ptr->pixel_depth || + row_info.pixel_depth != png_ptr->transformed_pixel_depth) + png_error(png_ptr, "internal write transform logic error"); + +#ifdef PNG_MNG_FEATURES_SUPPORTED + /* Write filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not write a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) && + (png_ptr->filter_type == PNG_INTRAPIXEL_DIFFERENCING)) + { + /* Intrapixel differencing */ + png_do_write_intrapixel(&row_info, png_ptr->row_buf + 1); + } +#endif + +/* Added at libpng-1.5.10 */ +#ifdef PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED + /* Check for out-of-range palette index */ + if (row_info.color_type == PNG_COLOR_TYPE_PALETTE && + png_ptr->num_palette_max >= 0) + png_do_check_palette_indexes(png_ptr, &row_info); +#endif + + /* Find a filter if necessary, filter the row and write it out. */ + png_write_find_filter(png_ptr, &row_info); + + if (png_ptr->write_row_fn != NULL) + (*(png_ptr->write_row_fn))(png_ptr, png_ptr->row_number, png_ptr->pass); +} + +#ifdef PNG_WRITE_FLUSH_SUPPORTED +/* Set the automatic flush interval or 0 to turn flushing off */ +void PNGAPI +png_set_flush(png_structrp png_ptr, int nrows) +{ + png_debug(1, "in png_set_flush"); + + if (png_ptr == NULL) + return; + + png_ptr->flush_dist = (nrows < 0 ? 0 : nrows); +} + +/* Flush the current output buffers now */ +void PNGAPI +png_write_flush(png_structrp png_ptr) +{ + png_debug(1, "in png_write_flush"); + + if (png_ptr == NULL) + return; + + /* We have already written out all of the data */ + if (png_ptr->row_number >= png_ptr->num_rows) + return; + + png_compress_IDAT(png_ptr, NULL, 0, Z_SYNC_FLUSH); + png_ptr->flush_rows = 0; + png_flush(png_ptr); +} +#endif /* PNG_WRITE_FLUSH_SUPPORTED */ + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +static void png_reset_filter_heuristics(png_structrp png_ptr);/* forward decl */ +#endif + +/* Free any memory used in png_ptr struct without freeing the struct itself. */ +static void +png_write_destroy(png_structrp png_ptr) +{ + png_debug(1, "in png_write_destroy"); + + /* Free any memory zlib uses */ + if (png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) + deflateEnd(&png_ptr->zstream); + + /* Free our memory. png_free checks NULL for us. */ + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list); + png_free(png_ptr, png_ptr->row_buf); +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_free(png_ptr, png_ptr->prev_row); + png_free(png_ptr, png_ptr->sub_row); + png_free(png_ptr, png_ptr->up_row); + png_free(png_ptr, png_ptr->avg_row); + png_free(png_ptr, png_ptr->paeth_row); +#endif + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + /* Use this to save a little code space, it doesn't free the filter_costs */ + png_reset_filter_heuristics(png_ptr); + png_free(png_ptr, png_ptr->filter_costs); + png_free(png_ptr, png_ptr->inv_filter_costs); +#endif + +#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED + png_free(png_ptr, png_ptr->chunk_list); +#endif + + /* The error handling and memory handling information is left intact at this + * point: the jmp_buf may still have to be freed. See png_destroy_png_struct + * for how this happens. + */ +} + +/* Free all memory used by the write. + * In libpng 1.6.0 this API changed quietly to no longer accept a NULL value for + * *png_ptr_ptr. Prior to 1.6.0 it would accept such a value and it would free + * the passed in info_structs but it would quietly fail to free any of the data + * inside them. In 1.6.0 it quietly does nothing (it has to be quiet because it + * has no png_ptr.) + */ +void PNGAPI +png_destroy_write_struct(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr) +{ + png_debug(1, "in png_destroy_write_struct"); + + if (png_ptr_ptr != NULL) + { + png_structrp png_ptr = *png_ptr_ptr; + + if (png_ptr != NULL) /* added in libpng 1.6.0 */ + { + png_destroy_info_struct(png_ptr, info_ptr_ptr); + + *png_ptr_ptr = NULL; + png_write_destroy(png_ptr); + png_destroy_png_struct(png_ptr); + } + } +} + +/* Allow the application to select one or more row filters to use. */ +void PNGAPI +png_set_filter(png_structrp png_ptr, int method, int filters) +{ + png_debug(1, "in png_set_filter"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_MNG_FEATURES_SUPPORTED + if ((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) && + (method == PNG_INTRAPIXEL_DIFFERENCING)) + method = PNG_FILTER_TYPE_BASE; + +#endif + if (method == PNG_FILTER_TYPE_BASE) + { + switch (filters & (PNG_ALL_FILTERS | 0x07)) + { +#ifdef PNG_WRITE_FILTER_SUPPORTED + case 5: + case 6: + case 7: png_app_error(png_ptr, "Unknown row filter for method 0"); + /* FALL THROUGH */ +#endif /* PNG_WRITE_FILTER_SUPPORTED */ + case PNG_FILTER_VALUE_NONE: + png_ptr->do_filter = PNG_FILTER_NONE; break; + +#ifdef PNG_WRITE_FILTER_SUPPORTED + case PNG_FILTER_VALUE_SUB: + png_ptr->do_filter = PNG_FILTER_SUB; break; + + case PNG_FILTER_VALUE_UP: + png_ptr->do_filter = PNG_FILTER_UP; break; + + case PNG_FILTER_VALUE_AVG: + png_ptr->do_filter = PNG_FILTER_AVG; break; + + case PNG_FILTER_VALUE_PAETH: + png_ptr->do_filter = PNG_FILTER_PAETH; break; + + default: + png_ptr->do_filter = (png_byte)filters; break; +#else + default: + png_app_error(png_ptr, "Unknown row filter for method 0"); +#endif /* PNG_WRITE_FILTER_SUPPORTED */ + } + + /* If we have allocated the row_buf, this means we have already started + * with the image and we should have allocated all of the filter buffers + * that have been selected. If prev_row isn't already allocated, then + * it is too late to start using the filters that need it, since we + * will be missing the data in the previous row. If an application + * wants to start and stop using particular filters during compression, + * it should start out with all of the filters, and then add and + * remove them after the start of compression. + */ + if (png_ptr->row_buf != NULL) + { +#ifdef PNG_WRITE_FILTER_SUPPORTED + if ((png_ptr->do_filter & PNG_FILTER_SUB) && png_ptr->sub_row == NULL) + { + png_ptr->sub_row = (png_bytep)png_malloc(png_ptr, + (png_ptr->rowbytes + 1)); + png_ptr->sub_row[0] = PNG_FILTER_VALUE_SUB; + } + + if ((png_ptr->do_filter & PNG_FILTER_UP) && png_ptr->up_row == NULL) + { + if (png_ptr->prev_row == NULL) + { + png_warning(png_ptr, "Can't add Up filter after starting"); + png_ptr->do_filter = (png_byte)(png_ptr->do_filter & + ~PNG_FILTER_UP); + } + + else + { + png_ptr->up_row = (png_bytep)png_malloc(png_ptr, + (png_ptr->rowbytes + 1)); + png_ptr->up_row[0] = PNG_FILTER_VALUE_UP; + } + } + + if ((png_ptr->do_filter & PNG_FILTER_AVG) && png_ptr->avg_row == NULL) + { + if (png_ptr->prev_row == NULL) + { + png_warning(png_ptr, "Can't add Average filter after starting"); + png_ptr->do_filter = (png_byte)(png_ptr->do_filter & + ~PNG_FILTER_AVG); + } + + else + { + png_ptr->avg_row = (png_bytep)png_malloc(png_ptr, + (png_ptr->rowbytes + 1)); + png_ptr->avg_row[0] = PNG_FILTER_VALUE_AVG; + } + } + + if ((png_ptr->do_filter & PNG_FILTER_PAETH) && + png_ptr->paeth_row == NULL) + { + if (png_ptr->prev_row == NULL) + { + png_warning(png_ptr, "Can't add Paeth filter after starting"); + png_ptr->do_filter &= (png_byte)(~PNG_FILTER_PAETH); + } + + else + { + png_ptr->paeth_row = (png_bytep)png_malloc(png_ptr, + (png_ptr->rowbytes + 1)); + png_ptr->paeth_row[0] = PNG_FILTER_VALUE_PAETH; + } + } + + if (png_ptr->do_filter == PNG_NO_FILTERS) +#endif /* PNG_WRITE_FILTER_SUPPORTED */ + png_ptr->do_filter = PNG_FILTER_NONE; + } + } + else + png_error(png_ptr, "Unknown custom filter method"); +} + +/* This allows us to influence the way in which libpng chooses the "best" + * filter for the current scanline. While the "minimum-sum-of-absolute- + * differences metric is relatively fast and effective, there is some + * question as to whether it can be improved upon by trying to keep the + * filtered data going to zlib more consistent, hopefully resulting in + * better compression. + */ +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* GRR 970116 */ +/* Convenience reset API. */ +static void +png_reset_filter_heuristics(png_structrp png_ptr) +{ + /* Clear out any old values in the 'weights' - this must be done because if + * the app calls set_filter_heuristics multiple times with different + * 'num_weights' values we would otherwise potentially have wrong sized + * arrays. + */ + png_ptr->num_prev_filters = 0; + png_ptr->heuristic_method = PNG_FILTER_HEURISTIC_UNWEIGHTED; + if (png_ptr->prev_filters != NULL) + { + png_bytep old = png_ptr->prev_filters; + png_ptr->prev_filters = NULL; + png_free(png_ptr, old); + } + if (png_ptr->filter_weights != NULL) + { + png_uint_16p old = png_ptr->filter_weights; + png_ptr->filter_weights = NULL; + png_free(png_ptr, old); + } + + if (png_ptr->inv_filter_weights != NULL) + { + png_uint_16p old = png_ptr->inv_filter_weights; + png_ptr->inv_filter_weights = NULL; + png_free(png_ptr, old); + } + + /* Leave the filter_costs - this array is fixed size. */ +} + +static int +png_init_filter_heuristics(png_structrp png_ptr, int heuristic_method, + int num_weights) +{ + if (png_ptr == NULL) + return 0; + + /* Clear out the arrays */ + png_reset_filter_heuristics(png_ptr); + + /* Check arguments; the 'reset' function makes the correct settings for the + * unweighted case, but we must handle the weight case by initializing the + * arrays for the caller. + */ + if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int i; + + if (num_weights > 0) + { + png_ptr->prev_filters = (png_bytep)png_malloc(png_ptr, + (png_uint_32)((sizeof (png_byte)) * num_weights)); + + /* To make sure that the weighting starts out fairly */ + for (i = 0; i < num_weights; i++) + { + png_ptr->prev_filters[i] = 255; + } + + png_ptr->filter_weights = (png_uint_16p)png_malloc(png_ptr, + (png_uint_32)((sizeof (png_uint_16)) * num_weights)); + + png_ptr->inv_filter_weights = (png_uint_16p)png_malloc(png_ptr, + (png_uint_32)((sizeof (png_uint_16)) * num_weights)); + + for (i = 0; i < num_weights; i++) + { + png_ptr->inv_filter_weights[i] = + png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; + } + + /* Safe to set this now */ + png_ptr->num_prev_filters = (png_byte)num_weights; + } + + /* If, in the future, there are other filter methods, this would + * need to be based on png_ptr->filter. + */ + if (png_ptr->filter_costs == NULL) + { + png_ptr->filter_costs = (png_uint_16p)png_malloc(png_ptr, + (png_uint_32)((sizeof (png_uint_16)) * PNG_FILTER_VALUE_LAST)); + + png_ptr->inv_filter_costs = (png_uint_16p)png_malloc(png_ptr, + (png_uint_32)((sizeof (png_uint_16)) * PNG_FILTER_VALUE_LAST)); + } + + for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) + { + png_ptr->inv_filter_costs[i] = + png_ptr->filter_costs[i] = PNG_COST_FACTOR; + } + + /* All the arrays are inited, safe to set this: */ + png_ptr->heuristic_method = PNG_FILTER_HEURISTIC_WEIGHTED; + + /* Return the 'ok' code. */ + return 1; + } + else if (heuristic_method == PNG_FILTER_HEURISTIC_DEFAULT || + heuristic_method == PNG_FILTER_HEURISTIC_UNWEIGHTED) + { + return 1; + } + else + { + png_warning(png_ptr, "Unknown filter heuristic method"); + return 0; + } +} + +/* Provide floating and fixed point APIs */ +#ifdef PNG_FLOATING_POINT_SUPPORTED +void PNGAPI +png_set_filter_heuristics(png_structrp png_ptr, int heuristic_method, + int num_weights, png_const_doublep filter_weights, + png_const_doublep filter_costs) +{ + png_debug(1, "in png_set_filter_heuristics"); + + /* The internal API allocates all the arrays and ensures that the elements of + * those arrays are set to the default value. + */ + if (!png_init_filter_heuristics(png_ptr, heuristic_method, num_weights)) + return; + + /* If using the weighted method copy in the weights. */ + if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int i; + for (i = 0; i < num_weights; i++) + { + if (filter_weights[i] <= 0.0) + { + png_ptr->inv_filter_weights[i] = + png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; + } + + else + { + png_ptr->inv_filter_weights[i] = + (png_uint_16)(PNG_WEIGHT_FACTOR*filter_weights[i]+.5); + + png_ptr->filter_weights[i] = + (png_uint_16)(PNG_WEIGHT_FACTOR/filter_weights[i]+.5); + } + } + + /* Here is where we set the relative costs of the different filters. We + * should take the desired compression level into account when setting + * the costs, so that Paeth, for instance, has a high relative cost at low + * compression levels, while it has a lower relative cost at higher + * compression settings. The filter types are in order of increasing + * relative cost, so it would be possible to do this with an algorithm. + */ + for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) if (filter_costs[i] >= 1.0) + { + png_ptr->inv_filter_costs[i] = + (png_uint_16)(PNG_COST_FACTOR / filter_costs[i] + .5); + + png_ptr->filter_costs[i] = + (png_uint_16)(PNG_COST_FACTOR * filter_costs[i] + .5); + } + } +} +#endif /* FLOATING_POINT */ + +#ifdef PNG_FIXED_POINT_SUPPORTED +void PNGAPI +png_set_filter_heuristics_fixed(png_structrp png_ptr, int heuristic_method, + int num_weights, png_const_fixed_point_p filter_weights, + png_const_fixed_point_p filter_costs) +{ + png_debug(1, "in png_set_filter_heuristics_fixed"); + + /* The internal API allocates all the arrays and ensures that the elements of + * those arrays are set to the default value. + */ + if (!png_init_filter_heuristics(png_ptr, heuristic_method, num_weights)) + return; + + /* If using the weighted method copy in the weights. */ + if (heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int i; + for (i = 0; i < num_weights; i++) + { + if (filter_weights[i] <= 0) + { + png_ptr->inv_filter_weights[i] = + png_ptr->filter_weights[i] = PNG_WEIGHT_FACTOR; + } + + else + { + png_ptr->inv_filter_weights[i] = (png_uint_16) + ((PNG_WEIGHT_FACTOR*filter_weights[i]+PNG_FP_HALF)/PNG_FP_1); + + png_ptr->filter_weights[i] = (png_uint_16)((PNG_WEIGHT_FACTOR* + PNG_FP_1+(filter_weights[i]/2))/filter_weights[i]); + } + } + + /* Here is where we set the relative costs of the different filters. We + * should take the desired compression level into account when setting + * the costs, so that Paeth, for instance, has a high relative cost at low + * compression levels, while it has a lower relative cost at higher + * compression settings. The filter types are in order of increasing + * relative cost, so it would be possible to do this with an algorithm. + */ + for (i = 0; i < PNG_FILTER_VALUE_LAST; i++) + if (filter_costs[i] >= PNG_FP_1) + { + png_uint_32 tmp; + + /* Use a 32 bit unsigned temporary here because otherwise the + * intermediate value will be a 32 bit *signed* integer (ANSI rules) + * and this will get the wrong answer on division. + */ + tmp = PNG_COST_FACTOR*PNG_FP_1 + (filter_costs[i]/2); + tmp /= filter_costs[i]; + + png_ptr->inv_filter_costs[i] = (png_uint_16)tmp; + + tmp = PNG_COST_FACTOR * filter_costs[i] + PNG_FP_HALF; + tmp /= PNG_FP_1; + + png_ptr->filter_costs[i] = (png_uint_16)tmp; + } + } +} +#endif /* FIXED_POINT */ +#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */ + +void PNGAPI +png_set_compression_level(png_structrp png_ptr, int level) +{ + png_debug(1, "in png_set_compression_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_level = level; +} + +void PNGAPI +png_set_compression_mem_level(png_structrp png_ptr, int mem_level) +{ + png_debug(1, "in png_set_compression_mem_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_mem_level = mem_level; +} + +void PNGAPI +png_set_compression_strategy(png_structrp png_ptr, int strategy) +{ + png_debug(1, "in png_set_compression_strategy"); + + if (png_ptr == NULL) + return; + + /* The flag setting here prevents the libpng dynamic selection of strategy. + */ + png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY; + png_ptr->zlib_strategy = strategy; +} + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +void PNGAPI +png_set_compression_window_bits(png_structrp png_ptr, int window_bits) +{ + if (png_ptr == NULL) + return; + + /* Prior to 1.6.0 this would warn but then set the window_bits value, this + * meant that negative window bits values could be selected which would cause + * libpng to write a non-standard PNG file with raw deflate or gzip + * compressed IDAT or ancillary chunks. Such files can be read and there is + * no warning on read, so this seems like a very bad idea. + */ + if (window_bits > 15) + { + png_warning(png_ptr, "Only compression windows <= 32k supported by PNG"); + window_bits = 15; + } + + else if (window_bits < 8) + { + png_warning(png_ptr, "Only compression windows >= 256 supported by PNG"); + window_bits = 8; + } + + png_ptr->zlib_window_bits = window_bits; +} + +void PNGAPI +png_set_compression_method(png_structrp png_ptr, int method) +{ + png_debug(1, "in png_set_compression_method"); + + if (png_ptr == NULL) + return; + + /* This would produce an invalid PNG file if it worked, but it doesn't and + * deflate will fault it, so it is harmless to just warn here. + */ + if (method != 8) + png_warning(png_ptr, "Only compression method 8 is supported by PNG"); + + png_ptr->zlib_method = method; +} + +/* The following were added to libpng-1.5.4 */ +#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +void PNGAPI +png_set_text_compression_level(png_structrp png_ptr, int level) +{ + png_debug(1, "in png_set_text_compression_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_level = level; +} + +void PNGAPI +png_set_text_compression_mem_level(png_structrp png_ptr, int mem_level) +{ + png_debug(1, "in png_set_text_compression_mem_level"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_mem_level = mem_level; +} + +void PNGAPI +png_set_text_compression_strategy(png_structrp png_ptr, int strategy) +{ + png_debug(1, "in png_set_text_compression_strategy"); + + if (png_ptr == NULL) + return; + + png_ptr->zlib_text_strategy = strategy; +} + +/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a + * smaller value of window_bits if it can do so safely. + */ +void PNGAPI +png_set_text_compression_window_bits(png_structrp png_ptr, int window_bits) +{ + if (png_ptr == NULL) + return; + + if (window_bits > 15) + { + png_warning(png_ptr, "Only compression windows <= 32k supported by PNG"); + window_bits = 15; + } + + else if (window_bits < 8) + { + png_warning(png_ptr, "Only compression windows >= 256 supported by PNG"); + window_bits = 8; + } + + png_ptr->zlib_text_window_bits = window_bits; +} + +void PNGAPI +png_set_text_compression_method(png_structrp png_ptr, int method) +{ + png_debug(1, "in png_set_text_compression_method"); + + if (png_ptr == NULL) + return; + + if (method != 8) + png_warning(png_ptr, "Only compression method 8 is supported by PNG"); + + png_ptr->zlib_text_method = method; +} +#endif /* PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED */ +/* end of API added to libpng-1.5.4 */ + +void PNGAPI +png_set_write_status_fn(png_structrp png_ptr, png_write_status_ptr write_row_fn) +{ + if (png_ptr == NULL) + return; + + png_ptr->write_row_fn = write_row_fn; +} + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED +void PNGAPI +png_set_write_user_transform_fn(png_structrp png_ptr, png_user_transform_ptr + write_user_transform_fn) +{ + png_debug(1, "in png_set_write_user_transform_fn"); + + if (png_ptr == NULL) + return; + + png_ptr->transformations |= PNG_USER_TRANSFORM; + png_ptr->write_user_transform_fn = write_user_transform_fn; +} +#endif + + +#ifdef PNG_INFO_IMAGE_SUPPORTED +void PNGAPI +png_write_png(png_structrp png_ptr, png_inforp info_ptr, + int transforms, voidp params) +{ + if (png_ptr == NULL || info_ptr == NULL) + return; + + /* Write the file header information. */ + png_write_info(png_ptr, info_ptr); + + /* ------ these transformations don't touch the info structure ------- */ + +#ifdef PNG_WRITE_INVERT_SUPPORTED + /* Invert monochrome pixels */ + if (transforms & PNG_TRANSFORM_INVERT_MONO) + png_set_invert_mono(png_ptr); +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED + /* Shift the pixels up to a legal bit depth and fill in + * as appropriate to correctly scale the image. + */ + if ((transforms & PNG_TRANSFORM_SHIFT) + && (info_ptr->valid & PNG_INFO_sBIT)) + png_set_shift(png_ptr, &info_ptr->sig_bit); +#endif + +#ifdef PNG_WRITE_PACK_SUPPORTED + /* Pack pixels into bytes */ + if (transforms & PNG_TRANSFORM_PACKING) + png_set_packing(png_ptr); +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED + /* Swap location of alpha bytes from ARGB to RGBA */ + if (transforms & PNG_TRANSFORM_SWAP_ALPHA) + png_set_swap_alpha(png_ptr); +#endif + +#ifdef PNG_WRITE_FILLER_SUPPORTED + /* Pack XRGB/RGBX/ARGB/RGBA into RGB (4 channels -> 3 channels) */ + if (transforms & PNG_TRANSFORM_STRIP_FILLER_AFTER) + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + + else if (transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) + png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE); +#endif + +#ifdef PNG_WRITE_BGR_SUPPORTED + /* Flip BGR pixels to RGB */ + if (transforms & PNG_TRANSFORM_BGR) + png_set_bgr(png_ptr); +#endif + +#ifdef PNG_WRITE_SWAP_SUPPORTED + /* Swap bytes of 16-bit files to most significant byte first */ + if (transforms & PNG_TRANSFORM_SWAP_ENDIAN) + png_set_swap(png_ptr); +#endif + +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED + /* Swap bits of 1, 2, 4 bit packed pixel formats */ + if (transforms & PNG_TRANSFORM_PACKSWAP) + png_set_packswap(png_ptr); +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + /* Invert the alpha channel from opacity to transparency */ + if (transforms & PNG_TRANSFORM_INVERT_ALPHA) + png_set_invert_alpha(png_ptr); +#endif + + /* ----------------------- end of transformations ------------------- */ + + /* Write the bits */ + if (info_ptr->valid & PNG_INFO_IDAT) + png_write_image(png_ptr, info_ptr->row_pointers); + + /* It is REQUIRED to call this to finish writing the rest of the file */ + png_write_end(png_ptr, info_ptr); + + PNG_UNUSED(transforms) /* Quiet compiler warnings */ + PNG_UNUSED(params) +} +#endif + + +#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED +#ifdef PNG_STDIO_SUPPORTED /* currently required for png_image_write_* */ +/* Initialize the write structure - general purpose utility. */ +static int +png_image_write_init(png_imagep image) +{ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, image, + png_safe_error, png_safe_warning); + + if (png_ptr != NULL) + { + png_infop info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr != NULL) + { + png_controlp control = png_voidcast(png_controlp, + png_malloc_warn(png_ptr, (sizeof *control))); + + if (control != NULL) + { + memset(control, 0, (sizeof *control)); + + control->png_ptr = png_ptr; + control->info_ptr = info_ptr; + control->for_write = 1; + + image->opaque = control; + return 1; + } + + /* Error clean up */ + png_destroy_info_struct(png_ptr, &info_ptr); + } + + png_destroy_write_struct(&png_ptr, NULL); + } + + return png_image_error(image, "png_image_write_: out of memory"); +} + +/* Arguments to png_image_write_main: */ +typedef struct +{ + /* Arguments: */ + png_imagep image; + png_const_voidp buffer; + png_int_32 row_stride; + png_const_voidp colormap; + int convert_to_8bit; + /* Local variables: */ + png_const_voidp first_row; + ptrdiff_t row_bytes; + png_voidp local_row; +} png_image_write_control; + +/* Write png_uint_16 input to a 16-bit PNG; the png_ptr has already been set to + * do any necessary byte swapping. The component order is defined by the + * png_image format value. + */ +static int +png_write_image_16bit(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + + png_const_uint_16p input_row = png_voidcast(png_const_uint_16p, + display->first_row); + png_uint_16p output_row = png_voidcast(png_uint_16p, display->local_row); + png_uint_16p row_end; + const int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1; + int aindex = 0; + png_uint_32 y = image->height; + + if (image->format & PNG_FORMAT_FLAG_ALPHA) + { + if (image->format & PNG_FORMAT_FLAG_AFIRST) + { + aindex = -1; + ++input_row; /* To point to the first component */ + ++output_row; + } + + else + aindex = channels; + } + + else + png_error(png_ptr, "png_write_image: internal call error"); + + /* Work out the output row end and count over this, note that the increment + * above to 'row' means that row_end can actually be beyond the end of the + * row; this is correct. + */ + row_end = output_row + image->width * (channels+1); + + while (y-- > 0) + { + png_const_uint_16p in_ptr = input_row; + png_uint_16p out_ptr = output_row; + + while (out_ptr < row_end) + { + const png_uint_16 alpha = in_ptr[aindex]; + png_uint_32 reciprocal = 0; + int c; + + out_ptr[aindex] = alpha; + + /* Calculate a reciprocal. The correct calculation is simply + * component/alpha*65535 << 15. (I.e. 15 bits of precision); this + * allows correct rounding by adding .5 before the shift. 'reciprocal' + * is only initialized when required. + */ + if (alpha > 0 && alpha < 65535) + reciprocal = ((0xffff<<15)+(alpha>>1))/alpha; + + c = channels; + do /* always at least one channel */ + { + png_uint_16 component = *in_ptr++; + + /* The following gives 65535 for an alpha of 0, which is fine, + * otherwise if 0/0 is represented as some other value there is more + * likely to be a discontinuity which will probably damage + * compression when moving from a fully transparent area to a + * nearly transparent one. (The assumption here is that opaque + * areas tend not to be 0 intensity.) + */ + if (component >= alpha) + component = 65535; + + /* component 0 && alpha < 65535) + { + png_uint_32 calc = component * reciprocal; + calc += 16384; /* round to nearest */ + component = (png_uint_16)(calc >> 15); + } + + *out_ptr++ = component; + } + while (--c > 0); + + /* Skip to next component (skip the intervening alpha channel) */ + ++in_ptr; + ++out_ptr; + } + + png_write_row(png_ptr, png_voidcast(png_const_bytep, display->local_row)); + input_row += display->row_bytes/(sizeof (png_uint_16)); + } + + return 1; +} + +/* Given 16-bit input (1 to 4 channels) write 8-bit output. If an alpha channel + * is present it must be removed from the components, the components are then + * written in sRGB encoding. No components are added or removed. + * + * Calculate an alpha reciprocal to reverse pre-multiplication. As above the + * calculation can be done to 15 bits of accuracy; however, the output needs to + * be scaled in the range 0..255*65535, so include that scaling here. + */ +#define UNP_RECIPROCAL(alpha) ((((0xffff*0xff)<<7)+(alpha>>1))/alpha) + +static png_byte +png_unpremultiply(png_uint_32 component, png_uint_32 alpha, + png_uint_32 reciprocal/*from the above macro*/) +{ + /* The following gives 1.0 for an alpha of 0, which is fine, otherwise if 0/0 + * is represented as some other value there is more likely to be a + * discontinuity which will probably damage compression when moving from a + * fully transparent area to a nearly transparent one. (The assumption here + * is that opaque areas tend not to be 0 intensity.) + * + * There is a rounding problem here; if alpha is less than 128 it will end up + * as 0 when scaled to 8 bits. To avoid introducing spurious colors into the + * output change for this too. + */ + if (component >= alpha || alpha < 128) + return 255; + + /* component 0) + { + /* The test is that alpha/257 (rounded) is less than 255, the first value + * that becomes 255 is 65407. + * NOTE: this must agree with the PNG_DIV257 macro (which must, therefore, + * be exact!) [Could also test reciprocal != 0] + */ + if (alpha < 65407) + { + component *= reciprocal; + component += 64; /* round to nearest */ + component >>= 7; + } + + else + component *= 255; + + /* Convert the component to sRGB. */ + return (png_byte)PNG_sRGB_FROM_LINEAR(component); + } + + else + return 0; +} + +static int +png_write_image_8bit(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + + png_const_uint_16p input_row = png_voidcast(png_const_uint_16p, + display->first_row); + png_bytep output_row = png_voidcast(png_bytep, display->local_row); + png_uint_32 y = image->height; + const int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1; + + if (image->format & PNG_FORMAT_FLAG_ALPHA) + { + png_bytep row_end; + int aindex; + + if (image->format & PNG_FORMAT_FLAG_AFIRST) + { + aindex = -1; + ++input_row; /* To point to the first component */ + ++output_row; + } + + else + aindex = channels; + + /* Use row_end in place of a loop counter: */ + row_end = output_row + image->width * (channels+1); + + while (y-- > 0) + { + png_const_uint_16p in_ptr = input_row; + png_bytep out_ptr = output_row; + + while (out_ptr < row_end) + { + png_uint_16 alpha = in_ptr[aindex]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); + png_uint_32 reciprocal = 0; + int c; + + /* Scale and write the alpha channel. */ + out_ptr[aindex] = alphabyte; + + if (alphabyte > 0 && alphabyte < 255) + reciprocal = UNP_RECIPROCAL(alpha); + + c = channels; + do /* always at least one channel */ + *out_ptr++ = png_unpremultiply(*in_ptr++, alpha, reciprocal); + while (--c > 0); + + /* Skip to next component (skip the intervening alpha channel) */ + ++in_ptr; + ++out_ptr; + } /* while out_ptr < row_end */ + + png_write_row(png_ptr, png_voidcast(png_const_bytep, + display->local_row)); + input_row += display->row_bytes/(sizeof (png_uint_16)); + } /* while y */ + } + + else + { + /* No alpha channel, so the row_end really is the end of the row and it + * is sufficient to loop over the components one by one. + */ + png_bytep row_end = output_row + image->width * channels; + + while (y-- > 0) + { + png_const_uint_16p in_ptr = input_row; + png_bytep out_ptr = output_row; + + while (out_ptr < row_end) + { + png_uint_32 component = *in_ptr++; + + component *= 255; + *out_ptr++ = (png_byte)PNG_sRGB_FROM_LINEAR(component); + } + + png_write_row(png_ptr, output_row); + input_row += display->row_bytes/(sizeof (png_uint_16)); + } + } + + return 1; +} + +static void +png_image_set_PLTE(png_image_write_control *display) +{ + const png_imagep image = display->image; + const void *cmap = display->colormap; + const int entries = image->colormap_entries > 256 ? 256 : + (int)image->colormap_entries; + + /* NOTE: the caller must check for cmap != NULL and entries != 0 */ + const png_uint_32 format = image->format; + const int channels = PNG_IMAGE_SAMPLE_CHANNELS(format); + +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int afirst = (format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif + +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int bgr = (format & PNG_FORMAT_FLAG_BGR) ? 2 : 0; +# else +# define bgr 0 +# endif + + int i, num_trans; + png_color palette[256]; + png_byte tRNS[256]; + + memset(tRNS, 255, (sizeof tRNS)); + memset(palette, 0, (sizeof palette)); + + for (i=num_trans=0; i= 3) /* RGB */ + { + palette[i].blue = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[(2 ^ bgr)]); + palette[i].green = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[1]); + palette[i].red = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[bgr]); + } + + else /* Gray */ + palette[i].blue = palette[i].red = palette[i].green = + (png_byte)PNG_sRGB_FROM_LINEAR(255 * *entry); + } + + else /* alpha */ + { + png_uint_16 alpha = entry[afirst ? 0 : channels-1]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); + png_uint_32 reciprocal = 0; + + /* Calculate a reciprocal, as in the png_write_image_8bit code above + * this is designed to produce a value scaled to 255*65535 when + * divided by 128 (i.e. asr 7). + */ + if (alphabyte > 0 && alphabyte < 255) + reciprocal = (((0xffff*0xff)<<7)+(alpha>>1))/alpha; + + tRNS[i] = alphabyte; + if (alphabyte < 255) + num_trans = i+1; + + if (channels >= 3) /* RGB */ + { + palette[i].blue = png_unpremultiply(entry[afirst + (2 ^ bgr)], + alpha, reciprocal); + palette[i].green = png_unpremultiply(entry[afirst + 1], alpha, + reciprocal); + palette[i].red = png_unpremultiply(entry[afirst + bgr], alpha, + reciprocal); + } + + else /* gray */ + palette[i].blue = palette[i].red = palette[i].green = + png_unpremultiply(entry[afirst], alpha, reciprocal); + } + } + + else /* Color-map has sRGB values */ + { + png_const_bytep entry = png_voidcast(png_const_bytep, cmap); + + entry += i * channels; + + switch (channels) + { + case 4: + tRNS[i] = entry[afirst ? 0 : 3]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALL THROUGH */ + case 3: + palette[i].blue = entry[afirst + (2 ^ bgr)]; + palette[i].green = entry[afirst + 1]; + palette[i].red = entry[afirst + bgr]; + break; + + case 2: + tRNS[i] = entry[1 ^ afirst]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALL THROUGH */ + case 1: + palette[i].blue = palette[i].red = palette[i].green = + entry[afirst]; + break; + + default: + break; + } + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + + png_set_PLTE(image->opaque->png_ptr, image->opaque->info_ptr, palette, + entries); + + if (num_trans > 0) + png_set_tRNS(image->opaque->png_ptr, image->opaque->info_ptr, tRNS, + num_trans, NULL); + + image->colormap_entries = entries; +} + +static int +png_image_write_main(png_voidp argument) +{ + png_image_write_control *display = png_voidcast(png_image_write_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + png_inforp info_ptr = image->opaque->info_ptr; + png_uint_32 format = image->format; + + int colormap = (format & PNG_FORMAT_FLAG_COLORMAP) != 0; + int linear = !colormap && (format & PNG_FORMAT_FLAG_LINEAR) != 0; /* input */ + int alpha = !colormap && (format & PNG_FORMAT_FLAG_ALPHA) != 0; + int write_16bit = linear && !colormap && !display->convert_to_8bit; + +# ifdef PNG_BENIGN_ERRORS_SUPPORTED + /* Make sure we error out on any bad situation */ + png_set_benign_errors(png_ptr, 0/*error*/); +# endif + + /* Default the 'row_stride' parameter if required. */ + if (display->row_stride == 0) + display->row_stride = PNG_IMAGE_ROW_STRIDE(*image); + + /* Set the required transforms then write the rows in the correct order. */ + if (format & PNG_FORMAT_FLAG_COLORMAP) + { + if (display->colormap != NULL && image->colormap_entries > 0) + { + png_uint_32 entries = image->colormap_entries; + + png_set_IHDR(png_ptr, info_ptr, image->width, image->height, + entries > 16 ? 8 : (entries > 4 ? 4 : (entries > 2 ? 2 : 1)), + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_image_set_PLTE(display); + } + + else + png_error(image->opaque->png_ptr, + "no color-map for color-mapped image"); + } + + else + png_set_IHDR(png_ptr, info_ptr, image->width, image->height, + write_16bit ? 16 : 8, + ((format & PNG_FORMAT_FLAG_COLOR) ? PNG_COLOR_MASK_COLOR : 0) + + ((format & PNG_FORMAT_FLAG_ALPHA) ? PNG_COLOR_MASK_ALPHA : 0), + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + /* Counter-intuitively the data transformations must be called *after* + * png_write_info, not before as in the read code, but the 'set' functions + * must still be called before. Just set the color space information, never + * write an interlaced image. + */ + + if (write_16bit) + { + /* The gamma here is 1.0 (linear) and the cHRM chunk matches sRGB. */ + png_set_gAMA_fixed(png_ptr, info_ptr, PNG_GAMMA_LINEAR); + + if (!(image->flags & PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB)) + png_set_cHRM_fixed(png_ptr, info_ptr, + /* color x y */ + /* white */ 31270, 32900, + /* red */ 64000, 33000, + /* green */ 30000, 60000, + /* blue */ 15000, 6000 + ); + } + + else if (!(image->flags & PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB)) + png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); + + /* Else writing an 8-bit file and the *colors* aren't sRGB, but the 8-bit + * space must still be gamma encoded. + */ + else + png_set_gAMA_fixed(png_ptr, info_ptr, PNG_GAMMA_sRGB_INVERSE); + + /* Write the file header. */ + png_write_info(png_ptr, info_ptr); + + /* Now set up the data transformations (*after* the header is written), + * remove the handled transformations from the 'format' flags for checking. + * + * First check for a little endian system if writing 16 bit files. + */ + if (write_16bit) + { + PNG_CONST png_uint_16 le = 0x0001; + + if (*(png_const_bytep)&le) + png_set_swap(png_ptr); + } + +# ifdef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED + if (format & PNG_FORMAT_FLAG_BGR) + { + if (!colormap && (format & PNG_FORMAT_FLAG_COLOR) != 0) + png_set_bgr(png_ptr); + format &= ~PNG_FORMAT_FLAG_BGR; + } +# endif + +# ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED + if (format & PNG_FORMAT_FLAG_AFIRST) + { + if (!colormap && (format & PNG_FORMAT_FLAG_ALPHA) != 0) + png_set_swap_alpha(png_ptr); + format &= ~PNG_FORMAT_FLAG_AFIRST; + } +# endif + + /* If there are 16 or fewer color-map entries we wrote a lower bit depth + * above, but the application data is still byte packed. + */ + if (colormap && image->colormap_entries <= 16) + png_set_packing(png_ptr); + + /* That should have handled all (both) the transforms. */ + if ((format & ~(png_uint_32)(PNG_FORMAT_FLAG_COLOR | PNG_FORMAT_FLAG_LINEAR | + PNG_FORMAT_FLAG_ALPHA | PNG_FORMAT_FLAG_COLORMAP)) != 0) + png_error(png_ptr, "png_write_image: unsupported transformation"); + + { + png_const_bytep row = png_voidcast(png_const_bytep, display->buffer); + ptrdiff_t row_bytes = display->row_stride; + + if (linear) + row_bytes *= (sizeof (png_uint_16)); + + if (row_bytes < 0) + row += (image->height-1) * (-row_bytes); + + display->first_row = row; + display->row_bytes = row_bytes; + } + + /* Apply 'fast' options if the flag is set. */ + if ((image->flags & PNG_IMAGE_FLAG_FAST) != 0) + { + png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_NO_FILTERS); + /* NOTE: determined by experiment using pngstest, this reflects some + * balance between the time to write the image once and the time to read + * it about 50 times. The speed-up in pngstest was about 10-20% of the + * total (user) time on a heavily loaded system. + */ + png_set_compression_level(png_ptr, 3); + } + + /* Check for the cases that currently require a pre-transform on the row + * before it is written. This only applies when the input is 16-bit and + * either there is an alpha channel or it is converted to 8-bit. + */ + if ((linear && alpha) || (!colormap && display->convert_to_8bit)) + { + png_bytep row = png_voidcast(png_bytep, png_malloc(png_ptr, + png_get_rowbytes(png_ptr, info_ptr))); + int result; + + display->local_row = row; + if (write_16bit) + result = png_safe_execute(image, png_write_image_16bit, display); + else + result = png_safe_execute(image, png_write_image_8bit, display); + display->local_row = NULL; + + png_free(png_ptr, row); + + /* Skip the 'write_end' on error: */ + if (!result) + return 0; + } + + /* Otherwise this is the case where the input is in a format currently + * supported by the rest of the libpng write code; call it directly. + */ + else + { + png_const_bytep row = png_voidcast(png_const_bytep, display->first_row); + ptrdiff_t row_bytes = display->row_bytes; + png_uint_32 y = image->height; + + while (y-- > 0) + { + png_write_row(png_ptr, row); + row += row_bytes; + } + } + + png_write_end(png_ptr, info_ptr); + return 1; +} + +int PNGAPI +png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, + const void *buffer, png_int_32 row_stride, const void *colormap) +{ + /* Write the image to the given (FILE*). */ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file != NULL) + { + if (png_image_write_init(image)) + { + png_image_write_control display; + int result; + + /* This is slightly evil, but png_init_io doesn't do anything other + * than this and we haven't changed the standard IO functions so + * this saves a 'safe' function. + */ + image->opaque->png_ptr->io_ptr = file; + + memset(&display, 0, (sizeof display)); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.convert_to_8bit = convert_to_8bit; + + result = png_safe_execute(image, png_image_write_main, &display); + png_image_free(image); + return result; + } + + else + return 0; + } + + else + return png_image_error(image, + "png_image_write_to_stdio: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_write_to_stdio: incorrect PNG_IMAGE_VERSION"); + + else + return 0; +} + +int PNGAPI +png_image_write_to_file(png_imagep image, const char *file_name, + int convert_to_8bit, const void *buffer, png_int_32 row_stride, + const void *colormap) +{ + /* Write the image to the named file. */ + if (image != NULL && image->version == PNG_IMAGE_VERSION) + { + if (file_name != NULL) + { + FILE *fp = fopen(file_name, "wb"); + + if (fp != NULL) + { + if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, + row_stride, colormap)) + { + int error; /* from fflush/fclose */ + + /* Make sure the file is flushed correctly. */ + if (fflush(fp) == 0 && ferror(fp) == 0) + { + if (fclose(fp) == 0) + return 1; + + error = errno; /* from fclose */ + } + + else + { + error = errno; /* from fflush or ferror */ + (void)fclose(fp); + } + + (void)remove(file_name); + /* The image has already been cleaned up; this is just used to + * set the error (because the original write succeeded). + */ + return png_image_error(image, strerror(error)); + } + + else + { + /* Clean up: just the opened file. */ + (void)fclose(fp); + (void)remove(file_name); + return 0; + } + } + + else + return png_image_error(image, strerror(errno)); + } + + else + return png_image_error(image, + "png_image_write_to_file: invalid argument"); + } + + else if (image != NULL) + return png_image_error(image, + "png_image_write_to_file: incorrect PNG_IMAGE_VERSION"); + + else + return 0; +} +#endif /* PNG_STDIO_SUPPORTED */ +#endif /* SIMPLIFIED_WRITE */ +#endif /* PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwtran.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwtran.c new file mode 100644 index 0000000..2cdd7c9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwtran.c @@ -0,0 +1,637 @@ + +/* pngwtran.c - transforms the data in a row for PNG writers + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED + +#ifdef PNG_WRITE_TRANSFORMS_SUPPORTED +/* Transform the data according to the user's wishes. The order of + * transformations is significant. + */ +void /* PRIVATE */ +png_do_write_transformations(png_structrp png_ptr, png_row_infop row_info) +{ + png_debug(1, "in png_do_write_transformations"); + + if (png_ptr == NULL) + return; + +#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED + if (png_ptr->transformations & PNG_USER_TRANSFORM) + if (png_ptr->write_user_transform_fn != NULL) + (*(png_ptr->write_user_transform_fn)) /* User write transform + function */ + (png_ptr, /* png_ptr */ + row_info, /* row_info: */ + /* png_uint_32 width; width of row */ + /* png_size_t rowbytes; number of bytes in row */ + /* png_byte color_type; color type of pixels */ + /* png_byte bit_depth; bit depth of samples */ + /* png_byte channels; number of channels (1-4) */ + /* png_byte pixel_depth; bits per pixel (depth*channels) */ + png_ptr->row_buf + 1); /* start of pixel data for row */ +#endif + +#ifdef PNG_WRITE_FILLER_SUPPORTED + if (png_ptr->transformations & PNG_FILLER) + png_do_strip_channel(row_info, png_ptr->row_buf + 1, + !(png_ptr->flags & PNG_FLAG_FILLER_AFTER)); +#endif + +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED + if (png_ptr->transformations & PNG_PACKSWAP) + png_do_packswap(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_PACK_SUPPORTED + if (png_ptr->transformations & PNG_PACK) + png_do_pack(row_info, png_ptr->row_buf + 1, + (png_uint_32)png_ptr->bit_depth); +#endif + +#ifdef PNG_WRITE_SWAP_SUPPORTED + if (png_ptr->transformations & PNG_SWAP_BYTES) + png_do_swap(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED + if (png_ptr->transformations & PNG_SHIFT) + png_do_shift(row_info, png_ptr->row_buf + 1, + &(png_ptr->shift)); +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_SWAP_ALPHA) + png_do_write_swap_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_ALPHA) + png_do_write_invert_alpha(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_BGR_SUPPORTED + if (png_ptr->transformations & PNG_BGR) + png_do_bgr(row_info, png_ptr->row_buf + 1); +#endif + +#ifdef PNG_WRITE_INVERT_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_MONO) + png_do_invert(row_info, png_ptr->row_buf + 1); +#endif +} + +#ifdef PNG_WRITE_PACK_SUPPORTED +/* Pack pixels into bytes. Pass the true bit depth in bit_depth. The + * row_info bit depth should be 8 (one pixel per byte). The channels + * should be 1 (this only happens on grayscale and paletted images). + */ +void /* PRIVATE */ +png_do_pack(png_row_infop row_info, png_bytep row, png_uint_32 bit_depth) +{ + png_debug(1, "in png_do_pack"); + + if (row_info->bit_depth == 8 && + row_info->channels == 1) + { + switch ((int)bit_depth) + { + case 1: + { + png_bytep sp, dp; + int mask, v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + mask = 0x80; + v = 0; + + for (i = 0; i < row_width; i++) + { + if (*sp != 0) + v |= mask; + + sp++; + + if (mask > 1) + mask >>= 1; + + else + { + mask = 0x80; + *dp = (png_byte)v; + dp++; + v = 0; + } + } + + if (mask != 0x80) + *dp = (png_byte)v; + + break; + } + + case 2: + { + png_bytep sp, dp; + int shift, v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + shift = 6; + v = 0; + + for (i = 0; i < row_width; i++) + { + png_byte value; + + value = (png_byte)(*sp & 0x03); + v |= (value << shift); + + if (shift == 0) + { + shift = 6; + *dp = (png_byte)v; + dp++; + v = 0; + } + + else + shift -= 2; + + sp++; + } + + if (shift != 6) + *dp = (png_byte)v; + + break; + } + + case 4: + { + png_bytep sp, dp; + int shift, v; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + sp = row; + dp = row; + shift = 4; + v = 0; + + for (i = 0; i < row_width; i++) + { + png_byte value; + + value = (png_byte)(*sp & 0x0f); + v |= (value << shift); + + if (shift == 0) + { + shift = 4; + *dp = (png_byte)v; + dp++; + v = 0; + } + + else + shift -= 4; + + sp++; + } + + if (shift != 4) + *dp = (png_byte)v; + + break; + } + + default: + break; + } + + row_info->bit_depth = (png_byte)bit_depth; + row_info->pixel_depth = (png_byte)(bit_depth * row_info->channels); + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_info->width); + } +} +#endif + +#ifdef PNG_WRITE_SHIFT_SUPPORTED +/* Shift pixel values to take advantage of whole range. Pass the + * true number of bits in bit_depth. The row should be packed + * according to row_info->bit_depth. Thus, if you had a row of + * bit depth 4, but the pixels only had values from 0 to 7, you + * would pass 3 as bit_depth, and this routine would translate the + * data to 0 to 15. + */ +void /* PRIVATE */ +png_do_shift(png_row_infop row_info, png_bytep row, + png_const_color_8p bit_depth) +{ + png_debug(1, "in png_do_shift"); + + if (row_info->color_type != PNG_COLOR_TYPE_PALETTE) + { + int shift_start[4], shift_dec[4]; + int channels = 0; + + if (row_info->color_type & PNG_COLOR_MASK_COLOR) + { + shift_start[channels] = row_info->bit_depth - bit_depth->red; + shift_dec[channels] = bit_depth->red; + channels++; + + shift_start[channels] = row_info->bit_depth - bit_depth->green; + shift_dec[channels] = bit_depth->green; + channels++; + + shift_start[channels] = row_info->bit_depth - bit_depth->blue; + shift_dec[channels] = bit_depth->blue; + channels++; + } + + else + { + shift_start[channels] = row_info->bit_depth - bit_depth->gray; + shift_dec[channels] = bit_depth->gray; + channels++; + } + + if (row_info->color_type & PNG_COLOR_MASK_ALPHA) + { + shift_start[channels] = row_info->bit_depth - bit_depth->alpha; + shift_dec[channels] = bit_depth->alpha; + channels++; + } + + /* With low row depths, could only be grayscale, so one channel */ + if (row_info->bit_depth < 8) + { + png_bytep bp = row; + png_size_t i; + unsigned int mask; + png_size_t row_bytes = row_info->rowbytes; + + if (bit_depth->gray == 1 && row_info->bit_depth == 2) + mask = 0x55; + + else if (row_info->bit_depth == 4 && bit_depth->gray == 3) + mask = 0x11; + + else + mask = 0xff; + + for (i = 0; i < row_bytes; i++, bp++) + { + int j; + unsigned int v, out; + + v = *bp; + out = 0; + + for (j = shift_start[0]; j > -shift_dec[0]; j -= shift_dec[0]) + { + if (j > 0) + out |= v << j; + + else + out |= (v >> (-j)) & mask; + } + + *bp = (png_byte)(out & 0xff); + } + } + + else if (row_info->bit_depth == 8) + { + png_bytep bp = row; + png_uint_32 i; + png_uint_32 istop = channels * row_info->width; + + for (i = 0; i < istop; i++, bp++) + { + + const unsigned int c = i%channels; + int j; + unsigned int v, out; + + v = *bp; + out = 0; + + for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) + { + if (j > 0) + out |= v << j; + + else + out |= v >> (-j); + } + + *bp = (png_byte)(out & 0xff); + } + } + + else + { + png_bytep bp; + png_uint_32 i; + png_uint_32 istop = channels * row_info->width; + + for (bp = row, i = 0; i < istop; i++) + { + const unsigned int c = i%channels; + int j; + unsigned int value, v; + + v = png_get_uint_16(bp); + value = 0; + + for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) + { + if (j > 0) + value |= v << j; + + else + value |= v >> (-j); + } + *bp++ = (png_byte)((value >> 8) & 0xff); + *bp++ = (png_byte)(value & 0xff); + } + } + } +} +#endif + +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED +void /* PRIVATE */ +png_do_write_swap_alpha(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_swap_alpha"); + + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This converts from ARGB to RGBA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save; + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This converts from AARRGGBB to RRGGBBAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save[2]; + save[0] = *(sp++); + save[1] = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save[0]; + *(dp++) = save[1]; + } + } +#endif /* PNG_WRITE_16BIT_SUPPORTED */ + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This converts from AG to GA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save; + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This converts from AAGG to GGAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + png_byte save[2]; + save[0] = *(sp++); + save[1] = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = save[0]; + *(dp++) = save[1]; + } + } +#endif /* PNG_WRITE_16BIT_SUPPORTED */ + } + } +} +#endif + +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED +void /* PRIVATE */ +png_do_write_invert_alpha(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_invert_alpha"); + + { + if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in RGBA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=3; dp = sp; + *(dp++) = (png_byte)(255 - *(sp++)); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in RRGGBBAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=6; dp = sp; + *(dp++) = (png_byte)(255 - *(sp++)); + *(dp++) = (png_byte)(255 - *(sp++)); + } + } +#endif /* PNG_WRITE_16BIT_SUPPORTED */ + } + + else if (row_info->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + if (row_info->bit_depth == 8) + { + /* This inverts the alpha channel in GA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + *(dp++) = *(sp++); + *(dp++) = (png_byte)(255 - *(sp++)); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else + { + /* This inverts the alpha channel in GGAA */ + png_bytep sp, dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + for (i = 0, sp = dp = row; i < row_width; i++) + { + /* Does nothing + *(dp++) = *(sp++); + *(dp++) = *(sp++); + */ + sp+=2; dp = sp; + *(dp++) = (png_byte)(255 - *(sp++)); + *(dp++) = (png_byte)(255 - *(sp++)); + } + } +#endif /* PNG_WRITE_16BIT_SUPPORTED */ + } + } +} +#endif +#endif /* PNG_WRITE_TRANSFORMS_SUPPORTED */ + +#ifdef PNG_MNG_FEATURES_SUPPORTED +/* Undoes intrapixel differencing */ +void /* PRIVATE */ +png_do_write_intrapixel(png_row_infop row_info, png_bytep row) +{ + png_debug(1, "in png_do_write_intrapixel"); + + if ((row_info->color_type & PNG_COLOR_MASK_COLOR)) + { + int bytes_per_pixel; + png_uint_32 row_width = row_info->width; + if (row_info->bit_depth == 8) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 3; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 4; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + *(rp) = (png_byte)((*rp - *(rp + 1)) & 0xff); + *(rp + 2) = (png_byte)((*(rp + 2) - *(rp + 1)) & 0xff); + } + } + +#ifdef PNG_WRITE_16BIT_SUPPORTED + else if (row_info->bit_depth == 16) + { + png_bytep rp; + png_uint_32 i; + + if (row_info->color_type == PNG_COLOR_TYPE_RGB) + bytes_per_pixel = 6; + + else if (row_info->color_type == PNG_COLOR_TYPE_RGB_ALPHA) + bytes_per_pixel = 8; + + else + return; + + for (i = 0, rp = row; i < row_width; i++, rp += bytes_per_pixel) + { + png_uint_32 s0 = (*(rp ) << 8) | *(rp + 1); + png_uint_32 s1 = (*(rp + 2) << 8) | *(rp + 3); + png_uint_32 s2 = (*(rp + 4) << 8) | *(rp + 5); + png_uint_32 red = (png_uint_32)((s0 - s1) & 0xffffL); + png_uint_32 blue = (png_uint_32)((s2 - s1) & 0xffffL); + *(rp ) = (png_byte)((red >> 8) & 0xff); + *(rp + 1) = (png_byte)(red & 0xff); + *(rp + 4) = (png_byte)((blue >> 8) & 0xff); + *(rp + 5) = (png_byte)(blue & 0xff); + } + } +#endif /* PNG_WRITE_16BIT_SUPPORTED */ + } +} +#endif /* PNG_MNG_FEATURES_SUPPORTED */ +#endif /* PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwutil.c b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwutil.c new file mode 100644 index 0000000..c90ff8b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/pnglib/pngwutil.c @@ -0,0 +1,3020 @@ + +/* pngwutil.c - utilities to write a PNG file + * + * Last changed in libpng 1.6.0 [February 14, 2013] + * Copyright (c) 1998-2013 Glenn Randers-Pehrson + * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) + * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + */ + +#include "pngpriv.h" + +#ifdef PNG_WRITE_SUPPORTED + +#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED +/* Place a 32-bit number into a buffer in PNG byte order. We work + * with unsigned numbers for convenience, although one supported + * ancillary chunk uses signed (two's complement) numbers. + */ +void PNGAPI +png_save_uint_32(png_bytep buf, png_uint_32 i) +{ + buf[0] = (png_byte)((i >> 24) & 0xff); + buf[1] = (png_byte)((i >> 16) & 0xff); + buf[2] = (png_byte)((i >> 8) & 0xff); + buf[3] = (png_byte)(i & 0xff); +} + +/* Place a 16-bit number into a buffer in PNG byte order. + * The parameter is declared unsigned int, not png_uint_16, + * just to avoid potential problems on pre-ANSI C compilers. + */ +void PNGAPI +png_save_uint_16(png_bytep buf, unsigned int i) +{ + buf[0] = (png_byte)((i >> 8) & 0xff); + buf[1] = (png_byte)(i & 0xff); +} +#endif + +/* Simple function to write the signature. If we have already written + * the magic bytes of the signature, or more likely, the PNG stream is + * being embedded into another stream and doesn't need its own signature, + * we should call png_set_sig_bytes() to tell libpng how many of the + * bytes have already been written. + */ +void PNGAPI +png_write_sig(png_structrp png_ptr) +{ + png_byte png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the signature is being written */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_SIGNATURE; +#endif + + /* Write the rest of the 8 byte signature */ + png_write_data(png_ptr, &png_signature[png_ptr->sig_bytes], + (png_size_t)(8 - png_ptr->sig_bytes)); + + if (png_ptr->sig_bytes < 3) + png_ptr->mode |= PNG_HAVE_PNG_SIGNATURE; +} + +/* Write the start of a PNG chunk. The type is the chunk type. + * The total_length is the sum of the lengths of all the data you will be + * passing in png_write_chunk_data(). + */ +static void +png_write_chunk_header(png_structrp png_ptr, png_uint_32 chunk_name, + png_uint_32 length) +{ + png_byte buf[8]; + +#if defined(PNG_DEBUG) && (PNG_DEBUG > 0) + PNG_CSTRING_FROM_CHUNK(buf, chunk_name); + png_debug2(0, "Writing %s chunk, length = %lu", buf, (unsigned long)length); +#endif + + if (png_ptr == NULL) + return; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the chunk header is being written. + * PNG_IO_CHUNK_HDR requires a single I/O call. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_HDR; +#endif + + /* Write the length and the chunk name */ + png_save_uint_32(buf, length); + png_save_uint_32(buf + 4, chunk_name); + png_write_data(png_ptr, buf, 8); + + /* Put the chunk name into png_ptr->chunk_name */ + png_ptr->chunk_name = chunk_name; + + /* Reset the crc and run it over the chunk name */ + png_reset_crc(png_ptr); + + png_calculate_crc(png_ptr, buf + 4, 4); + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that chunk data will (possibly) be written. + * PNG_IO_CHUNK_DATA does NOT require a specific number of I/O calls. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_DATA; +#endif +} + +void PNGAPI +png_write_chunk_start(png_structrp png_ptr, png_const_bytep chunk_string, + png_uint_32 length) +{ + png_write_chunk_header(png_ptr, PNG_CHUNK_FROM_STRING(chunk_string), length); +} + +/* Write the data of a PNG chunk started with png_write_chunk_header(). + * Note that multiple calls to this function are allowed, and that the + * sum of the lengths from these calls *must* add up to the total_length + * given to png_write_chunk_header(). + */ +void PNGAPI +png_write_chunk_data(png_structrp png_ptr, png_const_bytep data, + png_size_t length) +{ + /* Write the data, and run the CRC over it */ + if (png_ptr == NULL) + return; + + if (data != NULL && length > 0) + { + png_write_data(png_ptr, data, length); + + /* Update the CRC after writing the data, + * in case that the user I/O routine alters it. + */ + png_calculate_crc(png_ptr, data, length); + } +} + +/* Finish a chunk started with png_write_chunk_header(). */ +void PNGAPI +png_write_chunk_end(png_structrp png_ptr) +{ + png_byte buf[4]; + + if (png_ptr == NULL) return; + +#ifdef PNG_IO_STATE_SUPPORTED + /* Inform the I/O callback that the chunk CRC is being written. + * PNG_IO_CHUNK_CRC requires a single I/O function call. + */ + png_ptr->io_state = PNG_IO_WRITING | PNG_IO_CHUNK_CRC; +#endif + + /* Write the crc in a single operation */ + png_save_uint_32(buf, png_ptr->crc); + + png_write_data(png_ptr, buf, (png_size_t)4); +} + +/* Write a PNG chunk all at once. The type is an array of ASCII characters + * representing the chunk name. The array must be at least 4 bytes in + * length, and does not need to be null terminated. To be safe, pass the + * pre-defined chunk names here, and if you need a new one, define it + * where the others are defined. The length is the length of the data. + * All the data must be present. If that is not possible, use the + * png_write_chunk_start(), png_write_chunk_data(), and png_write_chunk_end() + * functions instead. + */ +static void +png_write_complete_chunk(png_structrp png_ptr, png_uint_32 chunk_name, + png_const_bytep data, png_size_t length) +{ + if (png_ptr == NULL) + return; + + /* On 64 bit architectures 'length' may not fit in a png_uint_32. */ + if (length > PNG_UINT_31_MAX) + png_error(png_ptr, "length exceeds PNG maxima"); + + png_write_chunk_header(png_ptr, chunk_name, (png_uint_32)length); + png_write_chunk_data(png_ptr, data, length); + png_write_chunk_end(png_ptr); +} + +/* This is the API that calls the internal function above. */ +void PNGAPI +png_write_chunk(png_structrp png_ptr, png_const_bytep chunk_string, + png_const_bytep data, png_size_t length) +{ + png_write_complete_chunk(png_ptr, PNG_CHUNK_FROM_STRING(chunk_string), data, + length); +} + +/* This is used below to find the size of an image to pass to png_deflate_claim, + * so it only needs to be accurate if the size is less than 16384 bytes (the + * point at which a lower LZ window size can be used.) + */ +static png_alloc_size_t +png_image_size(png_structrp png_ptr) +{ + /* Only return sizes up to the maximum of a png_uint_32, do this by limiting + * the width and height used to 15 bits. + */ + png_uint_32 h = png_ptr->height; + + if (png_ptr->rowbytes < 32768 && h < 32768) + { + if (png_ptr->interlaced) + { + /* Interlacing makes the image larger because of the replication of + * both the filter byte and the padding to a byte boundary. + */ + png_uint_32 w = png_ptr->width; + unsigned int pd = png_ptr->pixel_depth; + png_alloc_size_t cb_base; + int pass; + + for (cb_base=0, pass=0; pass<=6; ++pass) + { + png_uint_32 pw = PNG_PASS_COLS(w, pass); + + if (pw > 0) + cb_base += (PNG_ROWBYTES(pd, pw)+1) * PNG_PASS_ROWS(h, pass); + } + + return cb_base; + } + + else + return (png_ptr->rowbytes+1) * h; + } + + else + return 0xffffffffU; +} + +#ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + /* This is the code to hack the first two bytes of the deflate stream (the + * deflate header) to correct the windowBits value to match the actual data + * size. Note that the second argument is the *uncompressed* size but the + * first argument is the *compressed* data (and it must be deflate + * compressed.) + */ +static void +optimize_cmf(png_bytep data, png_alloc_size_t data_size) +{ + /* Optimize the CMF field in the zlib stream. The resultant zlib stream is + * still compliant to the stream specification. + */ + if (data_size <= 16384) /* else windowBits must be 15 */ + { + unsigned int z_cmf = data[0]; /* zlib compression method and flags */ + + if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) + { + unsigned int z_cinfo; + unsigned int half_z_window_size; + + z_cinfo = z_cmf >> 4; + half_z_window_size = 1U << (z_cinfo + 7); + + if (data_size <= half_z_window_size) /* else no change */ + { + unsigned int tmp; + + do + { + half_z_window_size >>= 1; + --z_cinfo; + } + while (z_cinfo > 0 && data_size <= half_z_window_size); + + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + + data[0] = (png_byte)z_cmf; + tmp = data[1] & 0xe0; + tmp += 0x1f - ((z_cmf << 8) + tmp) % 0x1f; + data[1] = (png_byte)tmp; + } + } + } +} +#else +# define optimize_cmf(dp,dl) ((void)0) +#endif /* PNG_WRITE_OPTIMIZE_CMF_SUPPORTED */ + +/* Initialize the compressor for the appropriate type of compression. */ +static int +png_deflate_claim(png_structrp png_ptr, png_uint_32 owner, + png_alloc_size_t data_size) +{ + if (png_ptr->zowner != 0) + { + char msg[64]; + + PNG_STRING_FROM_CHUNK(msg, owner); + msg[4] = ':'; + msg[5] = ' '; + PNG_STRING_FROM_CHUNK(msg+6, png_ptr->zowner); + /* So the message that results is " using zstream"; this is an + * internal error, but is very useful for debugging. i18n requirements + * are minimal. + */ + (void)png_safecat(msg, (sizeof msg), 10, " using zstream"); +# if PNG_LIBPNG_BUILD_BASE_TYPE >= PNG_LIBPNG_BUILD_RC + png_warning(png_ptr, msg); + + /* Attempt sane error recovery */ + if (png_ptr->zowner == png_IDAT) /* don't steal from IDAT */ + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("in use by IDAT"); + return Z_STREAM_ERROR; + } + + png_ptr->zowner = 0; +# else + png_error(png_ptr, msg); +# endif + } + + { + int level = png_ptr->zlib_level; + int method = png_ptr->zlib_method; + int windowBits = png_ptr->zlib_window_bits; + int memLevel = png_ptr->zlib_mem_level; + int strategy; /* set below */ + int ret; /* zlib return code */ + + if (owner == png_IDAT) + { + if (png_ptr->flags & PNG_FLAG_ZLIB_CUSTOM_STRATEGY) + strategy = png_ptr->zlib_strategy; + + else if (png_ptr->do_filter != PNG_FILTER_NONE) + strategy = PNG_Z_DEFAULT_STRATEGY; + + else + strategy = PNG_Z_DEFAULT_NOFILTER_STRATEGY; + } + + else + { +# ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED + level = png_ptr->zlib_text_level; + method = png_ptr->zlib_text_method; + windowBits = png_ptr->zlib_text_window_bits; + memLevel = png_ptr->zlib_text_mem_level; + strategy = png_ptr->zlib_text_strategy; +# else + /* If customization is not supported the values all come from the + * IDAT values except for the strategy, which is fixed to the + * default. (This is the pre-1.6.0 behavior too, although it was + * implemented in a very different way.) + */ + strategy = Z_DEFAULT_STRATEGY; +# endif + } + + /* Adjust 'windowBits' down if larger than 'data_size'; to stop this + * happening just pass 32768 as the data_size parameter. Notice that zlib + * requires an extra 262 bytes in the window in addition to the data to be + * able to see the whole of the data, so if data_size+262 takes us to the + * next windowBits size we need to fix up the value later. (Because even + * though deflate needs the extra window, inflate does not!) + */ + if (data_size <= 16384) + { + /* IMPLEMENTATION NOTE: this 'half_window_size' stuff is only here to + * work round a Microsoft Visual C misbehavior which, contrary to C-90, + * widens the result of the following shift to 64-bits if (and, + * apparently, only if) it is used in a test. + */ + unsigned int half_window_size = 1U << (windowBits-1); + + while (data_size + 262 <= half_window_size) + { + half_window_size >>= 1; + --windowBits; + } + } + + /* Check against the previous initialized values, if any. */ + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) && + (png_ptr->zlib_set_level != level || + png_ptr->zlib_set_method != method || + png_ptr->zlib_set_window_bits != windowBits || + png_ptr->zlib_set_mem_level != memLevel || + png_ptr->zlib_set_strategy != strategy)) + { + if (deflateEnd(&png_ptr->zstream) != Z_OK) + png_warning(png_ptr, "deflateEnd failed (ignored)"); + + png_ptr->flags &= ~PNG_FLAG_ZSTREAM_INITIALIZED; + } + + /* For safety clear out the input and output pointers (currently zlib + * doesn't use them on Init, but it might in the future). + */ + png_ptr->zstream.next_in = NULL; + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->zstream.avail_out = 0; + + /* Now initialize if required, setting the new parameters, otherwise just + * to a simple reset to the previous parameters. + */ + if (png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) + ret = deflateReset(&png_ptr->zstream); + + else + { + ret = deflateInit2(&png_ptr->zstream, level, method, windowBits, + memLevel, strategy); + + if (ret == Z_OK) + png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED; + } + + /* The return code is from either deflateReset or deflateInit2; they have + * pretty much the same set of error codes. + */ + if (ret == Z_OK) + png_ptr->zowner = owner; + + else + png_zstream_error(png_ptr, ret); + + return ret; + } +} + +/* Clean up (or trim) a linked list of compression buffers. */ +void /* PRIVATE */ +png_free_buffer_list(png_structrp png_ptr, png_compression_bufferp *listp) +{ + png_compression_bufferp list = *listp; + + if (list != NULL) + { + *listp = NULL; + + do + { + png_compression_bufferp next = list->next; + + png_free(png_ptr, list); + list = next; + } + while (list != NULL); + } +} + +#ifdef PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +/* This pair of functions encapsulates the operation of (a) compressing a + * text string, and (b) issuing it later as a series of chunk data writes. + * The compression_state structure is shared context for these functions + * set up by the caller to allow access to the relevant local variables. + * + * compression_buffer (new in 1.6.0) is just a linked list of zbuffer_size + * temporary buffers. From 1.6.0 it is retained in png_struct so that it will + * be correctly freed in the event of a write error (previous implementations + * just leaked memory.) + */ +typedef struct +{ + png_const_bytep input; /* The uncompressed input data */ + png_alloc_size_t input_len; /* Its length */ + png_uint_32 output_len; /* Final compressed length */ + png_byte output[1024]; /* First block of output */ +} compression_state; + +static void +png_text_compress_init(compression_state *comp, png_const_bytep input, + png_alloc_size_t input_len) +{ + comp->input = input; + comp->input_len = input_len; + comp->output_len = 0; +} + +/* Compress the data in the compression state input */ +static int +png_text_compress(png_structrp png_ptr, png_uint_32 chunk_name, + compression_state *comp, png_uint_32 prefix_len) +{ + int ret; + + /* To find the length of the output it is necessary to first compress the + * input, the result is buffered rather than using the two-pass algorithm + * that is used on the inflate side; deflate is assumed to be slower and a + * PNG writer is assumed to have more memory available than a PNG reader. + * + * IMPLEMENTATION NOTE: the zlib API deflateBound() can be used to find an + * upper limit on the output size, but it is always bigger than the input + * size so it is likely to be more efficient to use this linked-list + * approach. + */ + ret = png_deflate_claim(png_ptr, chunk_name, comp->input_len); + + if (ret != Z_OK) + return ret; + + /* Set up the compression buffers, we need a loop here to avoid overflowing a + * uInt. Use ZLIB_IO_MAX to limit the input. The output is always limited + * by the output buffer size, so there is no need to check that. Since this + * is ANSI-C we know that an 'int', hence a uInt, is always at least 16 bits + * in size. + */ + { + png_compression_bufferp *end = &png_ptr->zbuffer_list; + png_alloc_size_t input_len = comp->input_len; /* may be zero! */ + png_uint_32 output_len; + + /* zlib updates these for us: */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(comp->input); + png_ptr->zstream.avail_in = 0; /* Set below */ + png_ptr->zstream.next_out = comp->output; + png_ptr->zstream.avail_out = (sizeof comp->output); + + output_len = png_ptr->zstream.avail_out; + + do + { + uInt avail_in = ZLIB_IO_MAX; + + if (avail_in > input_len) + avail_in = (uInt)input_len; + + input_len -= avail_in; + + png_ptr->zstream.avail_in = avail_in; + + if (png_ptr->zstream.avail_out == 0) + { + png_compression_buffer *next; + + /* Chunk data is limited to 2^31 bytes in length, so the prefix + * length must be counted here. + */ + if (output_len + prefix_len > PNG_UINT_31_MAX) + { + ret = Z_MEM_ERROR; + break; + } + + /* Need a new (malloc'ed) buffer, but there may be one present + * already. + */ + next = *end; + if (next == NULL) + { + next = png_voidcast(png_compression_bufferp, png_malloc_base + (png_ptr, PNG_COMPRESSION_BUFFER_SIZE(png_ptr))); + + if (next == NULL) + { + ret = Z_MEM_ERROR; + break; + } + + /* Link in this buffer (so that it will be freed later) */ + next->next = NULL; + *end = next; + } + + png_ptr->zstream.next_out = next->output; + png_ptr->zstream.avail_out = png_ptr->zbuffer_size; + output_len += png_ptr->zstream.avail_out; + + /* Move 'end' to the next buffer pointer. */ + end = &next->next; + } + + /* Compress the data */ + ret = deflate(&png_ptr->zstream, + input_len > 0 ? Z_NO_FLUSH : Z_FINISH); + + /* Claw back input data that was not consumed (because avail_in is + * reset above every time round the loop). + */ + input_len += png_ptr->zstream.avail_in; + png_ptr->zstream.avail_in = 0; /* safety */ + } + while (ret == Z_OK); + + /* There may be some space left in the last output buffer, this needs to + * be subtracted from output_len. + */ + output_len -= png_ptr->zstream.avail_out; + png_ptr->zstream.avail_out = 0; /* safety */ + comp->output_len = output_len; + + /* Now double check the output length, put in a custom message if it is + * too long. Otherwise ensure the z_stream::msg pointer is set to + * something. + */ + if (output_len + prefix_len >= PNG_UINT_31_MAX) + { + png_ptr->zstream.msg = PNGZ_MSG_CAST("compressed data too long"); + ret = Z_MEM_ERROR; + } + + else + png_zstream_error(png_ptr, ret); + + /* Reset zlib for another zTXt/iTXt or image data */ + png_ptr->zowner = 0; + + /* The only success case is Z_STREAM_END, input_len must be 0, if not this + * is an internal error. + */ + if (ret == Z_STREAM_END && input_len == 0) + { + /* Fix up the deflate header, if required */ + optimize_cmf(comp->output, comp->input_len); + + /* But Z_OK is returned, not Z_STREAM_END; this allows the claim + * function above to return Z_STREAM_END on an error (though it never + * does in the current versions of zlib.) + */ + return Z_OK; + } + + else + return ret; + } +} + +/* Ship the compressed text out via chunk writes */ +static void +png_write_compressed_data_out(png_structrp png_ptr, compression_state *comp) +{ + png_uint_32 output_len = comp->output_len; + png_const_bytep output = comp->output; + png_uint_32 avail = (sizeof comp->output); + png_compression_buffer *next = png_ptr->zbuffer_list; + + for (;;) + { + if (avail > output_len) + avail = output_len; + + png_write_chunk_data(png_ptr, output, avail); + + output_len -= avail; + + if (output_len == 0 || next == NULL) + break; + + avail = png_ptr->zbuffer_size; + output = next->output; + next = next->next; + } + + /* This is an internal error; 'next' must have been NULL! */ + if (output_len > 0) + png_error(png_ptr, "error writing ancillary chunked compressed data"); +} +#endif /* PNG_WRITE_COMPRESSED_TEXT_SUPPORTED */ + +#if defined(PNG_WRITE_TEXT_SUPPORTED) || defined(PNG_WRITE_pCAL_SUPPORTED) || \ + defined(PNG_WRITE_iCCP_SUPPORTED) || defined(PNG_WRITE_sPLT_SUPPORTED) +/* Check that the tEXt or zTXt keyword is valid per PNG 1.0 specification, + * and if invalid, correct the keyword rather than discarding the entire + * chunk. The PNG 1.0 specification requires keywords 1-79 characters in + * length, forbids leading or trailing whitespace, multiple internal spaces, + * and the non-break space (0x80) from ISO 8859-1. Returns keyword length. + * + * The 'new_key' buffer must be 80 characters in size (for the keyword plus a + * trailing '\0'). If this routine returns 0 then there was no keyword, or a + * valid one could not be generated, and the caller must png_error. + */ +static png_uint_32 +png_check_keyword(png_structrp png_ptr, png_const_charp key, png_bytep new_key) +{ + png_const_charp orig_key = key; + png_uint_32 key_len = 0; + int bad_character = 0; + int space = 1; + + png_debug(1, "in png_check_keyword"); + + if (key == NULL) + { + *new_key = 0; + return 0; + } + + while (*key && key_len < 79) + { + png_byte ch = (png_byte)(0xff & *key++); + + if ((ch > 32 && ch <= 126) || (ch >= 161 /*&& ch <= 255*/)) + *new_key++ = ch, ++key_len, space = 0; + + else if (!space) + { + /* A space or an invalid character when one wasn't seen immediately + * before; output just a space. + */ + *new_key++ = 32, ++key_len, space = 1; + + /* If the character was not a space then it is invalid. */ + if (ch != 32) + bad_character = ch; + } + + else if (!bad_character) + bad_character = ch; /* just skip it, record the first error */ + } + + if (key_len > 0 && space) /* trailing space */ + { + --key_len, --new_key; + if (!bad_character) + bad_character = 32; + } + + /* Terminate the keyword */ + *new_key = 0; + + if (key_len == 0) + return 0; + + /* Try to only output one warning per keyword: */ + if (*key) /* keyword too long */ + png_warning(png_ptr, "keyword truncated"); + + else if (bad_character) + { + PNG_WARNING_PARAMETERS(p) + + png_warning_parameter(p, 1, orig_key); + png_warning_parameter_signed(p, 2, PNG_NUMBER_FORMAT_02x, bad_character); + + png_formatted_warning(png_ptr, p, "keyword \"@1\": bad character '0x@2'"); + } + + return key_len; +} +#endif + +/* Write the IHDR chunk, and update the png_struct with the necessary + * information. Note that the rest of this code depends upon this + * information being correct. + */ +void /* PRIVATE */ +png_write_IHDR(png_structrp png_ptr, png_uint_32 width, png_uint_32 height, + int bit_depth, int color_type, int compression_type, int filter_type, + int interlace_type) +{ + png_byte buf[13]; /* Buffer to store the IHDR info */ + + png_debug(1, "in png_write_IHDR"); + + /* Check that we have valid input data from the application info */ + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + switch (bit_depth) + { + case 1: + case 2: + case 4: + case 8: +#ifdef PNG_WRITE_16BIT_SUPPORTED + case 16: +#endif + png_ptr->channels = 1; break; + + default: + png_error(png_ptr, + "Invalid bit depth for grayscale image"); + } + break; + + case PNG_COLOR_TYPE_RGB: +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (bit_depth != 8 && bit_depth != 16) +#else + if (bit_depth != 8) +#endif + png_error(png_ptr, "Invalid bit depth for RGB image"); + + png_ptr->channels = 3; + break; + + case PNG_COLOR_TYPE_PALETTE: + switch (bit_depth) + { + case 1: + case 2: + case 4: + case 8: + png_ptr->channels = 1; + break; + + default: + png_error(png_ptr, "Invalid bit depth for paletted image"); + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + if (bit_depth != 8 && bit_depth != 16) + png_error(png_ptr, "Invalid bit depth for grayscale+alpha image"); + + png_ptr->channels = 2; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (bit_depth != 8 && bit_depth != 16) +#else + if (bit_depth != 8) +#endif + png_error(png_ptr, "Invalid bit depth for RGBA image"); + + png_ptr->channels = 4; + break; + + default: + png_error(png_ptr, "Invalid image color type specified"); + } + + if (compression_type != PNG_COMPRESSION_TYPE_BASE) + { + png_warning(png_ptr, "Invalid compression type specified"); + compression_type = PNG_COMPRESSION_TYPE_BASE; + } + + /* Write filter_method 64 (intrapixel differencing) only if + * 1. Libpng was compiled with PNG_MNG_FEATURES_SUPPORTED and + * 2. Libpng did not write a PNG signature (this filter_method is only + * used in PNG datastreams that are embedded in MNG datastreams) and + * 3. The application called png_permit_mng_features with a mask that + * included PNG_FLAG_MNG_FILTER_64 and + * 4. The filter_method is 64 and + * 5. The color_type is RGB or RGBA + */ + if ( +#ifdef PNG_MNG_FEATURES_SUPPORTED + !((png_ptr->mng_features_permitted & PNG_FLAG_MNG_FILTER_64) && + ((png_ptr->mode&PNG_HAVE_PNG_SIGNATURE) == 0) && + (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_RGB_ALPHA) && + (filter_type == PNG_INTRAPIXEL_DIFFERENCING)) && +#endif + filter_type != PNG_FILTER_TYPE_BASE) + { + png_warning(png_ptr, "Invalid filter type specified"); + filter_type = PNG_FILTER_TYPE_BASE; + } + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + if (interlace_type != PNG_INTERLACE_NONE && + interlace_type != PNG_INTERLACE_ADAM7) + { + png_warning(png_ptr, "Invalid interlace type specified"); + interlace_type = PNG_INTERLACE_ADAM7; + } +#else + interlace_type=PNG_INTERLACE_NONE; +#endif + + /* Save the relevent information */ + png_ptr->bit_depth = (png_byte)bit_depth; + png_ptr->color_type = (png_byte)color_type; + png_ptr->interlaced = (png_byte)interlace_type; +#ifdef PNG_MNG_FEATURES_SUPPORTED + png_ptr->filter_type = (png_byte)filter_type; +#endif + png_ptr->compression_type = (png_byte)compression_type; + png_ptr->width = width; + png_ptr->height = height; + + png_ptr->pixel_depth = (png_byte)(bit_depth * png_ptr->channels); + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, width); + /* Set the usr info, so any transformations can modify it */ + png_ptr->usr_width = png_ptr->width; + png_ptr->usr_bit_depth = png_ptr->bit_depth; + png_ptr->usr_channels = png_ptr->channels; + + /* Pack the header information into the buffer */ + png_save_uint_32(buf, width); + png_save_uint_32(buf + 4, height); + buf[8] = (png_byte)bit_depth; + buf[9] = (png_byte)color_type; + buf[10] = (png_byte)compression_type; + buf[11] = (png_byte)filter_type; + buf[12] = (png_byte)interlace_type; + + /* Write the chunk */ + png_write_complete_chunk(png_ptr, png_IHDR, buf, (png_size_t)13); + + if (!(png_ptr->do_filter)) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE || + png_ptr->bit_depth < 8) + png_ptr->do_filter = PNG_FILTER_NONE; + + else + png_ptr->do_filter = PNG_ALL_FILTERS; + } + + png_ptr->mode = PNG_HAVE_IHDR; /* not READY_FOR_ZTXT */ +} + +/* Write the palette. We are careful not to trust png_color to be in the + * correct order for PNG, so people can redefine it to any convenient + * structure. + */ +void /* PRIVATE */ +png_write_PLTE(png_structrp png_ptr, png_const_colorp palette, + png_uint_32 num_pal) +{ + png_uint_32 i; + png_const_colorp pal_ptr; + png_byte buf[3]; + + png_debug(1, "in png_write_PLTE"); + + if (( +#ifdef PNG_MNG_FEATURES_SUPPORTED + !(png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE) && +#endif + num_pal == 0) || num_pal > 256) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { + png_error(png_ptr, "Invalid number of colors in palette"); + } + + else + { + png_warning(png_ptr, "Invalid number of colors in palette"); + return; + } + } + + if (!(png_ptr->color_type&PNG_COLOR_MASK_COLOR)) + { + png_warning(png_ptr, + "Ignoring request to write a PLTE chunk in grayscale PNG"); + + return; + } + + png_ptr->num_palette = (png_uint_16)num_pal; + png_debug1(3, "num_palette = %d", png_ptr->num_palette); + + png_write_chunk_header(png_ptr, png_PLTE, (png_uint_32)(num_pal * 3)); +#ifdef PNG_POINTER_INDEXING_SUPPORTED + + for (i = 0, pal_ptr = palette; i < num_pal; i++, pal_ptr++) + { + buf[0] = pal_ptr->red; + buf[1] = pal_ptr->green; + buf[2] = pal_ptr->blue; + png_write_chunk_data(png_ptr, buf, (png_size_t)3); + } + +#else + /* This is a little slower but some buggy compilers need to do this + * instead + */ + pal_ptr=palette; + + for (i = 0; i < num_pal; i++) + { + buf[0] = pal_ptr[i].red; + buf[1] = pal_ptr[i].green; + buf[2] = pal_ptr[i].blue; + png_write_chunk_data(png_ptr, buf, (png_size_t)3); + } + +#endif + png_write_chunk_end(png_ptr); + png_ptr->mode |= PNG_HAVE_PLTE; +} + +/* This is similar to png_text_compress, above, except that it does not require + * all of the data at once and, instead of buffering the compressed result, + * writes it as IDAT chunks. Unlike png_text_compress it *can* png_error out + * because it calls the write interface. As a result it does its own error + * reporting and does not return an error code. In the event of error it will + * just call png_error. The input data length may exceed 32-bits. The 'flush' + * parameter is exactly the same as that to deflate, with the following + * meanings: + * + * Z_NO_FLUSH: normal incremental output of compressed data + * Z_SYNC_FLUSH: do a SYNC_FLUSH, used by png_write_flush + * Z_FINISH: this is the end of the input, do a Z_FINISH and clean up + * + * The routine manages the acquire and release of the png_ptr->zstream by + * checking and (at the end) clearing png_ptr->zowner, it does some sanity + * checks on the 'mode' flags while doing this. + */ +void /* PRIVATE */ +png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, + png_alloc_size_t input_len, int flush) +{ + if (png_ptr->zowner != png_IDAT) + { + /* First time. Ensure we have a temporary buffer for compression and + * trim the buffer list if it has more than one entry to free memory. + * If 'WRITE_COMPRESSED_TEXT' is not set the list will never have been + * created at this point, but the check here is quick and safe. + */ + if (png_ptr->zbuffer_list == NULL) + { + png_ptr->zbuffer_list = png_voidcast(png_compression_bufferp, + png_malloc(png_ptr, PNG_COMPRESSION_BUFFER_SIZE(png_ptr))); + png_ptr->zbuffer_list->next = NULL; + } + + else + png_free_buffer_list(png_ptr, &png_ptr->zbuffer_list->next); + + /* It is a terminal error if we can't claim the zstream. */ + if (png_deflate_claim(png_ptr, png_IDAT, png_image_size(png_ptr)) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + /* The output state is maintained in png_ptr->zstream, so it must be + * initialized here after the claim. + */ + png_ptr->zstream.next_out = png_ptr->zbuffer_list->output; + png_ptr->zstream.avail_out = png_ptr->zbuffer_size; + } + + /* Now loop reading and writing until all the input is consumed or an error + * terminates the operation. The _out values are maintained across calls to + * this function, but the input must be reset each time. + */ + png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input); + png_ptr->zstream.avail_in = 0; /* set below */ + for (;;) + { + int ret; + + /* INPUT: from the row data */ + uInt avail = ZLIB_IO_MAX; + + if (avail > input_len) + avail = (uInt)input_len; /* safe because of the check */ + + png_ptr->zstream.avail_in = avail; + input_len -= avail; + + ret = deflate(&png_ptr->zstream, input_len > 0 ? Z_NO_FLUSH : flush); + + /* Include as-yet unconsumed input */ + input_len += png_ptr->zstream.avail_in; + png_ptr->zstream.avail_in = 0; + + /* OUTPUT: write complete IDAT chunks when avail_out drops to zero, note + * that these two zstream fields are preserved across the calls, therefore + * there is no need to set these up on entry to the loop. + */ + if (png_ptr->zstream.avail_out == 0) + { + png_bytep data = png_ptr->zbuffer_list->output; + uInt size = png_ptr->zbuffer_size; + + /* Write an IDAT containing the data then reset the buffer. The + * first IDAT may need deflate header optimization. + */ +# ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + if (!(png_ptr->mode & PNG_HAVE_IDAT) && + png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE) + optimize_cmf(data, png_image_size(png_ptr)); +# endif + + png_write_complete_chunk(png_ptr, png_IDAT, data, size); + png_ptr->mode |= PNG_HAVE_IDAT; + + png_ptr->zstream.next_out = data; + png_ptr->zstream.avail_out = size; + + /* For SYNC_FLUSH or FINISH it is essential to keep calling zlib with + * the same flush parameter until it has finished output, for NO_FLUSH + * it doesn't matter. + */ + if (ret == Z_OK && flush != Z_NO_FLUSH) + continue; + } + + /* The order of these checks doesn't matter much; it just effect which + * possible error might be detected if multiple things go wrong at once. + */ + if (ret == Z_OK) /* most likely return code! */ + { + /* If all the input has been consumed then just return. If Z_FINISH + * was used as the flush parameter something has gone wrong if we get + * here. + */ + if (input_len == 0) + { + if (flush == Z_FINISH) + png_error(png_ptr, "Z_OK on Z_FINISH with output space"); + + return; + } + } + + else if (ret == Z_STREAM_END && flush == Z_FINISH) + { + /* This is the end of the IDAT data; any pending output must be + * flushed. For small PNG files we may still be at the beginning. + */ + png_bytep data = png_ptr->zbuffer_list->output; + uInt size = png_ptr->zbuffer_size - png_ptr->zstream.avail_out; + +# ifdef PNG_WRITE_OPTIMIZE_CMF_SUPPORTED + if (!(png_ptr->mode & PNG_HAVE_IDAT) && + png_ptr->compression_type == PNG_COMPRESSION_TYPE_BASE) + optimize_cmf(data, png_image_size(png_ptr)); +# endif + + png_write_complete_chunk(png_ptr, png_IDAT, data, size); + png_ptr->zstream.avail_out = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->mode |= PNG_HAVE_IDAT | PNG_AFTER_IDAT; + + png_ptr->zowner = 0; /* Release the stream */ + return; + } + + else + { + /* This is an error condition. */ + png_zstream_error(png_ptr, ret); + png_error(png_ptr, png_ptr->zstream.msg); + } + } +} + +/* Write an IEND chunk */ +void /* PRIVATE */ +png_write_IEND(png_structrp png_ptr) +{ + png_debug(1, "in png_write_IEND"); + + png_write_complete_chunk(png_ptr, png_IEND, NULL, (png_size_t)0); + png_ptr->mode |= PNG_HAVE_IEND; +} + +#ifdef PNG_WRITE_gAMA_SUPPORTED +/* Write a gAMA chunk */ +void /* PRIVATE */ +png_write_gAMA_fixed(png_structrp png_ptr, png_fixed_point file_gamma) +{ + png_byte buf[4]; + + png_debug(1, "in png_write_gAMA"); + + /* file_gamma is saved in 1/100,000ths */ + png_save_uint_32(buf, (png_uint_32)file_gamma); + png_write_complete_chunk(png_ptr, png_gAMA, buf, (png_size_t)4); +} +#endif + +#ifdef PNG_WRITE_sRGB_SUPPORTED +/* Write a sRGB chunk */ +void /* PRIVATE */ +png_write_sRGB(png_structrp png_ptr, int srgb_intent) +{ + png_byte buf[1]; + + png_debug(1, "in png_write_sRGB"); + + if (srgb_intent >= PNG_sRGB_INTENT_LAST) + png_warning(png_ptr, + "Invalid sRGB rendering intent specified"); + + buf[0]=(png_byte)srgb_intent; + png_write_complete_chunk(png_ptr, png_sRGB, buf, (png_size_t)1); +} +#endif + +#ifdef PNG_WRITE_iCCP_SUPPORTED +/* Write an iCCP chunk */ +void /* PRIVATE */ +png_write_iCCP(png_structrp png_ptr, png_const_charp name, + png_const_bytep profile) +{ + png_uint_32 name_len; + png_uint_32 profile_len; + png_byte new_name[81]; /* 1 byte for the compression byte */ + compression_state comp; + + png_debug(1, "in png_write_iCCP"); + + /* These are all internal problems: the profile should have been checked + * before when it was stored. + */ + if (profile == NULL) + png_error(png_ptr, "No profile for iCCP chunk"); /* internal error */ + + profile_len = png_get_uint_32(profile); + + if (profile_len < 132) + png_error(png_ptr, "ICC profile too short"); + + if (profile_len & 0x03) + png_error(png_ptr, "ICC profile length invalid (not a multiple of 4)"); + + { + png_uint_32 embedded_profile_len = png_get_uint_32(profile); + + if (profile_len != embedded_profile_len) + png_error(png_ptr, "Profile length does not match profile"); + } + + name_len = png_check_keyword(png_ptr, name, new_name); + + if (name_len == 0) + png_error(png_ptr, "iCCP: invalid keyword"); + + new_name[++name_len] = PNG_COMPRESSION_TYPE_BASE; + + /* Make sure we include the NULL after the name and the compression type */ + ++name_len; + + png_text_compress_init(&comp, profile, profile_len); + + /* Allow for keyword terminator and compression byte */ + if (png_text_compress(png_ptr, png_iCCP, &comp, name_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + png_write_chunk_header(png_ptr, png_iCCP, name_len + comp.output_len); + + png_write_chunk_data(png_ptr, new_name, name_len); + + png_write_compressed_data_out(png_ptr, &comp); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sPLT_SUPPORTED +/* Write a sPLT chunk */ +void /* PRIVATE */ +png_write_sPLT(png_structrp png_ptr, png_const_sPLT_tp spalette) +{ + png_uint_32 name_len; + png_byte new_name[80]; + png_byte entrybuf[10]; + png_size_t entry_size = (spalette->depth == 8 ? 6 : 10); + png_size_t palette_size = entry_size * spalette->nentries; + png_sPLT_entryp ep; +#ifndef PNG_POINTER_INDEXING_SUPPORTED + int i; +#endif + + png_debug(1, "in png_write_sPLT"); + + name_len = png_check_keyword(png_ptr, spalette->name, new_name); + + if (name_len == 0) + png_error(png_ptr, "sPLT: invalid keyword"); + + /* Make sure we include the NULL after the name */ + png_write_chunk_header(png_ptr, png_sPLT, + (png_uint_32)(name_len + 2 + palette_size)); + + png_write_chunk_data(png_ptr, (png_bytep)new_name, + (png_size_t)(name_len + 1)); + + png_write_chunk_data(png_ptr, &spalette->depth, (png_size_t)1); + + /* Loop through each palette entry, writing appropriately */ +#ifdef PNG_POINTER_INDEXING_SUPPORTED + for (ep = spalette->entries; epentries + spalette->nentries; ep++) + { + if (spalette->depth == 8) + { + entrybuf[0] = (png_byte)ep->red; + entrybuf[1] = (png_byte)ep->green; + entrybuf[2] = (png_byte)ep->blue; + entrybuf[3] = (png_byte)ep->alpha; + png_save_uint_16(entrybuf + 4, ep->frequency); + } + + else + { + png_save_uint_16(entrybuf + 0, ep->red); + png_save_uint_16(entrybuf + 2, ep->green); + png_save_uint_16(entrybuf + 4, ep->blue); + png_save_uint_16(entrybuf + 6, ep->alpha); + png_save_uint_16(entrybuf + 8, ep->frequency); + } + + png_write_chunk_data(png_ptr, entrybuf, entry_size); + } +#else + ep=spalette->entries; + for (i = 0; i>spalette->nentries; i++) + { + if (spalette->depth == 8) + { + entrybuf[0] = (png_byte)ep[i].red; + entrybuf[1] = (png_byte)ep[i].green; + entrybuf[2] = (png_byte)ep[i].blue; + entrybuf[3] = (png_byte)ep[i].alpha; + png_save_uint_16(entrybuf + 4, ep[i].frequency); + } + + else + { + png_save_uint_16(entrybuf + 0, ep[i].red); + png_save_uint_16(entrybuf + 2, ep[i].green); + png_save_uint_16(entrybuf + 4, ep[i].blue); + png_save_uint_16(entrybuf + 6, ep[i].alpha); + png_save_uint_16(entrybuf + 8, ep[i].frequency); + } + + png_write_chunk_data(png_ptr, entrybuf, entry_size); + } +#endif + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sBIT_SUPPORTED +/* Write the sBIT chunk */ +void /* PRIVATE */ +png_write_sBIT(png_structrp png_ptr, png_const_color_8p sbit, int color_type) +{ + png_byte buf[4]; + png_size_t size; + + png_debug(1, "in png_write_sBIT"); + + /* Make sure we don't depend upon the order of PNG_COLOR_8 */ + if (color_type & PNG_COLOR_MASK_COLOR) + { + png_byte maxbits; + + maxbits = (png_byte)(color_type==PNG_COLOR_TYPE_PALETTE ? 8 : + png_ptr->usr_bit_depth); + + if (sbit->red == 0 || sbit->red > maxbits || + sbit->green == 0 || sbit->green > maxbits || + sbit->blue == 0 || sbit->blue > maxbits) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[0] = sbit->red; + buf[1] = sbit->green; + buf[2] = sbit->blue; + size = 3; + } + + else + { + if (sbit->gray == 0 || sbit->gray > png_ptr->usr_bit_depth) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[0] = sbit->gray; + size = 1; + } + + if (color_type & PNG_COLOR_MASK_ALPHA) + { + if (sbit->alpha == 0 || sbit->alpha > png_ptr->usr_bit_depth) + { + png_warning(png_ptr, "Invalid sBIT depth specified"); + return; + } + + buf[size++] = sbit->alpha; + } + + png_write_complete_chunk(png_ptr, png_sBIT, buf, size); +} +#endif + +#ifdef PNG_WRITE_cHRM_SUPPORTED +/* Write the cHRM chunk */ +void /* PRIVATE */ +png_write_cHRM_fixed(png_structrp png_ptr, const png_xy *xy) +{ + png_byte buf[32]; + + png_debug(1, "in png_write_cHRM"); + + /* Each value is saved in 1/100,000ths */ + png_save_int_32(buf, xy->whitex); + png_save_int_32(buf + 4, xy->whitey); + + png_save_int_32(buf + 8, xy->redx); + png_save_int_32(buf + 12, xy->redy); + + png_save_int_32(buf + 16, xy->greenx); + png_save_int_32(buf + 20, xy->greeny); + + png_save_int_32(buf + 24, xy->bluex); + png_save_int_32(buf + 28, xy->bluey); + + png_write_complete_chunk(png_ptr, png_cHRM, buf, 32); +} +#endif + +#ifdef PNG_WRITE_tRNS_SUPPORTED +/* Write the tRNS chunk */ +void /* PRIVATE */ +png_write_tRNS(png_structrp png_ptr, png_const_bytep trans_alpha, + png_const_color_16p tran, int num_trans, int color_type) +{ + png_byte buf[6]; + + png_debug(1, "in png_write_tRNS"); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + if (num_trans <= 0 || num_trans > (int)png_ptr->num_palette) + { + png_app_warning(png_ptr, + "Invalid number of transparent colors specified"); + return; + } + + /* Write the chunk out as it is */ + png_write_complete_chunk(png_ptr, png_tRNS, trans_alpha, + (png_size_t)num_trans); + } + + else if (color_type == PNG_COLOR_TYPE_GRAY) + { + /* One 16 bit value */ + if (tran->gray >= (1 << png_ptr->bit_depth)) + { + png_app_warning(png_ptr, + "Ignoring attempt to write tRNS chunk out-of-range for bit_depth"); + + return; + } + + png_save_uint_16(buf, tran->gray); + png_write_complete_chunk(png_ptr, png_tRNS, buf, (png_size_t)2); + } + + else if (color_type == PNG_COLOR_TYPE_RGB) + { + /* Three 16 bit values */ + png_save_uint_16(buf, tran->red); + png_save_uint_16(buf + 2, tran->green); + png_save_uint_16(buf + 4, tran->blue); +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4])) +#else + if (buf[0] | buf[2] | buf[4]) +#endif + { + png_app_warning(png_ptr, + "Ignoring attempt to write 16-bit tRNS chunk when bit_depth is 8"); + return; + } + + png_write_complete_chunk(png_ptr, png_tRNS, buf, (png_size_t)6); + } + + else + { + png_app_warning(png_ptr, "Can't write tRNS with an alpha channel"); + } +} +#endif + +#ifdef PNG_WRITE_bKGD_SUPPORTED +/* Write the background chunk */ +void /* PRIVATE */ +png_write_bKGD(png_structrp png_ptr, png_const_color_16p back, int color_type) +{ + png_byte buf[6]; + + png_debug(1, "in png_write_bKGD"); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + if ( +#ifdef PNG_MNG_FEATURES_SUPPORTED + (png_ptr->num_palette || + (!(png_ptr->mng_features_permitted & PNG_FLAG_MNG_EMPTY_PLTE))) && +#endif + back->index >= png_ptr->num_palette) + { + png_warning(png_ptr, "Invalid background palette index"); + return; + } + + buf[0] = back->index; + png_write_complete_chunk(png_ptr, png_bKGD, buf, (png_size_t)1); + } + + else if (color_type & PNG_COLOR_MASK_COLOR) + { + png_save_uint_16(buf, back->red); + png_save_uint_16(buf + 2, back->green); + png_save_uint_16(buf + 4, back->blue); +#ifdef PNG_WRITE_16BIT_SUPPORTED + if (png_ptr->bit_depth == 8 && (buf[0] | buf[2] | buf[4])) +#else + if (buf[0] | buf[2] | buf[4]) +#endif + { + png_warning(png_ptr, + "Ignoring attempt to write 16-bit bKGD chunk when bit_depth is 8"); + + return; + } + + png_write_complete_chunk(png_ptr, png_bKGD, buf, (png_size_t)6); + } + + else + { + if (back->gray >= (1 << png_ptr->bit_depth)) + { + png_warning(png_ptr, + "Ignoring attempt to write bKGD chunk out-of-range for bit_depth"); + + return; + } + + png_save_uint_16(buf, back->gray); + png_write_complete_chunk(png_ptr, png_bKGD, buf, (png_size_t)2); + } +} +#endif + +#ifdef PNG_WRITE_hIST_SUPPORTED +/* Write the histogram */ +void /* PRIVATE */ +png_write_hIST(png_structrp png_ptr, png_const_uint_16p hist, int num_hist) +{ + int i; + png_byte buf[3]; + + png_debug(1, "in png_write_hIST"); + + if (num_hist > (int)png_ptr->num_palette) + { + png_debug2(3, "num_hist = %d, num_palette = %d", num_hist, + png_ptr->num_palette); + + png_warning(png_ptr, "Invalid number of histogram entries specified"); + return; + } + + png_write_chunk_header(png_ptr, png_hIST, (png_uint_32)(num_hist * 2)); + + for (i = 0; i < num_hist; i++) + { + png_save_uint_16(buf, hist[i]); + png_write_chunk_data(png_ptr, buf, (png_size_t)2); + } + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_tEXt_SUPPORTED +/* Write a tEXt chunk */ +void /* PRIVATE */ +png_write_tEXt(png_structrp png_ptr, png_const_charp key, png_const_charp text, + png_size_t text_len) +{ + png_uint_32 key_len; + png_byte new_key[80]; + + png_debug(1, "in png_write_tEXt"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "tEXt: invalid keyword"); + + if (text == NULL || *text == '\0') + text_len = 0; + + else + text_len = strlen(text); + + if (text_len > PNG_UINT_31_MAX - (key_len+1)) + png_error(png_ptr, "tEXt: text too long"); + + /* Make sure we include the 0 after the key */ + png_write_chunk_header(png_ptr, png_tEXt, + (png_uint_32)/*checked above*/(key_len + text_len + 1)); + /* + * We leave it to the application to meet PNG-1.0 requirements on the + * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of + * any non-Latin-1 characters except for NEWLINE. ISO PNG will forbid them. + * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. + */ + png_write_chunk_data(png_ptr, new_key, key_len + 1); + + if (text_len) + png_write_chunk_data(png_ptr, (png_const_bytep)text, text_len); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_zTXt_SUPPORTED +/* Write a compressed text chunk */ +void /* PRIVATE */ +png_write_zTXt(png_structrp png_ptr, png_const_charp key, png_const_charp text, + png_size_t text_len, int compression) +{ + png_uint_32 key_len; + png_byte new_key[81]; + compression_state comp; + + png_debug(1, "in png_write_zTXt"); + PNG_UNUSED(text_len) /* Always use strlen */ + + if (compression == PNG_TEXT_COMPRESSION_NONE) + { + png_write_tEXt(png_ptr, key, text, 0); + return; + } + + if (compression != PNG_TEXT_COMPRESSION_zTXt) + png_error(png_ptr, "zTXt: invalid compression type"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "zTXt: invalid keyword"); + + /* Add the compression method and 1 for the keyword separator. */ + new_key[++key_len] = PNG_COMPRESSION_TYPE_BASE; + ++key_len; + + /* Compute the compressed data; do it now for the length */ + png_text_compress_init(&comp, (png_const_bytep)text, + text == NULL ? 0 : strlen(text)); + + if (png_text_compress(png_ptr, png_zTXt, &comp, key_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + + /* Write start of chunk */ + png_write_chunk_header(png_ptr, png_zTXt, key_len + comp.output_len); + + /* Write key */ + png_write_chunk_data(png_ptr, new_key, key_len); + + /* Write the compressed data */ + png_write_compressed_data_out(png_ptr, &comp); + + /* Close the chunk */ + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_iTXt_SUPPORTED +/* Write an iTXt chunk */ +void /* PRIVATE */ +png_write_iTXt(png_structrp png_ptr, int compression, png_const_charp key, + png_const_charp lang, png_const_charp lang_key, png_const_charp text) +{ + png_uint_32 key_len, prefix_len; + png_size_t lang_len, lang_key_len; + png_byte new_key[82]; + compression_state comp; + + png_debug(1, "in png_write_iTXt"); + + key_len = png_check_keyword(png_ptr, key, new_key); + + if (key_len == 0) + png_error(png_ptr, "iTXt: invalid keyword"); + + /* Set the compression flag */ + switch (compression) + { + case PNG_ITXT_COMPRESSION_NONE: + case PNG_TEXT_COMPRESSION_NONE: + compression = new_key[++key_len] = 0; /* no compression */ + break; + + case PNG_TEXT_COMPRESSION_zTXt: + case PNG_ITXT_COMPRESSION_zTXt: + compression = new_key[++key_len] = 1; /* compressed */ + break; + + default: + png_error(png_ptr, "iTXt: invalid compression"); + } + + new_key[++key_len] = PNG_COMPRESSION_TYPE_BASE; + ++key_len; /* for the keywod separator */ + + /* We leave it to the application to meet PNG-1.0 requirements on the + * contents of the text. PNG-1.0 through PNG-1.2 discourage the use of + * any non-Latin-1 characters except for NEWLINE. ISO PNG, however, + * specifies that the text is UTF-8 and this really doesn't require any + * checking. + * + * The NUL character is forbidden by PNG-1.0 through PNG-1.2 and ISO PNG. + * + * TODO: validate the language tag correctly (see the spec.) + */ + if (lang == NULL) lang = ""; /* empty language is valid */ + lang_len = strlen(lang)+1; + if (lang_key == NULL) lang_key = ""; /* may be empty */ + lang_key_len = strlen(lang_key)+1; + if (text == NULL) text = ""; /* may be empty */ + + prefix_len = key_len; + if (lang_len > PNG_UINT_31_MAX-prefix_len) + prefix_len = PNG_UINT_31_MAX; + else + prefix_len = (png_uint_32)(prefix_len + lang_len); + + if (lang_key_len > PNG_UINT_31_MAX-prefix_len) + prefix_len = PNG_UINT_31_MAX; + else + prefix_len = (png_uint_32)(prefix_len + lang_key_len); + + png_text_compress_init(&comp, (png_const_bytep)text, strlen(text)); + + if (compression) + { + if (png_text_compress(png_ptr, png_iTXt, &comp, prefix_len) != Z_OK) + png_error(png_ptr, png_ptr->zstream.msg); + } + + else + { + if (comp.input_len > PNG_UINT_31_MAX-prefix_len) + png_error(png_ptr, "iTXt: uncompressed text too long"); + } + + png_write_chunk_header(png_ptr, png_iTXt, comp.output_len + prefix_len); + + png_write_chunk_data(png_ptr, new_key, key_len); + + png_write_chunk_data(png_ptr, (png_const_bytep)lang, lang_len); + + png_write_chunk_data(png_ptr, (png_const_bytep)lang_key, lang_key_len); + + if (compression) + png_write_compressed_data_out(png_ptr, &comp); + + else + png_write_chunk_data(png_ptr, (png_const_bytep)text, comp.input_len); + + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_oFFs_SUPPORTED +/* Write the oFFs chunk */ +void /* PRIVATE */ +png_write_oFFs(png_structrp png_ptr, png_int_32 x_offset, png_int_32 y_offset, + int unit_type) +{ + png_byte buf[9]; + + png_debug(1, "in png_write_oFFs"); + + if (unit_type >= PNG_OFFSET_LAST) + png_warning(png_ptr, "Unrecognized unit type for oFFs chunk"); + + png_save_int_32(buf, x_offset); + png_save_int_32(buf + 4, y_offset); + buf[8] = (png_byte)unit_type; + + png_write_complete_chunk(png_ptr, png_oFFs, buf, (png_size_t)9); +} +#endif +#ifdef PNG_WRITE_pCAL_SUPPORTED +/* Write the pCAL chunk (described in the PNG extensions document) */ +void /* PRIVATE */ +png_write_pCAL(png_structrp png_ptr, png_charp purpose, png_int_32 X0, + png_int_32 X1, int type, int nparams, png_const_charp units, + png_charpp params) +{ + png_uint_32 purpose_len; + png_size_t units_len, total_len; + png_size_tp params_len; + png_byte buf[10]; + png_byte new_purpose[80]; + int i; + + png_debug1(1, "in png_write_pCAL (%d parameters)", nparams); + + if (type >= PNG_EQUATION_LAST) + png_error(png_ptr, "Unrecognized equation type for pCAL chunk"); + + purpose_len = png_check_keyword(png_ptr, purpose, new_purpose); + + if (purpose_len == 0) + png_error(png_ptr, "pCAL: invalid keyword"); + + ++purpose_len; /* terminator */ + + png_debug1(3, "pCAL purpose length = %d", (int)purpose_len); + units_len = strlen(units) + (nparams == 0 ? 0 : 1); + png_debug1(3, "pCAL units length = %d", (int)units_len); + total_len = purpose_len + units_len + 10; + + params_len = (png_size_tp)png_malloc(png_ptr, + (png_alloc_size_t)(nparams * (sizeof (png_size_t)))); + + /* Find the length of each parameter, making sure we don't count the + * null terminator for the last parameter. + */ + for (i = 0; i < nparams; i++) + { + params_len[i] = strlen(params[i]) + (i == nparams - 1 ? 0 : 1); + png_debug2(3, "pCAL parameter %d length = %lu", i, + (unsigned long)params_len[i]); + total_len += params_len[i]; + } + + png_debug1(3, "pCAL total length = %d", (int)total_len); + png_write_chunk_header(png_ptr, png_pCAL, (png_uint_32)total_len); + png_write_chunk_data(png_ptr, new_purpose, purpose_len); + png_save_int_32(buf, X0); + png_save_int_32(buf + 4, X1); + buf[8] = (png_byte)type; + buf[9] = (png_byte)nparams; + png_write_chunk_data(png_ptr, buf, (png_size_t)10); + png_write_chunk_data(png_ptr, (png_const_bytep)units, (png_size_t)units_len); + + for (i = 0; i < nparams; i++) + { + png_write_chunk_data(png_ptr, (png_const_bytep)params[i], params_len[i]); + } + + png_free(png_ptr, params_len); + png_write_chunk_end(png_ptr); +} +#endif + +#ifdef PNG_WRITE_sCAL_SUPPORTED +/* Write the sCAL chunk */ +void /* PRIVATE */ +png_write_sCAL_s(png_structrp png_ptr, int unit, png_const_charp width, + png_const_charp height) +{ + png_byte buf[64]; + png_size_t wlen, hlen, total_len; + + png_debug(1, "in png_write_sCAL_s"); + + wlen = strlen(width); + hlen = strlen(height); + total_len = wlen + hlen + 2; + + if (total_len > 64) + { + png_warning(png_ptr, "Can't write sCAL (buffer too small)"); + return; + } + + buf[0] = (png_byte)unit; + memcpy(buf + 1, width, wlen + 1); /* Append the '\0' here */ + memcpy(buf + wlen + 2, height, hlen); /* Do NOT append the '\0' here */ + + png_debug1(3, "sCAL total length = %u", (unsigned int)total_len); + png_write_complete_chunk(png_ptr, png_sCAL, buf, total_len); +} +#endif + +#ifdef PNG_WRITE_pHYs_SUPPORTED +/* Write the pHYs chunk */ +void /* PRIVATE */ +png_write_pHYs(png_structrp png_ptr, png_uint_32 x_pixels_per_unit, + png_uint_32 y_pixels_per_unit, + int unit_type) +{ + png_byte buf[9]; + + png_debug(1, "in png_write_pHYs"); + + if (unit_type >= PNG_RESOLUTION_LAST) + png_warning(png_ptr, "Unrecognized unit type for pHYs chunk"); + + png_save_uint_32(buf, x_pixels_per_unit); + png_save_uint_32(buf + 4, y_pixels_per_unit); + buf[8] = (png_byte)unit_type; + + png_write_complete_chunk(png_ptr, png_pHYs, buf, (png_size_t)9); +} +#endif + +#ifdef PNG_WRITE_tIME_SUPPORTED +/* Write the tIME chunk. Use either png_convert_from_struct_tm() + * or png_convert_from_time_t(), or fill in the structure yourself. + */ +void /* PRIVATE */ +png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) +{ + png_byte buf[7]; + + png_debug(1, "in png_write_tIME"); + + if (mod_time->month > 12 || mod_time->month < 1 || + mod_time->day > 31 || mod_time->day < 1 || + mod_time->hour > 23 || mod_time->second > 60) + { + png_warning(png_ptr, "Invalid time specified for tIME chunk"); + return; + } + + png_save_uint_16(buf, mod_time->year); + buf[2] = mod_time->month; + buf[3] = mod_time->day; + buf[4] = mod_time->hour; + buf[5] = mod_time->minute; + buf[6] = mod_time->second; + + png_write_complete_chunk(png_ptr, png_tIME, buf, (png_size_t)7); +} +#endif + +/* Initializes the row writing capability of libpng */ +void /* PRIVATE */ +png_write_start_row(png_structrp png_ptr) +{ +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static PNG_CONST png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static PNG_CONST png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif + + png_alloc_size_t buf_size; + int usr_pixel_depth; + + png_debug(1, "in png_write_start_row"); + + usr_pixel_depth = png_ptr->usr_channels * png_ptr->usr_bit_depth; + buf_size = PNG_ROWBYTES(usr_pixel_depth, png_ptr->width) + 1; + + /* 1.5.6: added to allow checking in the row write code. */ + png_ptr->transformed_pixel_depth = png_ptr->pixel_depth; + png_ptr->maximum_pixel_depth = (png_byte)usr_pixel_depth; + + /* Set up row buffer */ + png_ptr->row_buf = (png_bytep)png_malloc(png_ptr, buf_size); + + png_ptr->row_buf[0] = PNG_FILTER_VALUE_NONE; + +#ifdef PNG_WRITE_FILTER_SUPPORTED + /* Set up filtering buffer, if using this filter */ + if (png_ptr->do_filter & PNG_FILTER_SUB) + { + png_ptr->sub_row = (png_bytep)png_malloc(png_ptr, png_ptr->rowbytes + 1); + + png_ptr->sub_row[0] = PNG_FILTER_VALUE_SUB; + } + + /* We only need to keep the previous row if we are using one of these. */ + if (png_ptr->do_filter & (PNG_FILTER_AVG | PNG_FILTER_UP | PNG_FILTER_PAETH)) + { + /* Set up previous row buffer */ + png_ptr->prev_row = (png_bytep)png_calloc(png_ptr, buf_size); + + if (png_ptr->do_filter & PNG_FILTER_UP) + { + png_ptr->up_row = (png_bytep)png_malloc(png_ptr, + png_ptr->rowbytes + 1); + + png_ptr->up_row[0] = PNG_FILTER_VALUE_UP; + } + + if (png_ptr->do_filter & PNG_FILTER_AVG) + { + png_ptr->avg_row = (png_bytep)png_malloc(png_ptr, + png_ptr->rowbytes + 1); + + png_ptr->avg_row[0] = PNG_FILTER_VALUE_AVG; + } + + if (png_ptr->do_filter & PNG_FILTER_PAETH) + { + png_ptr->paeth_row = (png_bytep)png_malloc(png_ptr, + png_ptr->rowbytes + 1); + + png_ptr->paeth_row[0] = PNG_FILTER_VALUE_PAETH; + } + } +#endif /* PNG_WRITE_FILTER_SUPPORTED */ + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced, we need to set up width and height of pass */ + if (png_ptr->interlaced) + { + if (!(png_ptr->transformations & PNG_INTERLACE)) + { + png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - + png_pass_ystart[0]) / png_pass_yinc[0]; + + png_ptr->usr_width = (png_ptr->width + png_pass_inc[0] - 1 - + png_pass_start[0]) / png_pass_inc[0]; + } + + else + { + png_ptr->num_rows = png_ptr->height; + png_ptr->usr_width = png_ptr->width; + } + } + + else +#endif + { + png_ptr->num_rows = png_ptr->height; + png_ptr->usr_width = png_ptr->width; + } +} + +/* Internal use only. Called when finished processing a row of data. */ +void /* PRIVATE */ +png_write_finish_row(png_structrp png_ptr) +{ +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + static PNG_CONST png_byte png_pass_ystart[7] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + static PNG_CONST png_byte png_pass_yinc[7] = {8, 8, 8, 4, 4, 2, 2}; +#endif + + png_debug(1, "in png_write_finish_row"); + + /* Next row */ + png_ptr->row_number++; + + /* See if we are done */ + if (png_ptr->row_number < png_ptr->num_rows) + return; + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED + /* If interlaced, go to next pass */ + if (png_ptr->interlaced) + { + png_ptr->row_number = 0; + if (png_ptr->transformations & PNG_INTERLACE) + { + png_ptr->pass++; + } + + else + { + /* Loop until we find a non-zero width or height pass */ + do + { + png_ptr->pass++; + + if (png_ptr->pass >= 7) + break; + + png_ptr->usr_width = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + + png_ptr->num_rows = (png_ptr->height + + png_pass_yinc[png_ptr->pass] - 1 - + png_pass_ystart[png_ptr->pass]) / + png_pass_yinc[png_ptr->pass]; + + if (png_ptr->transformations & PNG_INTERLACE) + break; + + } while (png_ptr->usr_width == 0 || png_ptr->num_rows == 0); + + } + + /* Reset the row above the image for the next pass */ + if (png_ptr->pass < 7) + { + if (png_ptr->prev_row != NULL) + memset(png_ptr->prev_row, 0, + (png_size_t)(PNG_ROWBYTES(png_ptr->usr_channels* + png_ptr->usr_bit_depth, png_ptr->width)) + 1); + + return; + } + } +#endif + + /* If we get here, we've just written the last row, so we need + to flush the compressor */ + png_compress_IDAT(png_ptr, NULL, 0, Z_FINISH); +} + +#ifdef PNG_WRITE_INTERLACING_SUPPORTED +/* Pick out the correct pixels for the interlace pass. + * The basic idea here is to go through the row with a source + * pointer and a destination pointer (sp and dp), and copy the + * correct pixels for the pass. As the row gets compacted, + * sp will always be >= dp, so we should never overwrite anything. + * See the default: case for the easiest code to understand. + */ +void /* PRIVATE */ +png_do_write_interlace(png_row_infop row_info, png_bytep row, int pass) +{ + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + static PNG_CONST png_byte png_pass_start[7] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + static PNG_CONST png_byte png_pass_inc[7] = {8, 8, 4, 4, 2, 2, 1}; + + png_debug(1, "in png_do_write_interlace"); + + /* We don't have to do anything on the last pass (6) */ + if (pass < 6) + { + /* Each pixel depth is handled separately */ + switch (row_info->pixel_depth) + { + case 1: + { + png_bytep sp; + png_bytep dp; + int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + d = 0; + shift = 7; + + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (png_size_t)(i >> 3); + value = (int)(*sp >> (7 - (int)(i & 0x07))) & 0x01; + d |= (value << shift); + + if (shift == 0) + { + shift = 7; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift--; + + } + if (shift != 7) + *dp = (png_byte)d; + + break; + } + + case 2: + { + png_bytep sp; + png_bytep dp; + int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + shift = 6; + d = 0; + + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (png_size_t)(i >> 2); + value = (*sp >> ((3 - (int)(i & 0x03)) << 1)) & 0x03; + d |= (value << shift); + + if (shift == 0) + { + shift = 6; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift -= 2; + } + if (shift != 6) + *dp = (png_byte)d; + + break; + } + + case 4: + { + png_bytep sp; + png_bytep dp; + int shift; + int d; + int value; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + + dp = row; + shift = 4; + d = 0; + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + sp = row + (png_size_t)(i >> 1); + value = (*sp >> ((1 - (int)(i & 0x01)) << 2)) & 0x0f; + d |= (value << shift); + + if (shift == 0) + { + shift = 4; + *dp++ = (png_byte)d; + d = 0; + } + + else + shift -= 4; + } + if (shift != 4) + *dp = (png_byte)d; + + break; + } + + default: + { + png_bytep sp; + png_bytep dp; + png_uint_32 i; + png_uint_32 row_width = row_info->width; + png_size_t pixel_bytes; + + /* Start at the beginning */ + dp = row; + + /* Find out how many bytes each pixel takes up */ + pixel_bytes = (row_info->pixel_depth >> 3); + + /* Loop through the row, only looking at the pixels that matter */ + for (i = png_pass_start[pass]; i < row_width; + i += png_pass_inc[pass]) + { + /* Find out where the original pixel is */ + sp = row + (png_size_t)i * pixel_bytes; + + /* Move the pixel */ + if (dp != sp) + memcpy(dp, sp, pixel_bytes); + + /* Next pixel */ + dp += pixel_bytes; + } + break; + } + } + /* Set new row width */ + row_info->width = (row_info->width + + png_pass_inc[pass] - 1 - + png_pass_start[pass]) / + png_pass_inc[pass]; + + row_info->rowbytes = PNG_ROWBYTES(row_info->pixel_depth, + row_info->width); + } +} +#endif + +/* This filters the row, chooses which filter to use, if it has not already + * been specified by the application, and then writes the row out with the + * chosen filter. + */ +static void png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, + png_size_t row_bytes); + +#define PNG_MAXSUM (((png_uint_32)(-1)) >> 1) +#define PNG_HISHIFT 10 +#define PNG_LOMASK ((png_uint_32)0xffffL) +#define PNG_HIMASK ((png_uint_32)(~PNG_LOMASK >> PNG_HISHIFT)) +void /* PRIVATE */ +png_write_find_filter(png_structrp png_ptr, png_row_infop row_info) +{ + png_bytep best_row; +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_bytep prev_row, row_buf; + png_uint_32 mins, bpp; + png_byte filter_to_do = png_ptr->do_filter; + png_size_t row_bytes = row_info->rowbytes; +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + int num_p_filters = png_ptr->num_prev_filters; +#endif + + png_debug(1, "in png_write_find_filter"); + +#ifndef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->row_number == 0 && filter_to_do == PNG_ALL_FILTERS) + { + /* These will never be selected so we need not test them. */ + filter_to_do &= ~(PNG_FILTER_UP | PNG_FILTER_PAETH); + } +#endif + + /* Find out how many bytes offset each pixel is */ + bpp = (row_info->pixel_depth + 7) >> 3; + + prev_row = png_ptr->prev_row; +#endif + best_row = png_ptr->row_buf; +#ifdef PNG_WRITE_FILTER_SUPPORTED + row_buf = best_row; + mins = PNG_MAXSUM; + + /* The prediction method we use is to find which method provides the + * smallest value when summing the absolute values of the distances + * from zero, using anything >= 128 as negative numbers. This is known + * as the "minimum sum of absolute differences" heuristic. Other + * heuristics are the "weighted minimum sum of absolute differences" + * (experimental and can in theory improve compression), and the "zlib + * predictive" method (not implemented yet), which does test compressions + * of lines using different filter methods, and then chooses the + * (series of) filter(s) that give minimum compressed data size (VERY + * computationally expensive). + * + * GRR 980525: consider also + * + * (1) minimum sum of absolute differences from running average (i.e., + * keep running sum of non-absolute differences & count of bytes) + * [track dispersion, too? restart average if dispersion too large?] + * + * (1b) minimum sum of absolute differences from sliding average, probably + * with window size <= deflate window (usually 32K) + * + * (2) minimum sum of squared differences from zero or running average + * (i.e., ~ root-mean-square approach) + */ + + + /* We don't need to test the 'no filter' case if this is the only filter + * that has been chosen, as it doesn't actually do anything to the data. + */ + if ((filter_to_do & PNG_FILTER_NONE) && filter_to_do != PNG_FILTER_NONE) + { + png_bytep rp; + png_uint_32 sum = 0; + png_size_t i; + int v; + + for (i = 0, rp = row_buf + 1; i < row_bytes; i++, rp++) + { + v = *rp; + sum += (v < 128) ? v : 256 - v; + } + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + png_uint_32 sumhi, sumlo; + int j; + sumlo = sum & PNG_LOMASK; + sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; /* Gives us some footroom */ + + /* Reduce the sum if we match any of the previous rows */ + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE) + { + sumlo = (sumlo * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + sumhi = (sumhi * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + /* Factor in the cost of this filter (this is here for completeness, + * but it makes no sense to have a "cost" for the NONE filter, as + * it has the minimum possible computational cost - none). + */ + sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >> + PNG_COST_SHIFT; + + sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_NONE]) >> + PNG_COST_SHIFT; + + if (sumhi > PNG_HIMASK) + sum = PNG_MAXSUM; + + else + sum = (sumhi << PNG_HISHIFT) + sumlo; + } +#endif + mins = sum; + } + + /* Sub filter */ + if (filter_to_do == PNG_FILTER_SUB) + /* It's the only filter so no testing is needed */ + { + png_bytep rp, lp, dp; + png_size_t i; + + for (i = 0, rp = row_buf + 1, dp = png_ptr->sub_row + 1; i < bpp; + i++, rp++, dp++) + { + *dp = *rp; + } + + for (lp = row_buf + 1; i < row_bytes; + i++, rp++, lp++, dp++) + { + *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff); + } + + best_row = png_ptr->sub_row; + } + + else if (filter_to_do & PNG_FILTER_SUB) + { + png_bytep rp, dp, lp; + png_uint_32 sum = 0, lmins = mins; + png_size_t i; + int v; + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + /* We temporarily increase the "minimum sum" by the factor we + * would reduce the sum of this filter, so that we can do the + * early exit comparison without scaling the sum each time. + */ + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 lmhi, lmlo; + lmlo = lmins & PNG_LOMASK; + lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB) + { + lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> + PNG_COST_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> + PNG_COST_SHIFT; + + if (lmhi > PNG_HIMASK) + lmins = PNG_MAXSUM; + + else + lmins = (lmhi << PNG_HISHIFT) + lmlo; + } +#endif + + for (i = 0, rp = row_buf + 1, dp = png_ptr->sub_row + 1; i < bpp; + i++, rp++, dp++) + { + v = *dp = *rp; + + sum += (v < 128) ? v : 256 - v; + } + + for (lp = row_buf + 1; i < row_bytes; + i++, rp++, lp++, dp++) + { + v = *dp = (png_byte)(((int)*rp - (int)*lp) & 0xff); + + sum += (v < 128) ? v : 256 - v; + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 sumhi, sumlo; + sumlo = sum & PNG_LOMASK; + sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_SUB) + { + sumlo = (sumlo * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + sumhi = (sumhi * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + sumlo = (sumlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> + PNG_COST_SHIFT; + + sumhi = (sumhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_SUB]) >> + PNG_COST_SHIFT; + + if (sumhi > PNG_HIMASK) + sum = PNG_MAXSUM; + + else + sum = (sumhi << PNG_HISHIFT) + sumlo; + } +#endif + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->sub_row; + } + } + + /* Up filter */ + if (filter_to_do == PNG_FILTER_UP) + { + png_bytep rp, dp, pp; + png_size_t i; + + for (i = 0, rp = row_buf + 1, dp = png_ptr->up_row + 1, + pp = prev_row + 1; i < row_bytes; + i++, rp++, pp++, dp++) + { + *dp = (png_byte)(((int)*rp - (int)*pp) & 0xff); + } + + best_row = png_ptr->up_row; + } + + else if (filter_to_do & PNG_FILTER_UP) + { + png_bytep rp, dp, pp; + png_uint_32 sum = 0, lmins = mins; + png_size_t i; + int v; + + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 lmhi, lmlo; + lmlo = lmins & PNG_LOMASK; + lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP) + { + lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >> + PNG_COST_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_UP]) >> + PNG_COST_SHIFT; + + if (lmhi > PNG_HIMASK) + lmins = PNG_MAXSUM; + + else + lmins = (lmhi << PNG_HISHIFT) + lmlo; + } +#endif + + for (i = 0, rp = row_buf + 1, dp = png_ptr->up_row + 1, + pp = prev_row + 1; i < row_bytes; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff); + + sum += (v < 128) ? v : 256 - v; + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 sumhi, sumlo; + sumlo = sum & PNG_LOMASK; + sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_UP) + { + sumlo = (sumlo * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + sumhi = (sumhi * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >> + PNG_COST_SHIFT; + + sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_UP]) >> + PNG_COST_SHIFT; + + if (sumhi > PNG_HIMASK) + sum = PNG_MAXSUM; + + else + sum = (sumhi << PNG_HISHIFT) + sumlo; + } +#endif + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->up_row; + } + } + + /* Avg filter */ + if (filter_to_do == PNG_FILTER_AVG) + { + png_bytep rp, dp, pp, lp; + png_uint_32 i; + + for (i = 0, rp = row_buf + 1, dp = png_ptr->avg_row + 1, + pp = prev_row + 1; i < bpp; i++) + { + *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff); + } + + for (lp = row_buf + 1; i < row_bytes; i++) + { + *dp++ = (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2)) + & 0xff); + } + best_row = png_ptr->avg_row; + } + + else if (filter_to_do & PNG_FILTER_AVG) + { + png_bytep rp, dp, pp, lp; + png_uint_32 sum = 0, lmins = mins; + png_size_t i; + int v; + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 lmhi, lmlo; + lmlo = lmins & PNG_LOMASK; + lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_AVG) + { + lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >> + PNG_COST_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_AVG]) >> + PNG_COST_SHIFT; + + if (lmhi > PNG_HIMASK) + lmins = PNG_MAXSUM; + + else + lmins = (lmhi << PNG_HISHIFT) + lmlo; + } +#endif + + for (i = 0, rp = row_buf + 1, dp = png_ptr->avg_row + 1, + pp = prev_row + 1; i < bpp; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - ((int)*pp++ / 2)) & 0xff); + + sum += (v < 128) ? v : 256 - v; + } + + for (lp = row_buf + 1; i < row_bytes; i++) + { + v = *dp++ = + (png_byte)(((int)*rp++ - (((int)*pp++ + (int)*lp++) / 2)) & 0xff); + + sum += (v < 128) ? v : 256 - v; + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 sumhi, sumlo; + sumlo = sum & PNG_LOMASK; + sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_NONE) + { + sumlo = (sumlo * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + sumhi = (sumhi * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >> + PNG_COST_SHIFT; + + sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_AVG]) >> + PNG_COST_SHIFT; + + if (sumhi > PNG_HIMASK) + sum = PNG_MAXSUM; + + else + sum = (sumhi << PNG_HISHIFT) + sumlo; + } +#endif + + if (sum < mins) + { + mins = sum; + best_row = png_ptr->avg_row; + } + } + + /* Paeth filter */ + if (filter_to_do == PNG_FILTER_PAETH) + { + png_bytep rp, dp, pp, cp, lp; + png_size_t i; + + for (i = 0, rp = row_buf + 1, dp = png_ptr->paeth_row + 1, + pp = prev_row + 1; i < bpp; i++) + { + *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff); + } + + for (lp = row_buf + 1, cp = prev_row + 1; i < row_bytes; i++) + { + int a, b, c, pa, pb, pc, p; + + b = *pp++; + c = *cp++; + a = *lp++; + + p = b - c; + pc = a - c; + +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + + p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c; + + *dp++ = (png_byte)(((int)*rp++ - p) & 0xff); + } + best_row = png_ptr->paeth_row; + } + + else if (filter_to_do & PNG_FILTER_PAETH) + { + png_bytep rp, dp, pp, cp, lp; + png_uint_32 sum = 0, lmins = mins; + png_size_t i; + int v; + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 lmhi, lmlo; + lmlo = lmins & PNG_LOMASK; + lmhi = (lmins >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH) + { + lmlo = (lmlo * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + lmlo = (lmlo * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >> + PNG_COST_SHIFT; + + lmhi = (lmhi * png_ptr->inv_filter_costs[PNG_FILTER_VALUE_PAETH]) >> + PNG_COST_SHIFT; + + if (lmhi > PNG_HIMASK) + lmins = PNG_MAXSUM; + + else + lmins = (lmhi << PNG_HISHIFT) + lmlo; + } +#endif + + for (i = 0, rp = row_buf + 1, dp = png_ptr->paeth_row + 1, + pp = prev_row + 1; i < bpp; i++) + { + v = *dp++ = (png_byte)(((int)*rp++ - (int)*pp++) & 0xff); + + sum += (v < 128) ? v : 256 - v; + } + + for (lp = row_buf + 1, cp = prev_row + 1; i < row_bytes; i++) + { + int a, b, c, pa, pb, pc, p; + + b = *pp++; + c = *cp++; + a = *lp++; + +#ifndef PNG_SLOW_PAETH + p = b - c; + pc = a - c; +#ifdef PNG_USE_ABS + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); +#else + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; +#endif + p = (pa <= pb && pa <=pc) ? a : (pb <= pc) ? b : c; +#else /* PNG_SLOW_PAETH */ + p = a + b - c; + pa = abs(p - a); + pb = abs(p - b); + pc = abs(p - c); + + if (pa <= pb && pa <= pc) + p = a; + + else if (pb <= pc) + p = b; + + else + p = c; +#endif /* PNG_SLOW_PAETH */ + + v = *dp++ = (png_byte)(((int)*rp++ - p) & 0xff); + + sum += (v < 128) ? v : 256 - v; + + if (sum > lmins) /* We are already worse, don't continue. */ + break; + } + +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + if (png_ptr->heuristic_method == PNG_FILTER_HEURISTIC_WEIGHTED) + { + int j; + png_uint_32 sumhi, sumlo; + sumlo = sum & PNG_LOMASK; + sumhi = (sum >> PNG_HISHIFT) & PNG_HIMASK; + + for (j = 0; j < num_p_filters; j++) + { + if (png_ptr->prev_filters[j] == PNG_FILTER_VALUE_PAETH) + { + sumlo = (sumlo * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + + sumhi = (sumhi * png_ptr->filter_weights[j]) >> + PNG_WEIGHT_SHIFT; + } + } + + sumlo = (sumlo * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >> + PNG_COST_SHIFT; + + sumhi = (sumhi * png_ptr->filter_costs[PNG_FILTER_VALUE_PAETH]) >> + PNG_COST_SHIFT; + + if (sumhi > PNG_HIMASK) + sum = PNG_MAXSUM; + + else + sum = (sumhi << PNG_HISHIFT) + sumlo; + } +#endif + + if (sum < mins) + { + best_row = png_ptr->paeth_row; + } + } +#endif /* PNG_WRITE_FILTER_SUPPORTED */ + + /* Do the actual writing of the filtered row data from the chosen filter. */ + png_write_filtered_row(png_ptr, best_row, row_info->rowbytes+1); + +#ifdef PNG_WRITE_FILTER_SUPPORTED +#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED + /* Save the type of filter we picked this time for future calculations */ + if (png_ptr->num_prev_filters > 0) + { + int j; + + for (j = 1; j < num_p_filters; j++) + { + png_ptr->prev_filters[j] = png_ptr->prev_filters[j - 1]; + } + + png_ptr->prev_filters[j] = best_row[0]; + } +#endif +#endif /* PNG_WRITE_FILTER_SUPPORTED */ +} + + +/* Do the actual writing of a previously filtered row. */ +static void +png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, + png_size_t full_row_length/*includes filter byte*/) +{ + png_debug(1, "in png_write_filtered_row"); + + png_debug1(2, "filter = %d", filtered_row[0]); + + png_compress_IDAT(png_ptr, filtered_row, full_row_length, Z_NO_FLUSH); + + /* Swap the current and previous rows */ + if (png_ptr->prev_row != NULL) + { + png_bytep tptr; + + tptr = png_ptr->prev_row; + png_ptr->prev_row = png_ptr->row_buf; + png_ptr->row_buf = tptr; + } + + /* Finish row - updates counters and flushes zlib if last row */ + png_write_finish_row(png_ptr); + +#ifdef PNG_WRITE_FLUSH_SUPPORTED + png_ptr->flush_rows++; + + if (png_ptr->flush_dist > 0 && + png_ptr->flush_rows >= png_ptr->flush_dist) + { + png_write_flush(png_ptr); + } +#endif +} +#endif /* PNG_WRITE_SUPPORTED */ diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_Image.cpp b/JuceLibraryCode/modules/juce_graphics/images/juce_Image.cpp new file mode 100644 index 0000000..e7be9a3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_Image.cpp @@ -0,0 +1,659 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ImagePixelData::ImagePixelData (const Image::PixelFormat format, const int w, const int h) + : pixelFormat (format), width (w), height (h) +{ + jassert (format == Image::RGB || format == Image::ARGB || format == Image::SingleChannel); + jassert (w > 0 && h > 0); // It's illegal to create a zero-sized image! +} + +ImagePixelData::~ImagePixelData() +{ + listeners.call (&Listener::imageDataBeingDeleted, this); +} + +void ImagePixelData::sendDataChangeMessage() +{ + listeners.call (&Listener::imageDataChanged, this); +} + +//============================================================================== +ImageType::ImageType() {} +ImageType::~ImageType() {} + +Image ImageType::convert (const Image& source) const +{ + if (source.isNull() || getTypeID() == (ScopedPointer (source.getPixelData()->createType())->getTypeID())) + return source; + + const Image::BitmapData src (source, Image::BitmapData::readOnly); + + Image newImage (create (src.pixelFormat, src.width, src.height, false)); + Image::BitmapData dest (newImage, Image::BitmapData::writeOnly); + + jassert (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat); + + for (int y = 0; y < dest.height; ++y) + memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.lineStride); + + return newImage; +} + +//============================================================================== +class SoftwarePixelData : public ImagePixelData +{ +public: + SoftwarePixelData (const Image::PixelFormat format_, const int w, const int h, const bool clearImage) + : ImagePixelData (format_, w, h), + pixelStride (format_ == Image::RGB ? 3 : ((format_ == Image::ARGB) ? 4 : 1)), + lineStride ((pixelStride * jmax (1, w) + 3) & ~3) + { + imageData.allocate ((size_t) (lineStride * jmax (1, h)), clearImage); + } + + LowLevelGraphicsContext* createLowLevelContext() override + { + sendDataChangeMessage(); + return new LowLevelGraphicsSoftwareRenderer (Image (this)); + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override + { + bitmap.data = imageData + x * pixelStride + y * lineStride; + bitmap.pixelFormat = pixelFormat; + bitmap.lineStride = lineStride; + bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); + } + + ImagePixelData* clone() override + { + SoftwarePixelData* s = new SoftwarePixelData (pixelFormat, width, height, false); + memcpy (s->imageData, imageData, (size_t) (lineStride * height)); + return s; + } + + ImageType* createType() const override { return new SoftwareImageType(); } + +private: + HeapBlock imageData; + const int pixelStride, lineStride; + + JUCE_LEAK_DETECTOR (SoftwarePixelData) +}; + +SoftwareImageType::SoftwareImageType() {} +SoftwareImageType::~SoftwareImageType() {} + +ImagePixelData::Ptr SoftwareImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const +{ + return new SoftwarePixelData (format, width, height, clearImage); +} + +int SoftwareImageType::getTypeID() const +{ + return 2; +} + +//============================================================================== +NativeImageType::NativeImageType() {} +NativeImageType::~NativeImageType() {} + +int NativeImageType::getTypeID() const +{ + return 1; +} + +#if JUCE_WINDOWS || JUCE_LINUX +ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const +{ + return new SoftwarePixelData (format, width, height, clearImage); +} +#endif + +//============================================================================== +class SubsectionPixelData : public ImagePixelData +{ +public: + SubsectionPixelData (ImagePixelData* const im, const Rectangle& r) + : ImagePixelData (im->pixelFormat, r.getWidth(), r.getHeight()), + image (im), area (r) + { + } + + LowLevelGraphicsContext* createLowLevelContext() override + { + LowLevelGraphicsContext* g = image->createLowLevelContext(); + g->clipToRectangle (area); + g->setOrigin (area.getPosition()); + return g; + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override + { + image->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); + + if (mode != Image::BitmapData::readOnly) + sendDataChangeMessage(); + } + + ImagePixelData* clone() override + { + jassert (getReferenceCount() > 0); // (This method can't be used on an unowned pointer, as it will end up self-deleting) + const ScopedPointer type (image->createType()); + + Image newImage (type->create (pixelFormat, area.getWidth(), area.getHeight(), pixelFormat != Image::RGB)); + + { + Graphics g (newImage); + g.drawImageAt (Image (this), 0, 0); + } + + newImage.getPixelData()->incReferenceCount(); + return newImage.getPixelData(); + } + + ImageType* createType() const override { return image->createType(); } + +private: + const ImagePixelData::Ptr image; + const Rectangle area; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubsectionPixelData) +}; + +Image Image::getClippedImage (const Rectangle& area) const +{ + if (area.contains (getBounds())) + return *this; + + const Rectangle validArea (area.getIntersection (getBounds())); + return Image (validArea.isEmpty() ? nullptr : new SubsectionPixelData (image, validArea)); +} + + +//============================================================================== +Image::Image() noexcept +{ +} + +Image::Image (ImagePixelData* const instance) noexcept + : image (instance) +{ +} + +Image::Image (const PixelFormat format, int width, int height, bool clearImage) + : image (NativeImageType().create (format, width, height, clearImage)) +{ +} + +Image::Image (const PixelFormat format, int width, int height, bool clearImage, const ImageType& type) + : image (type.create (format, width, height, clearImage)) +{ +} + +Image::Image (const Image& other) noexcept + : image (other.image) +{ +} + +Image& Image::operator= (const Image& other) +{ + image = other.image; + return *this; +} + +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +Image::Image (Image&& other) noexcept + : image (static_cast (other.image)) +{ +} + +Image& Image::operator= (Image&& other) noexcept +{ + image = static_cast (other.image); + return *this; +} +#endif + +Image::~Image() +{ +} + +const Image Image::null; + +int Image::getReferenceCount() const noexcept { return image == nullptr ? 0 : image->getReferenceCount(); } +int Image::getWidth() const noexcept { return image == nullptr ? 0 : image->width; } +int Image::getHeight() const noexcept { return image == nullptr ? 0 : image->height; } +Rectangle Image::getBounds() const noexcept { return image == nullptr ? Rectangle() : Rectangle (image->width, image->height); } +Image::PixelFormat Image::getFormat() const noexcept { return image == nullptr ? UnknownFormat : image->pixelFormat; } +bool Image::isARGB() const noexcept { return getFormat() == ARGB; } +bool Image::isRGB() const noexcept { return getFormat() == RGB; } +bool Image::isSingleChannel() const noexcept { return getFormat() == SingleChannel; } +bool Image::hasAlphaChannel() const noexcept { return getFormat() != RGB; } + +LowLevelGraphicsContext* Image::createLowLevelContext() const +{ + return image == nullptr ? nullptr : image->createLowLevelContext(); +} + +void Image::duplicateIfShared() +{ + if (image != nullptr && image->getReferenceCount() > 1) + image = image->clone(); +} + +Image Image::createCopy() const +{ + if (image != nullptr) + return Image (image->clone()); + + return Image(); +} + +Image Image::rescaled (const int newWidth, const int newHeight, const Graphics::ResamplingQuality quality) const +{ + if (image == nullptr || (image->width == newWidth && image->height == newHeight)) + return *this; + + const ScopedPointer type (image->createType()); + Image newImage (type->create (image->pixelFormat, newWidth, newHeight, hasAlphaChannel())); + + Graphics g (newImage); + g.setImageResamplingQuality (quality); + g.drawImageTransformed (*this, AffineTransform::scale (newWidth / (float) image->width, + newHeight / (float) image->height), false); + return newImage; +} + +Image Image::convertedToFormat (PixelFormat newFormat) const +{ + if (image == nullptr || newFormat == image->pixelFormat) + return *this; + + const int w = image->width, h = image->height; + + const ScopedPointer type (image->createType()); + Image newImage (type->create (newFormat, w, h, false)); + + if (newFormat == SingleChannel) + { + if (! hasAlphaChannel()) + { + newImage.clear (getBounds(), Colours::black); + } + else + { + const BitmapData destData (newImage, 0, 0, w, h, BitmapData::writeOnly); + const BitmapData srcData (*this, 0, 0, w, h); + + for (int y = 0; y < h; ++y) + { + const PixelARGB* const src = (const PixelARGB*) srcData.getLinePointer (y); + uint8* const dst = destData.getLinePointer (y); + + for (int x = 0; x < w; ++x) + dst[x] = src[x].getAlpha(); + } + } + } + else if (image->pixelFormat == SingleChannel && newFormat == Image::ARGB) + { + const BitmapData destData (newImage, 0, 0, w, h, BitmapData::writeOnly); + const BitmapData srcData (*this, 0, 0, w, h); + + for (int y = 0; y < h; ++y) + { + const PixelAlpha* const src = (const PixelAlpha*) srcData.getLinePointer (y); + PixelARGB* const dst = (PixelARGB*) destData.getLinePointer (y); + + for (int x = 0; x < w; ++x) + dst[x].set (src[x]); + } + } + else + { + if (hasAlphaChannel()) + newImage.clear (getBounds()); + + Graphics g (newImage); + g.drawImageAt (*this, 0, 0); + } + + return newImage; +} + +NamedValueSet* Image::getProperties() const +{ + return image == nullptr ? nullptr : &(image->userData); +} + +//============================================================================== +Image::BitmapData::BitmapData (Image& im, const int x, const int y, const int w, const int h, BitmapData::ReadWriteMode mode) + : width (w), height (h) +{ + // The BitmapData class must be given a valid image, and a valid rectangle within it! + jassert (im.image != nullptr); + jassert (x >= 0 && y >= 0 && w > 0 && h > 0 && x + w <= im.getWidth() && y + h <= im.getHeight()); + + im.image->initialiseBitmapData (*this, x, y, mode); + jassert (data != nullptr && pixelStride > 0 && lineStride != 0); +} + +Image::BitmapData::BitmapData (const Image& im, const int x, const int y, const int w, const int h) + : width (w), height (h) +{ + // The BitmapData class must be given a valid image, and a valid rectangle within it! + jassert (im.image != nullptr); + jassert (x >= 0 && y >= 0 && w > 0 && h > 0 && x + w <= im.getWidth() && y + h <= im.getHeight()); + + im.image->initialiseBitmapData (*this, x, y, readOnly); + jassert (data != nullptr && pixelStride > 0 && lineStride != 0); +} + +Image::BitmapData::BitmapData (const Image& im, BitmapData::ReadWriteMode mode) + : width (im.getWidth()), + height (im.getHeight()) +{ + // The BitmapData class must be given a valid image! + jassert (im.image != nullptr); + + im.image->initialiseBitmapData (*this, 0, 0, mode); + jassert (data != nullptr && pixelStride > 0 && lineStride != 0); +} + +Image::BitmapData::~BitmapData() +{ +} + +Colour Image::BitmapData::getPixelColour (const int x, const int y) const noexcept +{ + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); + + const uint8* const pixel = getPixelPointer (x, y); + + switch (pixelFormat) + { + case Image::ARGB: return Colour (((const PixelARGB*) pixel)->getUnpremultipliedARGB()); + case Image::RGB: return Colour (((const PixelRGB*) pixel)->getUnpremultipliedARGB()); + case Image::SingleChannel: return Colour (((const PixelAlpha*) pixel)->getUnpremultipliedARGB()); + default: jassertfalse; break; + } + + return Colour(); +} + +void Image::BitmapData::setPixelColour (const int x, const int y, Colour colour) const noexcept +{ + jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height)); + + uint8* const pixel = getPixelPointer (x, y); + const PixelARGB col (colour.getPixelARGB()); + + switch (pixelFormat) + { + case Image::ARGB: ((PixelARGB*) pixel)->set (col); break; + case Image::RGB: ((PixelRGB*) pixel)->set (col); break; + case Image::SingleChannel: ((PixelAlpha*) pixel)->set (col); break; + default: jassertfalse; break; + } +} + +//============================================================================== +void Image::clear (const Rectangle& area, Colour colourToClearTo) +{ + const ScopedPointer g (image->createLowLevelContext()); + g->setFill (colourToClearTo); + g->fillRect (area, true); +} + +//============================================================================== +Colour Image::getPixelAt (const int x, const int y) const +{ + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) + { + const BitmapData srcData (*this, x, y, 1, 1); + return srcData.getPixelColour (0, 0); + } + + return Colour(); +} + +void Image::setPixelAt (const int x, const int y, Colour colour) +{ + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())) + { + const BitmapData destData (*this, x, y, 1, 1, BitmapData::writeOnly); + destData.setPixelColour (0, 0, colour); + } +} + +void Image::multiplyAlphaAt (const int x, const int y, const float multiplier) +{ + if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight()) + && hasAlphaChannel()) + { + const BitmapData destData (*this, x, y, 1, 1, BitmapData::readWrite); + + if (isARGB()) + ((PixelARGB*) destData.data)->multiplyAlpha (multiplier); + else + *(destData.data) = (uint8) (*(destData.data) * multiplier); + } +} + +template +struct PixelIterator +{ + template + static void iterate (const Image::BitmapData& data, const PixelOperation& pixelOp) + { + for (int y = 0; y < data.height; ++y) + { + uint8* p = data.getLinePointer (y); + + for (int x = 0; x < data.width; ++x) + { + pixelOp (*(PixelType*) p); + p += data.pixelStride; + } + } + } +}; + +template +static void performPixelOp (const Image::BitmapData& data, const PixelOperation& pixelOp) +{ + switch (data.pixelFormat) + { + case Image::ARGB: PixelIterator ::iterate (data, pixelOp); break; + case Image::RGB: PixelIterator ::iterate (data, pixelOp); break; + case Image::SingleChannel: PixelIterator::iterate (data, pixelOp); break; + default: jassertfalse; break; + } +} + +struct AlphaMultiplyOp +{ + AlphaMultiplyOp (float alpha_) noexcept : alpha (alpha_) {} + + const float alpha; + + template + void operator() (PixelType& pixel) const + { + pixel.multiplyAlpha (alpha); + } + + JUCE_DECLARE_NON_COPYABLE (AlphaMultiplyOp) +}; + +void Image::multiplyAllAlphas (const float amountToMultiplyBy) +{ + jassert (hasAlphaChannel()); + + const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite); + performPixelOp (destData, AlphaMultiplyOp (amountToMultiplyBy)); +} + +struct DesaturateOp +{ + template + void operator() (PixelType& pixel) const + { + pixel.desaturate(); + } +}; + +void Image::desaturate() +{ + if (isARGB() || isRGB()) + { + const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite); + performPixelOp (destData, DesaturateOp()); + } +} + +void Image::createSolidAreaMask (RectangleList& result, const float alphaThreshold) const +{ + if (hasAlphaChannel()) + { + const uint8 threshold = (uint8) jlimit (0, 255, roundToInt (alphaThreshold * 255.0f)); + SparseSet pixelsOnRow; + + const BitmapData srcData (*this, 0, 0, getWidth(), getHeight()); + + for (int y = 0; y < srcData.height; ++y) + { + pixelsOnRow.clear(); + const uint8* lineData = srcData.getLinePointer (y); + + if (isARGB()) + { + for (int x = 0; x < srcData.width; ++x) + { + if (((const PixelARGB*) lineData)->getAlpha() >= threshold) + pixelsOnRow.addRange (Range (x, x + 1)); + + lineData += srcData.pixelStride; + } + } + else + { + for (int x = 0; x < srcData.width; ++x) + { + if (*lineData >= threshold) + pixelsOnRow.addRange (Range (x, x + 1)); + + lineData += srcData.pixelStride; + } + } + + for (int i = 0; i < pixelsOnRow.getNumRanges(); ++i) + { + const Range range (pixelsOnRow.getRange (i)); + result.add (Rectangle (range.getStart(), y, range.getLength(), 1)); + } + + result.consolidate(); + } + } + else + { + result.add (0, 0, getWidth(), getHeight()); + } +} + +void Image::moveImageSection (int dx, int dy, + int sx, int sy, + int w, int h) +{ + if (dx < 0) + { + w += dx; + sx -= dx; + dx = 0; + } + + if (dy < 0) + { + h += dy; + sy -= dy; + dy = 0; + } + + if (sx < 0) + { + w += sx; + dx -= sx; + sx = 0; + } + + if (sy < 0) + { + h += sy; + dy -= sy; + sy = 0; + } + + const int minX = jmin (dx, sx); + const int minY = jmin (dy, sy); + + w = jmin (w, getWidth() - jmax (sx, dx)); + h = jmin (h, getHeight() - jmax (sy, dy)); + + if (w > 0 && h > 0) + { + const int maxX = jmax (dx, sx) + w; + const int maxY = jmax (dy, sy) + h; + + const BitmapData destData (*this, minX, minY, maxX - minX, maxY - minY, BitmapData::readWrite); + + uint8* dst = destData.getPixelPointer (dx - minX, dy - minY); + const uint8* src = destData.getPixelPointer (sx - minX, sy - minY); + + const size_t lineSize = (size_t) (destData.pixelStride * w); + + if (dy > sy) + { + while (--h >= 0) + { + const int offset = h * destData.lineStride; + memmove (dst + offset, src + offset, lineSize); + } + } + else if (dst != src) + { + while (--h >= 0) + { + memmove (dst, src, lineSize); + dst += destData.lineStride; + src += destData.lineStride; + } + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_Image.h b/JuceLibraryCode/modules/juce_graphics/images/juce_Image.h new file mode 100644 index 0000000..0f67b4e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_Image.h @@ -0,0 +1,533 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGE_H_INCLUDED +#define JUCE_IMAGE_H_INCLUDED + +class ImageType; +class ImagePixelData; + + +//============================================================================== +/** + Holds a fixed-size bitmap. + + The image is stored in either 24-bit RGB or 32-bit premultiplied-ARGB format. + + To draw into an image, create a Graphics object for it. + e.g. @code + + // create a transparent 500x500 image.. + Image myImage (Image::RGB, 500, 500, true); + + Graphics g (myImage); + g.setColour (Colours::red); + g.fillEllipse (20, 20, 300, 200); // draws a red ellipse in our image. + @endcode + + Other useful ways to create an image are with the ImageCache class, or the + ImageFileFormat, which provides a way to load common image files. + + @see Graphics, ImageFileFormat, ImageCache, ImageConvolutionKernel +*/ +class JUCE_API Image +{ +public: + //============================================================================== + /** + */ + enum PixelFormat + { + UnknownFormat, + RGB, /**<< each pixel is a 3-byte packed RGB colour value. For byte order, see the PixelRGB class. */ + ARGB, /**<< each pixel is a 4-byte ARGB premultiplied colour value. For byte order, see the PixelARGB class. */ + SingleChannel /**<< each pixel is a 1-byte alpha channel value. */ + }; + + //============================================================================== + /** Creates a null image. */ + Image() noexcept; + + /** Creates an image with a specified size and format. + + The image's internal type will be of the NativeImageType class - to specify a + different type, use the other constructor, which takes an ImageType to use. + + @param format the number of colour channels in the image + @param imageWidth the desired width of the image, in pixels - this value must be + greater than zero (otherwise a width of 1 will be used) + @param imageHeight the desired width of the image, in pixels - this value must be + greater than zero (otherwise a height of 1 will be used) + @param clearImage if true, the image will initially be cleared to black (if it's RGB) + or transparent black (if it's ARGB). If false, the image may contain + junk initially, so you need to make sure you overwrite it thoroughly. + */ + Image (PixelFormat format, int imageWidth, int imageHeight, bool clearImage); + + /** Creates an image with a specified size and format. + + @param format the number of colour channels in the image + @param imageWidth the desired width of the image, in pixels - this value must be + greater than zero (otherwise a width of 1 will be used) + @param imageHeight the desired width of the image, in pixels - this value must be + greater than zero (otherwise a height of 1 will be used) + @param clearImage if true, the image will initially be cleared to black (if it's RGB) + or transparent black (if it's ARGB). If false, the image may contain + junk initially, so you need to make sure you overwrite it thoroughly. + @param type the type of image - this lets you specify the internal format that will + be used to allocate and manage the image data. + */ + Image (PixelFormat format, int imageWidth, int imageHeight, bool clearImage, const ImageType& type); + + /** Creates a shared reference to another image. + + This won't create a duplicate of the image - when Image objects are copied, they simply + point to the same shared image data. To make sure that an Image object has its own unique, + unshared internal data, call duplicateIfShared(). + */ + Image (const Image&) noexcept; + + /** Makes this image refer to the same underlying image as another object. + + This won't create a duplicate of the image - when Image objects are copied, they simply + point to the same shared image data. To make sure that an Image object has its own unique, + unshared internal data, call duplicateIfShared(). + */ + Image& operator= (const Image&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + Image (Image&&) noexcept; + Image& operator= (Image&&) noexcept; + #endif + + /** Destructor. */ + ~Image(); + + /** Returns true if the two images are referring to the same internal, shared image. */ + bool operator== (const Image& other) const noexcept { return image == other.image; } + + /** Returns true if the two images are not referring to the same internal, shared image. */ + bool operator!= (const Image& other) const noexcept { return image != other.image; } + + /** Returns true if this image isn't null. + If you create an Image with the default constructor, it has no size or content, and is null + until you reassign it to an Image which contains some actual data. + The isNull() method is the opposite of isValid(). + @see isNull + */ + inline bool isValid() const noexcept { return image != nullptr; } + + /** Returns true if this image is not valid. + If you create an Image with the default constructor, it has no size or content, and is null + until you reassign it to an Image which contains some actual data. + The isNull() method is the opposite of isValid(). + @see isValid + */ + inline bool isNull() const noexcept { return image == nullptr; } + + /** A null Image object that can be used when you need to return an invalid image. + This object is the equivalient to an Image created with the default constructor. + */ + static const Image null; + + //============================================================================== + /** Returns the image's width (in pixels). */ + int getWidth() const noexcept; + + /** Returns the image's height (in pixels). */ + int getHeight() const noexcept; + + /** Returns a rectangle with the same size as this image. + The rectangle's origin is always (0, 0). + */ + Rectangle getBounds() const noexcept; + + /** Returns the image's pixel format. */ + PixelFormat getFormat() const noexcept; + + /** True if the image's format is ARGB. */ + bool isARGB() const noexcept; + + /** True if the image's format is RGB. */ + bool isRGB() const noexcept; + + /** True if the image's format is a single-channel alpha map. */ + bool isSingleChannel() const noexcept; + + /** True if the image contains an alpha-channel. */ + bool hasAlphaChannel() const noexcept; + + //============================================================================== + /** Clears a section of the image with a given colour. + + This won't do any alpha-blending - it just sets all pixels in the image to + the given colour (which may be non-opaque if the image has an alpha channel). + */ + void clear (const Rectangle& area, Colour colourToClearTo = Colour (0x00000000)); + + /** Returns a rescaled version of this image. + + A new image is returned which is a copy of this one, rescaled to the given size. + + Note that if the new size is identical to the existing image, this will just return + a reference to the original image, and won't actually create a duplicate. + */ + Image rescaled (int newWidth, int newHeight, + Graphics::ResamplingQuality quality = Graphics::mediumResamplingQuality) const; + + /** Creates a copy of this image. + Note that it's usually more efficient to use duplicateIfShared(), because it may not be necessary + to copy an image if nothing else is using it. + @see getReferenceCount + */ + Image createCopy() const; + + /** Returns a version of this image with a different image format. + + A new image is returned which has been converted to the specified format. + + Note that if the new format is no different to the current one, this will just return + a reference to the original image, and won't actually create a copy. + */ + Image convertedToFormat (PixelFormat newFormat) const; + + /** Makes sure that no other Image objects share the same underlying data as this one. + + If no other Image objects refer to the same shared data as this one, this method has no + effect. But if there are other references to the data, this will create a new copy of + the data internally. + + Call this if you want to draw onto the image, but want to make sure that this doesn't + affect any other code that may be sharing the same data. + + @see getReferenceCount + */ + void duplicateIfShared(); + + /** Returns an image which refers to a subsection of this image. + + This will not make a copy of the original - the new image will keep a reference to it, so that + if the original image is changed, the contents of the subsection will also change. Likewise if you + draw into the subimage, you'll also be drawing onto that area of the original image. Note that if + you use operator= to make the original Image object refer to something else, the subsection image + won't pick up this change, it'll remain pointing at the original. + + The area passed-in will be clipped to the bounds of this image, so this may return a smaller + image than the area you asked for, or even a null image if the area was out-of-bounds. + */ + Image getClippedImage (const Rectangle& area) const; + + //============================================================================== + /** Returns the colour of one of the pixels in the image. + + If the coordinates given are beyond the image's boundaries, this will + return Colours::transparentBlack. + + @see setPixelAt, Image::BitmapData::getPixelColour + */ + Colour getPixelAt (int x, int y) const; + + /** Sets the colour of one of the image's pixels. + + If the coordinates are beyond the image's boundaries, then nothing will happen. + + Note that this won't do any alpha-blending, it'll just replace the existing pixel + with the given one. The colour's opacity will be ignored if this image doesn't have + an alpha-channel. + + @see getPixelAt, Image::BitmapData::setPixelColour + */ + void setPixelAt (int x, int y, Colour colour); + + /** Changes the opacity of a pixel. + + This only has an effect if the image has an alpha channel and if the + given coordinates are inside the image's boundary. + + The multiplier must be in the range 0 to 1.0, and the current alpha + at the given coordinates will be multiplied by this value. + + @see setPixelAt + */ + void multiplyAlphaAt (int x, int y, float multiplier); + + /** Changes the overall opacity of the image. + + This will multiply the alpha value of each pixel in the image by the given + amount (limiting the resulting alpha values between 0 and 255). This allows + you to make an image more or less transparent. + + If the image doesn't have an alpha channel, this won't have any effect. + */ + void multiplyAllAlphas (float amountToMultiplyBy); + + /** Changes all the colours to be shades of grey, based on their current luminosity. + */ + void desaturate(); + + //============================================================================== + /** Retrieves a section of an image as raw pixel data, so it can be read or written to. + + You should only use this class as a last resort - messing about with the internals of + an image is only recommended for people who really know what they're doing! + + A BitmapData object should be used as a temporary, stack-based object. Don't keep one + hanging around while the image is being used elsewhere. + + Depending on the way the image class is implemented, this may create a temporary buffer + which is copied back to the image when the object is deleted, or it may just get a pointer + directly into the image's raw data. + + You can use the stride and data values in this class directly, but don't alter them! + The actual format of the pixel data depends on the image's format - see Image::getFormat(), + and the PixelRGB, PixelARGB and PixelAlpha classes for more info. + */ + class JUCE_API BitmapData + { + public: + enum ReadWriteMode + { + readOnly, + writeOnly, + readWrite + }; + + BitmapData (Image& image, int x, int y, int w, int h, ReadWriteMode mode); + BitmapData (const Image& image, int x, int y, int w, int h); + BitmapData (const Image& image, ReadWriteMode mode); + ~BitmapData(); + + /** Returns a pointer to the start of a line in the image. + The coordinate you provide here isn't checked, so it's the caller's responsibility to make + sure it's not out-of-range. + */ + inline uint8* getLinePointer (int y) const noexcept { return data + y * lineStride; } + + /** Returns a pointer to a pixel in the image. + The coordinates you give here are not checked, so it's the caller's responsibility to make sure they're + not out-of-range. + */ + inline uint8* getPixelPointer (int x, int y) const noexcept { return data + y * lineStride + x * pixelStride; } + + /** Returns the colour of a given pixel. + For performance reasons, this won't do any bounds-checking on the coordinates, so it's the caller's + repsonsibility to make sure they're within the image's size. + */ + Colour getPixelColour (int x, int y) const noexcept; + + /** Sets the colour of a given pixel. + For performance reasons, this won't do any bounds-checking on the coordinates, so it's the caller's + repsonsibility to make sure they're within the image's size. + */ + void setPixelColour (int x, int y, Colour colour) const noexcept; + + /** Returns the size of the bitmap. */ + Rectangle getBounds() const noexcept { return Rectangle (width, height); } + + uint8* data; /**< The raw pixel data, packed according to the image's pixel format. */ + PixelFormat pixelFormat; /**< The format of the data. */ + int lineStride; /**< The number of bytes between each line. */ + int pixelStride; /**< The number of bytes between each pixel. */ + int width, height; + + //============================================================================== + /** Used internally by custom image types to manage pixel data lifetime. */ + class BitmapDataReleaser + { + protected: + BitmapDataReleaser() {} + public: + virtual ~BitmapDataReleaser() {} + }; + + ScopedPointer dataReleaser; + + private: + JUCE_DECLARE_NON_COPYABLE (BitmapData) + }; + + //============================================================================== + /** Copies a section of the image to somewhere else within itself. */ + void moveImageSection (int destX, int destY, + int sourceX, int sourceY, + int width, int height); + + /** Creates a RectangleList containing rectangles for all non-transparent pixels + of the image. + + @param result the list that will have the area added to it + @param alphaThreshold for a semi-transparent image, any pixels whose alpha is + above this level will be considered opaque + */ + void createSolidAreaMask (RectangleList& result, float alphaThreshold) const; + + //============================================================================== + /** Returns a NamedValueSet that is attached to the image and which can be used for + associating custom values with it. + + If this is a null image, this will return a null pointer. + */ + NamedValueSet* getProperties() const; + + //============================================================================== + /** Creates a context suitable for drawing onto this image. + Don't call this method directly! It's used internally by the Graphics class. + */ + LowLevelGraphicsContext* createLowLevelContext() const; + + /** Returns the number of Image objects which are currently referring to the same internal + shared image data. + + @see duplicateIfShared + */ + int getReferenceCount() const noexcept; + + //============================================================================== + /** @internal */ + ImagePixelData* getPixelData() const noexcept { return image; } + + /** @internal */ + explicit Image (ImagePixelData*) noexcept; + +private: + //============================================================================== + ReferenceCountedObjectPtr image; + + JUCE_LEAK_DETECTOR (Image) +}; + + +//============================================================================== +/** + This is a base class for holding image data in implementation-specific ways. + + You may never need to use this class directly - it's used internally + by the Image class to store the actual image data. To access pixel data directly, + you should use Image::BitmapData rather than this class. + + ImagePixelData objects are created indirectly, by subclasses of ImageType. + @see Image, ImageType +*/ +class JUCE_API ImagePixelData : public ReferenceCountedObject +{ +public: + ImagePixelData (Image::PixelFormat, int width, int height); + ~ImagePixelData(); + + /** Creates a context that will draw into this image. */ + virtual LowLevelGraphicsContext* createLowLevelContext() = 0; + /** Creates a copy of this image. */ + virtual ImagePixelData* clone() = 0; + /** Creates an instance of the type of this image. */ + virtual ImageType* createType() const = 0; + /** Initialises a BitmapData object. */ + virtual void initialiseBitmapData (Image::BitmapData&, int x, int y, Image::BitmapData::ReadWriteMode) = 0; + + /** The pixel format of the image data. */ + const Image::PixelFormat pixelFormat; + const int width, height; + + /** User-defined settings that are attached to this image. + @see Image::getProperties(). + */ + NamedValueSet userData; + + typedef ReferenceCountedObjectPtr Ptr; + + //============================================================================== + struct Listener + { + virtual ~Listener() {} + + virtual void imageDataChanged (ImagePixelData*) = 0; + virtual void imageDataBeingDeleted (ImagePixelData*) = 0; + }; + + ListenerList listeners; + + void sendDataChangeMessage(); + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePixelData) +}; + +//============================================================================== +/** + This base class is for handlers that control a type of image manipulation format, + e.g. an in-memory bitmap, an OpenGL image, CoreGraphics image, etc. + + @see SoftwareImageType, NativeImageType, OpenGLImageType +*/ +class JUCE_API ImageType +{ +public: + ImageType(); + virtual ~ImageType(); + + /** Creates a new image of this type, and the specified parameters. */ + virtual ImagePixelData::Ptr create (Image::PixelFormat format, int width, int height, bool shouldClearImage) const = 0; + + /** Must return a unique number to identify this type. */ + virtual int getTypeID() const = 0; + + /** Returns an image which is a copy of the source image, but using this type of storage mechanism. + For example, to make sure that an image is stored in-memory, you could use: + @code myImage = SoftwareImageType().convert (myImage); @endcode + */ + virtual Image convert (const Image& source) const; +}; + +//============================================================================== +/** + An image storage type which holds the pixels in-memory as a simple block of values. + @see ImageType, NativeImageType +*/ +class JUCE_API SoftwareImageType : public ImageType +{ +public: + SoftwareImageType(); + ~SoftwareImageType(); + + ImagePixelData::Ptr create (Image::PixelFormat, int width, int height, bool clearImage) const override; + int getTypeID() const override; +}; + +//============================================================================== +/** + An image storage type which holds the pixels using whatever is the default storage + format on the current platform. + @see ImageType, SoftwareImageType +*/ +class JUCE_API NativeImageType : public ImageType +{ +public: + NativeImageType(); + ~NativeImageType(); + + ImagePixelData::Ptr create (Image::PixelFormat, int width, int height, bool clearImage) const override; + int getTypeID() const override; +}; + + +#endif // JUCE_IMAGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp new file mode 100644 index 0000000..a806e48 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp @@ -0,0 +1,176 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class ImageCache::Pimpl : private Timer, + private DeletedAtShutdown +{ +public: + Pimpl() : cacheTimeout (5000) + { + } + + ~Pimpl() + { + clearSingletonInstance(); + } + + Image getFromHashCode (const int64 hashCode) + { + const ScopedLock sl (lock); + + for (int i = images.size(); --i >= 0;) + { + const Item* const item = images.getUnchecked(i); + + if (item->hashCode == hashCode) + return item->image; + } + + return Image::null; + } + + void addImageToCache (const Image& image, const int64 hashCode) + { + if (image.isValid()) + { + if (! isTimerRunning()) + startTimer (2000); + + Item* const item = new Item(); + item->hashCode = hashCode; + item->image = image; + item->lastUseTime = Time::getApproximateMillisecondCounter(); + + const ScopedLock sl (lock); + images.add (item); + } + } + + void timerCallback() override + { + const uint32 now = Time::getApproximateMillisecondCounter(); + + const ScopedLock sl (lock); + + for (int i = images.size(); --i >= 0;) + { + Item* const item = images.getUnchecked(i); + + if (item->image.getReferenceCount() <= 1) + { + if (now > item->lastUseTime + cacheTimeout || now < item->lastUseTime - 1000) + images.remove (i); + } + else + { + item->lastUseTime = now; // multiply-referenced, so this image is still in use. + } + } + + if (images.size() == 0) + stopTimer(); + } + + void releaseUnusedImages() + { + const ScopedLock sl (lock); + + for (int i = images.size(); --i >= 0;) + if (images.getUnchecked(i)->image.getReferenceCount() <= 1) + images.remove (i); + } + + struct Item + { + Image image; + int64 hashCode; + uint32 lastUseTime; + }; + + unsigned int cacheTimeout; + + juce_DeclareSingleton_SingleThreaded_Minimal (ImageCache::Pimpl) + +private: + OwnedArray images; + CriticalSection lock; + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; + +juce_ImplementSingleton_SingleThreaded (ImageCache::Pimpl) + + +//============================================================================== +Image ImageCache::getFromHashCode (const int64 hashCode) +{ + if (Pimpl::getInstanceWithoutCreating() != nullptr) + return Pimpl::getInstanceWithoutCreating()->getFromHashCode (hashCode); + + return Image::null; +} + +void ImageCache::addImageToCache (const Image& image, const int64 hashCode) +{ + Pimpl::getInstance()->addImageToCache (image, hashCode); +} + +Image ImageCache::getFromFile (const File& file) +{ + const int64 hashCode = file.hashCode64(); + Image image (getFromHashCode (hashCode)); + + if (image.isNull()) + { + image = ImageFileFormat::loadFrom (file); + addImageToCache (image, hashCode); + } + + return image; +} + +Image ImageCache::getFromMemory (const void* imageData, const int dataSize) +{ + const int64 hashCode = (int64) (pointer_sized_int) imageData; + Image image (getFromHashCode (hashCode)); + + if (image.isNull()) + { + image = ImageFileFormat::loadFrom (imageData, (size_t) dataSize); + addImageToCache (image, hashCode); + } + + return image; +} + +void ImageCache::setCacheTimeout (const int millisecs) +{ + jassert (millisecs >= 0); + Pimpl::getInstance()->cacheTimeout = (unsigned int) millisecs; +} + +void ImageCache::releaseUnusedImages() +{ + Pimpl::getInstance()->releaseUnusedImages(); +} diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.h b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.h new file mode 100644 index 0000000..c424395 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.h @@ -0,0 +1,125 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGECACHE_H_INCLUDED +#define JUCE_IMAGECACHE_H_INCLUDED + + +//============================================================================== +/** + A global cache of images that have been loaded from files or memory. + + If you're loading an image and may need to use the image in more than one + place, this is used to allow the same image to be shared rather than loading + multiple copies into memory. + + Another advantage is that after images are released, they will be kept in + memory for a few seconds before it is actually deleted, so if you're repeatedly + loading/deleting the same image, it'll reduce the chances of having to reload it + each time. + + @see Image, ImageFileFormat +*/ +class JUCE_API ImageCache +{ +public: + //============================================================================== + /** Loads an image from a file, (or just returns the image if it's already cached). + + If the cache already contains an image that was loaded from this file, + that image will be returned. Otherwise, this method will try to load the + file, add it to the cache, and return it. + + Remember that the image returned is shared, so drawing into it might + affect other things that are using it! If you want to draw on it, first + call Image::duplicateIfShared() + + @param file the file to try to load + @returns the image, or null if it there was an error loading it + @see getFromMemory, getFromCache, ImageFileFormat::loadFrom + */ + static Image getFromFile (const File& file); + + /** Loads an image from an in-memory image file, (or just returns the image if it's already cached). + + If the cache already contains an image that was loaded from this block of memory, + that image will be returned. Otherwise, this method will try to load the + file, add it to the cache, and return it. + + Remember that the image returned is shared, so drawing into it might + affect other things that are using it! If you want to draw on it, first + call Image::duplicateIfShared() + + @param imageData the block of memory containing the image data + @param dataSize the data size in bytes + @returns the image, or an invalid image if it there was an error loading it + @see getFromMemory, getFromCache, ImageFileFormat::loadFrom + */ + static Image getFromMemory (const void* imageData, int dataSize); + + //============================================================================== + /** Checks the cache for an image with a particular hashcode. + + If there's an image in the cache with this hashcode, it will be returned, + otherwise it will return an invalid image. + + @param hashCode the hash code that was associated with the image by addImageToCache() + @see addImageToCache + */ + static Image getFromHashCode (int64 hashCode); + + /** Adds an image to the cache with a user-defined hash-code. + + The image passed-in will be referenced (not copied) by the cache, so it's probably + a good idea not to draw into it after adding it, otherwise this will affect all + instances of it that may be in use. + + @param image the image to add + @param hashCode the hash-code to associate with it + @see getFromHashCode + */ + static void addImageToCache (const Image& image, int64 hashCode); + + /** Changes the amount of time before an unused image will be removed from the cache. + By default this is about 5 seconds. + */ + static void setCacheTimeout (int millisecs); + + /** Releases any images in the cache that aren't being referenced by active + Image objects. + */ + static void releaseUnusedImages(); + +private: + //============================================================================== + class Pimpl; + friend class Pimpl; + + ImageCache(); + ~ImageCache(); + + JUCE_DECLARE_NON_COPYABLE (ImageCache) +}; + +#endif // JUCE_IMAGECACHE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.cpp b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.cpp new file mode 100644 index 0000000..78e4fb8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.cpp @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ImageConvolutionKernel::ImageConvolutionKernel (const int size_) + : values ((size_t) (size_ * size_)), + size (size_) +{ + clear(); +} + +ImageConvolutionKernel::~ImageConvolutionKernel() +{ +} + +//============================================================================== +float ImageConvolutionKernel::getKernelValue (const int x, const int y) const noexcept +{ + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) + return values [x + y * size]; + + jassertfalse; + return 0; +} + +void ImageConvolutionKernel::setKernelValue (const int x, const int y, const float value) noexcept +{ + if (isPositiveAndBelow (x, size) && isPositiveAndBelow (y, size)) + { + values [x + y * size] = value; + } + else + { + jassertfalse; + } +} + +void ImageConvolutionKernel::clear() +{ + for (int i = size * size; --i >= 0;) + values[i] = 0; +} + +void ImageConvolutionKernel::setOverallSum (const float desiredTotalSum) +{ + double currentTotal = 0.0; + + for (int i = size * size; --i >= 0;) + currentTotal += values[i]; + + rescaleAllValues ((float) (desiredTotalSum / currentTotal)); +} + +void ImageConvolutionKernel::rescaleAllValues (const float multiplier) +{ + for (int i = size * size; --i >= 0;) + values[i] *= multiplier; +} + +//============================================================================== +void ImageConvolutionKernel::createGaussianBlur (const float radius) +{ + const double radiusFactor = -1.0 / (radius * radius * 2); + const int centre = size >> 1; + + for (int y = size; --y >= 0;) + { + for (int x = size; --x >= 0;) + { + const int cx = x - centre; + const int cy = y - centre; + + values [x + y * size] = (float) exp (radiusFactor * (cx * cx + cy * cy)); + } + } + + setOverallSum (1.0f); +} + +//============================================================================== +void ImageConvolutionKernel::applyToImage (Image& destImage, + const Image& sourceImage, + const Rectangle& destinationArea) const +{ + if (sourceImage == destImage) + { + destImage.duplicateIfShared(); + } + else + { + if (sourceImage.getWidth() != destImage.getWidth() + || sourceImage.getHeight() != destImage.getHeight() + || sourceImage.getFormat() != destImage.getFormat()) + { + jassertfalse; + return; + } + } + + const Rectangle area (destinationArea.getIntersection (destImage.getBounds())); + + if (area.isEmpty()) + return; + + const int right = area.getRight(); + const int bottom = area.getBottom(); + + const Image::BitmapData destData (destImage, area.getX(), area.getY(), area.getWidth(), area.getHeight(), + Image::BitmapData::writeOnly); + uint8* line = destData.data; + + const Image::BitmapData srcData (sourceImage, Image::BitmapData::readOnly); + + if (destData.pixelStride == 4) + { + for (int y = area.getY(); y < bottom; ++y) + { + uint8* dest = line; + line += destData.lineStride; + + for (int x = area.getX(); x < right; ++x) + { + float c1 = 0; + float c2 = 0; + float c3 = 0; + float c4 = 0; + + for (int yy = 0; yy < size; ++yy) + { + const int sy = y + yy - (size >> 1); + + if (sy >= srcData.height) + break; + + if (sy >= 0) + { + int sx = x - (size >> 1); + const uint8* src = srcData.getPixelPointer (sx, sy); + + for (int xx = 0; xx < size; ++xx) + { + if (sx >= srcData.width) + break; + + if (sx >= 0) + { + const float kernelMult = values [xx + yy * size]; + c1 += kernelMult * *src++; + c2 += kernelMult * *src++; + c3 += kernelMult * *src++; + c4 += kernelMult * *src++; + } + else + { + src += 4; + } + + ++sx; + } + } + } + + *dest++ = (uint8) jmin (0xff, roundToInt (c1)); + *dest++ = (uint8) jmin (0xff, roundToInt (c2)); + *dest++ = (uint8) jmin (0xff, roundToInt (c3)); + *dest++ = (uint8) jmin (0xff, roundToInt (c4)); + } + } + } + else if (destData.pixelStride == 3) + { + for (int y = area.getY(); y < bottom; ++y) + { + uint8* dest = line; + line += destData.lineStride; + + for (int x = area.getX(); x < right; ++x) + { + float c1 = 0; + float c2 = 0; + float c3 = 0; + + for (int yy = 0; yy < size; ++yy) + { + const int sy = y + yy - (size >> 1); + + if (sy >= srcData.height) + break; + + if (sy >= 0) + { + int sx = x - (size >> 1); + const uint8* src = srcData.getPixelPointer (sx, sy); + + for (int xx = 0; xx < size; ++xx) + { + if (sx >= srcData.width) + break; + + if (sx >= 0) + { + const float kernelMult = values [xx + yy * size]; + c1 += kernelMult * *src++; + c2 += kernelMult * *src++; + c3 += kernelMult * *src++; + } + else + { + src += 3; + } + + ++sx; + } + } + } + + *dest++ = (uint8) roundToInt (c1); + *dest++ = (uint8) roundToInt (c2); + *dest++ = (uint8) roundToInt (c3); + } + } + } + else if (destData.pixelStride == 1) + { + for (int y = area.getY(); y < bottom; ++y) + { + uint8* dest = line; + line += destData.lineStride; + + for (int x = area.getX(); x < right; ++x) + { + float c1 = 0; + + for (int yy = 0; yy < size; ++yy) + { + const int sy = y + yy - (size >> 1); + + if (sy >= srcData.height) + break; + + if (sy >= 0) + { + int sx = x - (size >> 1); + const uint8* src = srcData.getPixelPointer (sx, sy); + + for (int xx = 0; xx < size; ++xx) + { + if (sx >= srcData.width) + break; + + if (sx >= 0) + { + const float kernelMult = values [xx + yy * size]; + c1 += kernelMult * *src++; + } + else + { + src += 3; + } + + ++sx; + } + } + } + + *dest++ = (uint8) roundToInt (c1); + } + } + } +} diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.h b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.h new file mode 100644 index 0000000..885dc6b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageConvolutionKernel.h @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGECONVOLUTIONKERNEL_H_INCLUDED +#define JUCE_IMAGECONVOLUTIONKERNEL_H_INCLUDED + + +//============================================================================== +/** + Represents a filter kernel to use in convoluting an image. + + @see Image::applyConvolution +*/ +class JUCE_API ImageConvolutionKernel +{ +public: + //============================================================================== + /** Creates an empty convulution kernel. + + @param size the length of each dimension of the kernel, so e.g. if the size + is 5, it will create a 5x5 kernel + */ + ImageConvolutionKernel (int size); + + /** Destructor. */ + ~ImageConvolutionKernel(); + + //============================================================================== + /** Resets all values in the kernel to zero. */ + void clear(); + + /** Returns one of the kernel values. */ + float getKernelValue (int x, int y) const noexcept; + + /** Sets the value of a specific cell in the kernel. + + The x and y parameters must be in the range 0 < x < getKernelSize(). + + @see setOverallSum + */ + void setKernelValue (int x, int y, float value) noexcept; + + /** Rescales all values in the kernel to make the total add up to a fixed value. + + This will multiply all values in the kernel by (desiredTotalSum / currentTotalSum). + */ + void setOverallSum (float desiredTotalSum); + + /** Multiplies all values in the kernel by a value. */ + void rescaleAllValues (float multiplier); + + /** Intialises the kernel for a gaussian blur. + + @param blurRadius this may be larger or smaller than the kernel's actual + size but this will obviously be wasteful or clip at the + edges. Ideally the kernel should be just larger than + (blurRadius * 2). + */ + void createGaussianBlur (float blurRadius); + + //============================================================================== + /** Returns the size of the kernel. + + E.g. if it's a 3x3 kernel, this returns 3. + */ + int getKernelSize() const { return size; } + + //============================================================================== + /** Applies the kernel to an image. + + @param destImage the image that will receive the resultant convoluted pixels. + @param sourceImage the source image to read from - this can be the same image as + the destination, but if different, it must be exactly the same + size and format. + @param destinationArea the region of the image to apply the filter to + */ + void applyToImage (Image& destImage, + const Image& sourceImage, + const Rectangle& destinationArea) const; + +private: + //============================================================================== + HeapBlock values; + const int size; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageConvolutionKernel) +}; + + +#endif // JUCE_IMAGECONVOLUTIONKERNEL_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.cpp b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.cpp new file mode 100644 index 0000000..a1cfd6a --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.cpp @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct DefaultImageFormats +{ + static ImageFileFormat** get() + { + static DefaultImageFormats formats; + return formats.formats; + } + +private: + DefaultImageFormats() noexcept + { + formats[0] = &png; + formats[1] = &jpg; + formats[2] = &gif; + formats[3] = nullptr; + } + + PNGImageFormat png; + JPEGImageFormat jpg; + GIFImageFormat gif; + + ImageFileFormat* formats[4]; +}; + +ImageFileFormat* ImageFileFormat::findImageFormatForStream (InputStream& input) +{ + const int64 streamPos = input.getPosition(); + + for (ImageFileFormat** i = DefaultImageFormats::get(); *i != nullptr; ++i) + { + const bool found = (*i)->canUnderstand (input); + input.setPosition (streamPos); + + if (found) + return *i; + } + + return nullptr; +} + +ImageFileFormat* ImageFileFormat::findImageFormatForFileExtension (const File& file) +{ + for (ImageFileFormat** i = DefaultImageFormats::get(); *i != nullptr; ++i) + if ((*i)->usesFileExtension (file)) + return *i; + + return nullptr; +} + +//============================================================================== +Image ImageFileFormat::loadFrom (InputStream& input) +{ + ImageFileFormat* const format = findImageFormatForStream (input); + + if (format != nullptr) + return format->decodeImage (input); + + return Image::null; +} + +Image ImageFileFormat::loadFrom (const File& file) +{ + FileInputStream stream (file); + + if (stream.openedOk()) + { + BufferedInputStream b (stream, 8192); + return loadFrom (b); + } + + return Image::null; +} + +Image ImageFileFormat::loadFrom (const void* rawData, const size_t numBytes) +{ + if (rawData != nullptr && numBytes > 4) + { + MemoryInputStream stream (rawData, numBytes, false); + return loadFrom (stream); + } + + return Image::null; +} diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.h b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.h new file mode 100644 index 0000000..76a13d2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageFileFormat.h @@ -0,0 +1,216 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGEFILEFORMAT_H_INCLUDED +#define JUCE_IMAGEFILEFORMAT_H_INCLUDED + + +//============================================================================== +/** + Base-class for codecs that can read and write image file formats such + as PNG, JPEG, etc. + + This class also contains static methods to make it easy to load images + from files, streams or from memory. + + @see Image, ImageCache +*/ +class JUCE_API ImageFileFormat +{ +protected: + //============================================================================== + /** Creates an ImageFormat. */ + ImageFileFormat() {} + +public: + /** Destructor. */ + virtual ~ImageFileFormat() {} + + //============================================================================== + /** Returns a description of this file format. + + E.g. "JPEG", "PNG" + */ + virtual String getFormatName() = 0; + + /** Returns true if the given stream seems to contain data that this format understands. + + The format class should only read the first few bytes of the stream and sniff + for header bytes that it understands. + + Note that this will advance the stream and leave it in a new position, so if you're + planning on re-using it, you may want to rewind it after calling this method. + + @see decodeImage + */ + virtual bool canUnderstand (InputStream& input) = 0; + + /** Returns true if this format uses the file extension of the given file. */ + virtual bool usesFileExtension (const File& possibleFile) = 0; + + /** Tries to decode and return an image from the given stream. + + This will be called for an image format after calling its canUnderStand() method + to see if it can handle the stream. + + @param input the stream to read the data from. The stream will be positioned + at the start of the image data (but this may not necessarily + be position 0) + @returns the image that was decoded, or an invalid image if it fails. + @see loadFrom + */ + virtual Image decodeImage (InputStream& input) = 0; + + //============================================================================== + /** Attempts to write an image to a stream. + + To specify extra information like encoding quality, there will be appropriate parameters + in the subclasses of the specific file types. + + @returns true if it nothing went wrong. + */ + virtual bool writeImageToStream (const Image& sourceImage, + OutputStream& destStream) = 0; + + //============================================================================== + /** Tries the built-in formats to see if it can find one to read this stream. + There are currently built-in decoders for PNG, JPEG and GIF formats. + The object that is returned should not be deleted by the caller. + @see canUnderstand, decodeImage, loadFrom + */ + static ImageFileFormat* findImageFormatForStream (InputStream& input); + + /** Looks for a format that can handle the given file extension. + There are currently built-in formats for PNG, JPEG and GIF formats. + The object that is returned should not be deleted by the caller. + */ + static ImageFileFormat* findImageFormatForFileExtension (const File& file); + + //============================================================================== + /** Tries to load an image from a stream. + + This will use the findImageFormatForStream() method to locate a suitable + codec, and use that to load the image. + + @returns the image that was decoded, or an invalid image if it fails. + */ + static Image loadFrom (InputStream& input); + + /** Tries to load an image from a file. + + This will use the findImageFormatForStream() method to locate a suitable + codec, and use that to load the image. + + @returns the image that was decoded, or an invalid image if it fails. + */ + static Image loadFrom (const File& file); + + /** Tries to load an image from a block of raw image data. + + This will use the findImageFormatForStream() method to locate a suitable + codec, and use that to load the image. + + @returns the image that was decoded, or an invalid image if it fails. + */ + static Image loadFrom (const void* rawData, + size_t numBytesOfData); +}; + +//============================================================================== +/** + A subclass of ImageFileFormat for reading and writing PNG files. + + @see ImageFileFormat, JPEGImageFormat +*/ +class JUCE_API PNGImageFormat : public ImageFileFormat +{ +public: + //============================================================================== + PNGImageFormat(); + ~PNGImageFormat(); + + //============================================================================== + String getFormatName() override; + bool usesFileExtension (const File&) override; + bool canUnderstand (InputStream&) override; + Image decodeImage (InputStream&) override; + bool writeImageToStream (const Image&, OutputStream&) override; +}; + + +//============================================================================== +/** + A subclass of ImageFileFormat for reading and writing JPEG files. + + @see ImageFileFormat, PNGImageFormat +*/ +class JUCE_API JPEGImageFormat : public ImageFileFormat +{ +public: + //============================================================================== + JPEGImageFormat(); + ~JPEGImageFormat(); + + //============================================================================== + /** Specifies the quality to be used when writing a JPEG file. + + @param newQuality a value 0 to 1.0, where 0 is low quality, 1.0 is best, or + any negative value is "default" quality + */ + void setQuality (float newQuality); + + //============================================================================== + String getFormatName() override; + bool usesFileExtension (const File&) override; + bool canUnderstand (InputStream&) override; + Image decodeImage (InputStream&) override; + bool writeImageToStream (const Image&, OutputStream&) override; + +private: + float quality; +}; + +//============================================================================== +/** + A subclass of ImageFileFormat for reading GIF files. + + @see ImageFileFormat, PNGImageFormat, JPEGImageFormat +*/ +class JUCE_API GIFImageFormat : public ImageFileFormat +{ +public: + //============================================================================== + GIFImageFormat(); + ~GIFImageFormat(); + + //============================================================================== + String getFormatName() override; + bool usesFileExtension (const File&) override; + bool canUnderstand (InputStream&) override; + Image decodeImage (InputStream&) override; + bool writeImageToStream (const Image&, OutputStream&) override; +}; + + +#endif // JUCE_IMAGEFILEFORMAT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp b/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp new file mode 100644 index 0000000..627d565 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp @@ -0,0 +1,171 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if defined (JUCE_GRAPHICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +// Your project must contain an AppConfig.h file with your project-specific settings in it, +// and your header search path must make it accessible to the module's files. +#include "AppConfig.h" + +#include "../juce_core/native/juce_BasicNativeHeaders.h" +#include "juce_graphics.h" + +//============================================================================== +#if JUCE_MAC + #import + +#elif JUCE_WINDOWS + #if JUCE_MINGW && JUCE_USE_DIRECTWRITE + #warning "DirectWrite not currently implemented with mingw..." + #undef JUCE_USE_DIRECTWRITE + #endif + + #if JUCE_USE_DIRECTWRITE + /* If you hit a compile error trying to include these files, you may need to update + your version of the Windows SDK to the latest one. The DirectWrite and Direct2D + headers are in the version 7 SDKs. + */ + #include + #include + #endif + + #if JUCE_MINGW + #include + #endif + +#elif JUCE_IOS + #import + #import + + #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_3_2 + #error "JUCE no longer supports targets earlier than iOS 3.2" + #endif + +#elif JUCE_LINUX + #ifndef JUCE_USE_FREETYPE + #define JUCE_USE_FREETYPE 1 + #endif + + #if ! JUCE_USE_FREETYPE_AMALGAMATED + #include + #include FT_FREETYPE_H + #endif +#endif + +#if JUCE_USE_FREETYPE && JUCE_USE_FREETYPE_AMALGAMATED + #include "native/freetype/FreeTypeAmalgam.h" +#endif + +#undef SIZEOF + +#if (JUCE_MAC || JUCE_IOS) && USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER + #define JUCE_USING_COREIMAGE_LOADER 1 +#else + #define JUCE_USING_COREIMAGE_LOADER 0 +#endif + +//============================================================================== +namespace juce +{ + +#include "colour/juce_Colour.cpp" +#include "colour/juce_ColourGradient.cpp" +#include "colour/juce_Colours.cpp" +#include "colour/juce_FillType.cpp" +#include "geometry/juce_AffineTransform.cpp" +#include "geometry/juce_EdgeTable.cpp" +#include "geometry/juce_Path.cpp" +#include "geometry/juce_PathIterator.cpp" +#include "geometry/juce_PathStrokeType.cpp" +#include "placement/juce_RectanglePlacement.cpp" +#include "contexts/juce_GraphicsContext.cpp" +#include "contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp" +#include "contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" +#include "images/juce_Image.cpp" +#include "images/juce_ImageCache.cpp" +#include "images/juce_ImageConvolutionKernel.cpp" +#include "images/juce_ImageFileFormat.cpp" +#include "image_formats/juce_GIFLoader.cpp" +#include "image_formats/juce_JPEGLoader.cpp" +#include "image_formats/juce_PNGLoader.cpp" +#include "fonts/juce_AttributedString.cpp" +#include "fonts/juce_Typeface.cpp" +#include "fonts/juce_CustomTypeface.cpp" +#include "fonts/juce_Font.cpp" +#include "fonts/juce_GlyphArrangement.cpp" +#include "fonts/juce_TextLayout.cpp" +#include "effects/juce_DropShadowEffect.cpp" +#include "effects/juce_GlowEffect.cpp" + +#if JUCE_USE_FREETYPE + #include "native/juce_freetype_Fonts.cpp" +#endif + +//============================================================================== +#if JUCE_MAC || JUCE_IOS + #include "../juce_core/native/juce_osx_ObjCHelpers.h" + #include "native/juce_mac_CoreGraphicsHelpers.h" + #include "native/juce_mac_Fonts.mm" + #include "native/juce_mac_CoreGraphicsContext.mm" + +#elif JUCE_WINDOWS + #include "../juce_core/native/juce_win32_ComSmartPtr.h" + #include "native/juce_win32_DirectWriteTypeface.cpp" + #include "native/juce_win32_DirectWriteTypeLayout.cpp" + #include "native/juce_win32_Fonts.cpp" + #if JUCE_DIRECT2D + #include "native/juce_win32_Direct2DGraphicsContext.cpp" + #endif + +#elif JUCE_LINUX + #include "native/juce_linux_Fonts.cpp" + +#elif JUCE_ANDROID + #include "../juce_core/native/juce_android_JNIHelpers.h" + #include "native/juce_android_GraphicsContext.cpp" + #include "native/juce_android_Fonts.cpp" + +#endif +} + +//============================================================================== +#if JUCE_USE_FREETYPE && JUCE_USE_FREETYPE_AMALGAMATED + #undef PIXEL_MASK + #undef ZLIB_VERSION + #undef Z_ASCII + #undef ZEXTERN + #undef ZEXPORT + + extern "C" + { + #include "native/freetype/FreeTypeAmalgam.c" + } +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/juce_graphics.h b/JuceLibraryCode/modules/juce_graphics/juce_graphics.h new file mode 100644 index 0000000..9a5a015 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/juce_graphics.h @@ -0,0 +1,113 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_GRAPHICS_H_INCLUDED // %% +#define JUCE_GRAPHICS_H_INCLUDED + +#include "../juce_core/juce_core.h" +#include "../juce_events/juce_events.h" + +//============================================================================= +/** Config: JUCE_USE_COREIMAGE_LOADER + + On OSX, enabling this flag means that the CoreImage codecs will be used to load + PNG/JPEG/GIF files. It is enabled by default, but you may want to disable it if + you'd rather use libpng, libjpeg, etc. +*/ +#ifndef JUCE_USE_COREIMAGE_LOADER + #define JUCE_USE_COREIMAGE_LOADER 1 +#endif + +/** Config: JUCE_USE_DIRECTWRITE + + Enabling this flag means that DirectWrite will be used when available for font + management and layout. +*/ +#ifndef JUCE_USE_DIRECTWRITE + #define JUCE_USE_DIRECTWRITE 1 +#endif + +#ifndef JUCE_INCLUDE_PNGLIB_CODE + #define JUCE_INCLUDE_PNGLIB_CODE 1 +#endif + +#ifndef JUCE_INCLUDE_JPEGLIB_CODE + #define JUCE_INCLUDE_JPEGLIB_CODE 1 +#endif + +#ifndef USE_COREGRAPHICS_RENDERING + #define USE_COREGRAPHICS_RENDERING 1 +#endif + +//============================================================================= +namespace juce +{ + +class Image; +class AffineTransform; +class Path; +class Font; +class Graphics; +class FillType; +class LowLevelGraphicsContext; + +#include "geometry/juce_AffineTransform.h" +#include "geometry/juce_Point.h" +#include "geometry/juce_Line.h" +#include "geometry/juce_Rectangle.h" +#include "placement/juce_Justification.h" +#include "geometry/juce_Path.h" +#include "geometry/juce_RectangleList.h" +#include "colour/juce_PixelFormats.h" +#include "colour/juce_Colour.h" +#include "colour/juce_ColourGradient.h" +#include "colour/juce_Colours.h" +#include "geometry/juce_BorderSize.h" +#include "geometry/juce_EdgeTable.h" +#include "geometry/juce_PathIterator.h" +#include "geometry/juce_PathStrokeType.h" +#include "placement/juce_RectanglePlacement.h" +#include "images/juce_ImageCache.h" +#include "images/juce_ImageConvolutionKernel.h" +#include "images/juce_ImageFileFormat.h" +#include "fonts/juce_AttributedString.h" +#include "fonts/juce_Typeface.h" +#include "fonts/juce_Font.h" +#include "fonts/juce_GlyphArrangement.h" +#include "fonts/juce_TextLayout.h" +#include "fonts/juce_CustomTypeface.h" +#include "contexts/juce_GraphicsContext.h" +#include "contexts/juce_LowLevelGraphicsContext.h" +#include "images/juce_Image.h" +#include "colour/juce_FillType.h" +#include "native/juce_RenderingHelpers.h" +#include "contexts/juce_LowLevelGraphicsSoftwareRenderer.h" +#include "contexts/juce_LowLevelGraphicsPostScriptRenderer.h" +#include "effects/juce_ImageEffectFilter.h" +#include "effects/juce_DropShadowEffect.h" +#include "effects/juce_GlowEffect.h" + +} + +#endif // JUCE_GRAPHICS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/juce_graphics.mm b/JuceLibraryCode/modules/juce_graphics/juce_graphics.mm new file mode 100644 index 0000000..b36b0ed --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/juce_graphics.mm @@ -0,0 +1,25 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#include "juce_graphics.cpp" diff --git a/JuceLibraryCode/modules/juce_graphics/juce_module_info b/JuceLibraryCode/modules/juce_graphics/juce_module_info new file mode 100644 index 0000000..ab4d71b --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/juce_module_info @@ -0,0 +1,30 @@ +{ + "id": "juce_graphics", + "name": "JUCE graphics classes", + "version": "3.1.1", + "description": "Classes for 2D vector graphics, image loading/saving, font handling, etc.", + "website": "http://www.juce.com/juce", + "license": "GPL/Commercial", + + "dependencies": [ { "id": "juce_core", "version": "matching" }, + { "id": "juce_events", "version": "matching" } ], + + "include": "juce_graphics.h", + + "compile": [ { "file": "juce_graphics.cpp", "target": "! xcode" }, + { "file": "juce_graphics.mm", "target": "xcode" } ], + + "browse": [ "colour/*", + "contexts/*", + "images/*", + "image_formats/*", + "geometry/*", + "placement/*", + "fonts/*", + "effects/*", + "native/*" ], + + "OSXFrameworks": "Cocoa QuartzCore", + "iOSFrameworks": "CoreGraphics CoreText QuartzCore", + "LinuxLibs": "X11 Xinerama Xext freetype" +} diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h b/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h new file mode 100644 index 0000000..885cebd --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -0,0 +1,2684 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_RENDERINGHELPERS_H_INCLUDED +#define JUCE_RENDERINGHELPERS_H_INCLUDED + +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4127) // "expression is constant" warning +#endif + +namespace RenderingHelpers +{ + +//============================================================================== +/** Holds either a simple integer translation, or an affine transform. +*/ +class TranslationOrTransform +{ +public: + TranslationOrTransform (Point origin) noexcept + : offset (origin), isOnlyTranslated (true), isRotated (false) + { + } + + TranslationOrTransform (const TranslationOrTransform& other) noexcept + : complexTransform (other.complexTransform), offset (other.offset), + isOnlyTranslated (other.isOnlyTranslated), isRotated (other.isRotated) + { + } + + AffineTransform getTransform() const noexcept + { + return isOnlyTranslated ? AffineTransform::translation (offset) + : complexTransform; + } + + AffineTransform getTransformWith (const AffineTransform& userTransform) const noexcept + { + return isOnlyTranslated ? userTransform.translated (offset) + : userTransform.followedBy (complexTransform); + } + + void setOrigin (Point delta) noexcept + { + if (isOnlyTranslated) + offset += delta; + else + complexTransform = AffineTransform::translation (delta) + .followedBy (complexTransform); + } + + void addTransform (const AffineTransform& t) noexcept + { + if (isOnlyTranslated && t.isOnlyTranslation()) + { + const int tx = (int) (t.getTranslationX() * 256.0f); + const int ty = (int) (t.getTranslationY() * 256.0f); + + if (((tx | ty) & 0xf8) == 0) + { + offset += Point (tx >> 8, ty >> 8); + return; + } + } + + complexTransform = getTransformWith (t); + isOnlyTranslated = false; + isRotated = (complexTransform.mat01 != 0 || complexTransform.mat10 != 0 + || complexTransform.mat00 < 0 || complexTransform.mat11 < 0); + } + + float getPhysicalPixelScaleFactor() const noexcept + { + return isOnlyTranslated ? 1.0f : std::abs (complexTransform.getScaleFactor()); + } + + void moveOriginInDeviceSpace (Point delta) noexcept + { + if (isOnlyTranslated) + offset += delta; + else + complexTransform = complexTransform.translated (delta); + } + + Rectangle translated (const Rectangle& r) const noexcept + { + jassert (isOnlyTranslated); + return r + offset; + } + + Rectangle translated (const Rectangle& r) const noexcept + { + jassert (isOnlyTranslated); + return r + offset.toFloat(); + } + + template + RectangleOrPoint transformed (const RectangleOrPoint& r) const noexcept + { + jassert (! isOnlyTranslated); + return r.transformedBy (complexTransform); + } + + template + Rectangle deviceSpaceToUserSpace (const Rectangle& r) const noexcept + { + return isOnlyTranslated ? r - offset + : r.transformedBy (complexTransform.inverted()); + } + + AffineTransform complexTransform; + Point offset; + bool isOnlyTranslated, isRotated; +}; + +//============================================================================== +/** Holds a cache of recently-used glyph objects of some type. */ +template +class GlyphCache : private DeletedAtShutdown +{ +public: + GlyphCache() + { + reset(); + } + + ~GlyphCache() + { + getSingletonPointer() = nullptr; + } + + static GlyphCache& getInstance() + { + GlyphCache*& g = getSingletonPointer(); + + if (g == nullptr) + g = new GlyphCache(); + + return *g; + } + + //============================================================================== + void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, Point pos) + { + if (ReferenceCountedObjectPtr glyph = findOrCreateGlyph (font, glyphNumber)) + { + glyph->lastAccessCount = ++accessCounter; + glyph->draw (target, pos); + } + } + + void reset() + { + const ScopedLock sl (lock); + glyphs.clear(); + addNewGlyphSlots (120); + hits.set (0); + misses.set (0); + } + +private: + friend struct ContainerDeletePolicy; + ReferenceCountedArray glyphs; + Atomic accessCounter, hits, misses; + CriticalSection lock; + + ReferenceCountedObjectPtr findOrCreateGlyph (const Font& font, int glyphNumber) + { + const ScopedLock sl (lock); + + if (CachedGlyphType* g = findExistingGlyph (font, glyphNumber)) + { + ++hits; + return g; + } + + ++misses; + CachedGlyphType* g = getGlyphForReuse(); + jassert (g != nullptr); + g->generate (font, glyphNumber); + return g; + } + + CachedGlyphType* findExistingGlyph (const Font& font, int glyphNumber) const + { + for (int i = 0; i < glyphs.size(); ++i) + { + CachedGlyphType* const g = glyphs.getUnchecked (i); + + if (g->glyph == glyphNumber && g->font == font) + return g; + } + + return nullptr; + } + + CachedGlyphType* getGlyphForReuse() + { + if (hits.value + misses.value > glyphs.size() * 16) + { + if (misses.value * 2 > hits.value) + addNewGlyphSlots (32); + + hits.set (0); + misses.set (0); + } + + if (CachedGlyphType* g = findLeastRecentlyUsedGlyph()) + return g; + + addNewGlyphSlots (32); + return glyphs.getLast(); + } + + void addNewGlyphSlots (int num) + { + glyphs.ensureStorageAllocated (glyphs.size() + num); + + while (--num >= 0) + glyphs.add (new CachedGlyphType()); + } + + CachedGlyphType* findLeastRecentlyUsedGlyph() const noexcept + { + CachedGlyphType* oldest = nullptr; + int oldestCounter = std::numeric_limits::max(); + + for (int i = glyphs.size() - 1; --i >= 0;) + { + CachedGlyphType* const glyph = glyphs.getUnchecked(i); + + if (glyph->lastAccessCount <= oldestCounter + && glyph->getReferenceCount() == 1) + { + oldestCounter = glyph->lastAccessCount; + oldest = glyph; + } + } + + return oldest; + } + + static GlyphCache*& getSingletonPointer() noexcept + { + static GlyphCache* g = nullptr; + return g; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphCache) +}; + +//============================================================================== +/** Caches a glyph as an edge-table. */ +template +class CachedGlyphEdgeTable : public ReferenceCountedObject +{ +public: + CachedGlyphEdgeTable() : glyph (0), lastAccessCount (0) {} + + void draw (RendererType& state, Point pos) const + { + if (snapToIntegerCoordinate) + pos.x = std::floor (pos.x + 0.5f); + + if (edgeTable != nullptr) + state.fillEdgeTable (*edgeTable, pos.x, roundToInt (pos.y)); + } + + void generate (const Font& newFont, const int glyphNumber) + { + font = newFont; + Typeface* const typeface = newFont.getTypeface(); + snapToIntegerCoordinate = typeface->isHinted(); + glyph = glyphNumber; + + const float fontHeight = font.getHeight(); + edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, + AffineTransform::scale (fontHeight * font.getHorizontalScale(), + fontHeight), fontHeight); + } + + Font font; + int glyph, lastAccessCount; + bool snapToIntegerCoordinate; + +private: + ScopedPointer edgeTable; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable) +}; + +//============================================================================== +/** Calculates the alpha values and positions for rendering the edges of a + non-pixel-aligned rectangle. +*/ +struct FloatRectangleRasterisingInfo +{ + FloatRectangleRasterisingInfo (const Rectangle& area) + : left (roundToInt (256.0f * area.getX())), + top (roundToInt (256.0f * area.getY())), + right (roundToInt (256.0f * area.getRight())), + bottom (roundToInt (256.0f * area.getBottom())) + { + if ((top >> 8) == (bottom >> 8)) + { + topAlpha = bottom - top; + bottomAlpha = 0; + totalTop = top >> 8; + totalBottom = bottom = top = totalTop + 1; + } + else + { + if ((top & 255) == 0) + { + topAlpha = 0; + top = totalTop = (top >> 8); + } + else + { + topAlpha = 255 - (top & 255); + totalTop = (top >> 8); + top = totalTop + 1; + } + + bottomAlpha = bottom & 255; + bottom >>= 8; + totalBottom = bottom + (bottomAlpha != 0 ? 1 : 0); + } + + if ((left >> 8) == (right >> 8)) + { + leftAlpha = right - left; + rightAlpha = 0; + totalLeft = (left >> 8); + totalRight = right = left = totalLeft + 1; + } + else + { + if ((left & 255) == 0) + { + leftAlpha = 0; + left = totalLeft = (left >> 8); + } + else + { + leftAlpha = 255 - (left & 255); + totalLeft = (left >> 8); + left = totalLeft + 1; + } + + rightAlpha = right & 255; + right >>= 8; + totalRight = right + (rightAlpha != 0 ? 1 : 0); + } + } + + template + void iterate (Callback& callback) const + { + if (topAlpha != 0) callback (totalLeft, totalTop, totalRight - totalLeft, 1, topAlpha); + if (bottomAlpha != 0) callback (totalLeft, bottom, totalRight - totalLeft, 1, bottomAlpha); + if (leftAlpha != 0) callback (totalLeft, totalTop, 1, totalBottom - totalTop, leftAlpha); + if (rightAlpha != 0) callback (right, totalTop, 1, totalBottom - totalTop, rightAlpha); + + callback (left, top, right - left, bottom - top, 255); + } + + inline bool isOnePixelWide() const noexcept { return right - left == 1 && leftAlpha + rightAlpha == 0; } + + inline int getTopLeftCornerAlpha() const noexcept { return (topAlpha * leftAlpha) >> 8; } + inline int getTopRightCornerAlpha() const noexcept { return (topAlpha * rightAlpha) >> 8; } + inline int getBottomLeftCornerAlpha() const noexcept { return (bottomAlpha * leftAlpha) >> 8; } + inline int getBottomRightCornerAlpha() const noexcept { return (bottomAlpha * rightAlpha) >> 8; } + + //============================================================================== + int left, top, right, bottom; // bounds of the solid central area, excluding anti-aliased edges + int totalTop, totalLeft, totalBottom, totalRight; // bounds of the total area, including edges + int topAlpha, leftAlpha, bottomAlpha, rightAlpha; // alpha of each anti-aliased edge +}; + +//============================================================================== +/** Contains classes for calculating the colour of pixels within various types of gradient. */ +namespace GradientPixelIterators +{ + /** Iterates the colour of pixels in a linear gradient */ + class Linear + { + public: + Linear (const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const colours, const int numColours) + : lookupTable (colours), + numEntries (numColours) + { + jassert (numColours >= 0); + Point p1 (gradient.point1); + Point p2 (gradient.point2); + + if (! transform.isIdentity()) + { + const Line l (p2, p1); + Point p3 = l.getPointAlongLine (0.0f, 100.0f); + + p1.applyTransform (transform); + p2.applyTransform (transform); + p3.applyTransform (transform); + + p2 = Line (p2, p3).findNearestPointTo (p1); + } + + vertical = std::abs (p1.x - p2.x) < 0.001f; + horizontal = std::abs (p1.y - p2.y) < 0.001f; + + if (vertical) + { + scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.y - p1.y)); + start = roundToInt (p1.y * scale); + } + else if (horizontal) + { + scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.x - p1.x)); + start = roundToInt (p1.x * scale); + } + else + { + grad = (p2.getY() - p1.y) / (double) (p1.x - p2.x); + yTerm = p1.getY() - p1.x / grad; + scale = roundToInt ((numEntries << (int) numScaleBits) / (yTerm * grad - (p2.y * grad - p2.x))); + grad *= scale; + } + } + + forcedinline void setY (const int y) noexcept + { + if (vertical) + linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> (int) numScaleBits)]; + else if (! horizontal) + start = roundToInt ((y - yTerm) * grad); + } + + inline PixelARGB getPixel (const int x) const noexcept + { + return vertical ? linePix + : lookupTable [jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; + } + + private: + const PixelARGB* const lookupTable; + const int numEntries; + PixelARGB linePix; + int start, scale; + double grad, yTerm; + bool vertical, horizontal; + enum { numScaleBits = 12 }; + + JUCE_DECLARE_NON_COPYABLE (Linear) + }; + + //============================================================================== + /** Iterates the colour of pixels in a circular radial gradient */ + class Radial + { + public: + Radial (const ColourGradient& gradient, const AffineTransform&, + const PixelARGB* const colours, const int numColours) + : lookupTable (colours), + numEntries (numColours), + gx1 (gradient.point1.x), + gy1 (gradient.point1.y) + { + jassert (numColours >= 0); + const Point diff (gradient.point1 - gradient.point2); + maxDist = diff.x * diff.x + diff.y * diff.y; + invScale = numEntries / std::sqrt (maxDist); + jassert (roundToInt (std::sqrt (maxDist) * invScale) <= numEntries); + } + + forcedinline void setY (const int y) noexcept + { + dy = y - gy1; + dy *= dy; + } + + inline PixelARGB getPixel (const int px) const noexcept + { + double x = px - gx1; + x *= x; + x += dy; + + return lookupTable [x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; + } + + protected: + const PixelARGB* const lookupTable; + const int numEntries; + const double gx1, gy1; + double maxDist, invScale, dy; + + JUCE_DECLARE_NON_COPYABLE (Radial) + }; + + //============================================================================== + /** Iterates the colour of pixels in a skewed radial gradient */ + class TransformedRadial : public Radial + { + public: + TransformedRadial (const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const colours, const int numColours) + : Radial (gradient, transform, colours, numColours), + inverseTransform (transform.inverted()) + { + tM10 = inverseTransform.mat10; + tM00 = inverseTransform.mat00; + } + + forcedinline void setY (const int y) noexcept + { + lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; + lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; + } + + inline PixelARGB getPixel (const int px) const noexcept + { + double x = px; + const double y = tM10 * x + lineYM11; + x = tM00 * x + lineYM01; + x *= x; + x += y * y; + + if (x >= maxDist) + return lookupTable [numEntries]; + + return lookupTable [jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; + } + + private: + double tM10, tM00, lineYM01, lineYM11; + const AffineTransform inverseTransform; + + JUCE_DECLARE_NON_COPYABLE (TransformedRadial) + }; +} + +#define JUCE_PERFORM_PIXEL_OP_LOOP(op) \ +{ \ + const int destStride = destData.pixelStride; \ + do { dest->op; dest = addBytesToPointer (dest, destStride); } while (--width > 0); \ +} + +//============================================================================== +/** Contains classes for filling edge tables with various fill types. */ +namespace EdgeTableFillers +{ + /** Fills an edge-table with a solid colour. */ + template + class SolidColour + { + public: + SolidColour (const Image::BitmapData& image, const PixelARGB colour) + : destData (image), sourceColour (colour) + { + if (sizeof (PixelType) == 3 && destData.pixelStride == sizeof (PixelType)) + { + areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() + && sourceColour.getGreen() == sourceColour.getBlue(); + filler[0].set (sourceColour); + filler[1].set (sourceColour); + filler[2].set (sourceColour); + filler[3].set (sourceColour); + } + else + { + areRGBComponentsEqual = false; + } + } + + forcedinline void setEdgeTableYPos (const int y) noexcept + { + linePixels = (PixelType*) destData.getLinePointer (y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept + { + if (replaceExisting) + getPixel (x)->set (sourceColour); + else + getPixel (x)->blend (sourceColour, (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + if (replaceExisting) + getPixel (x)->set (sourceColour); + else + getPixel (x)->blend (sourceColour); + } + + forcedinline void handleEdgeTableLine (const int x, const int width, const int alphaLevel) const noexcept + { + PixelARGB p (sourceColour); + p.multiplyAlpha (alphaLevel); + + PixelType* dest = getPixel (x); + + if (replaceExisting || p.getAlpha() >= 0xff) + replaceLine (dest, p, width); + else + blendLine (dest, p, width); + } + + forcedinline void handleEdgeTableLineFull (const int x, const int width) const noexcept + { + PixelType* dest = getPixel (x); + + if (replaceExisting || sourceColour.getAlpha() >= 0xff) + replaceLine (dest, sourceColour, width); + else + blendLine (dest, sourceColour, width); + } + + private: + const Image::BitmapData& destData; + PixelType* linePixels; + PixelARGB sourceColour; + PixelRGB filler [4]; + bool areRGBComponentsEqual; + + forcedinline PixelType* getPixel (const int x) const noexcept + { + return addBytesToPointer (linePixels, x * destData.pixelStride); + } + + inline void blendLine (PixelType* dest, const PixelARGB colour, int width) const noexcept + { + JUCE_PERFORM_PIXEL_OP_LOOP (blend (colour)) + } + + forcedinline void replaceLine (PixelRGB* dest, const PixelARGB colour, int width) const noexcept + { + if (destData.pixelStride == sizeof (*dest)) + { + if (areRGBComponentsEqual) // if all the component values are the same, we can cheat.. + { + memset (dest, colour.getRed(), (size_t) width * 3); + } + else + { + if (width >> 5) + { + const int* const intFiller = reinterpret_cast (filler); + + while (width > 8 && (((pointer_sized_int) dest) & 7) != 0) + { + dest->set (colour); + ++dest; + --width; + } + + while (width > 4) + { + int* d = reinterpret_cast (dest); + *d++ = intFiller[0]; + *d++ = intFiller[1]; + *d++ = intFiller[2]; + dest = reinterpret_cast (d); + width -= 4; + } + } + + while (--width >= 0) + { + dest->set (colour); + ++dest; + } + } + } + else + { + JUCE_PERFORM_PIXEL_OP_LOOP (set (colour)) + } + } + + forcedinline void replaceLine (PixelAlpha* dest, const PixelARGB colour, int width) const noexcept + { + if (destData.pixelStride == sizeof (*dest)) + memset (dest, colour.getAlpha(), (size_t) width); + else + JUCE_PERFORM_PIXEL_OP_LOOP (setAlpha (colour.getAlpha())) + } + + forcedinline void replaceLine (PixelARGB* dest, const PixelARGB colour, int width) const noexcept + { + JUCE_PERFORM_PIXEL_OP_LOOP (set (colour)) + } + + JUCE_DECLARE_NON_COPYABLE (SolidColour) + }; + + //============================================================================== + /** Fills an edge-table with a gradient. */ + template + class Gradient : public GradientType + { + public: + Gradient (const Image::BitmapData& dest, const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const colours, const int numColours) + : GradientType (gradient, transform, colours, numColours - 1), + destData (dest) + { + } + + forcedinline void setEdgeTableYPos (const int y) noexcept + { + linePixels = (PixelType*) destData.getLinePointer (y); + GradientType::setY (y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept + { + getPixel (x)->blend (GradientType::getPixel (x), (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + getPixel (x)->blend (GradientType::getPixel (x)); + } + + void handleEdgeTableLine (int x, int width, const int alphaLevel) const noexcept + { + PixelType* dest = getPixel (x); + + if (alphaLevel < 0xff) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++), (uint32) alphaLevel)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++))) + } + + void handleEdgeTableLineFull (int x, int width) const noexcept + { + PixelType* dest = getPixel (x); + JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++))) + } + + private: + const Image::BitmapData& destData; + PixelType* linePixels; + + forcedinline PixelType* getPixel (const int x) const noexcept + { + return addBytesToPointer (linePixels, x * destData.pixelStride); + } + + JUCE_DECLARE_NON_COPYABLE (Gradient) + }; + + //============================================================================== + /** Fills an edge-table with a non-transformed image. */ + template + class ImageFill + { + public: + ImageFill (const Image::BitmapData& dest, const Image::BitmapData& src, + const int alpha, const int x, const int y) + : destData (dest), + srcData (src), + extraAlpha (alpha + 1), + xOffset (repeatPattern ? negativeAwareModulo (x, src.width) - src.width : x), + yOffset (repeatPattern ? negativeAwareModulo (y, src.height) - src.height : y) + { + } + + forcedinline void setEdgeTableYPos (int y) noexcept + { + linePixels = (DestPixelType*) destData.getLinePointer (y); + + y -= yOffset; + if (repeatPattern) + { + jassert (y >= 0); + y %= srcData.height; + } + + sourceLineStart = (SrcPixelType*) srcData.getLinePointer (y); + } + + forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const noexcept + { + alphaLevel = (alphaLevel * extraAlpha) >> 8; + + getDestPixel (x)->blend (*getSrcPixel (repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)), (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + getDestPixel (x)->blend (*getSrcPixel (repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)), (uint32) extraAlpha); + } + + void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept + { + DestPixelType* dest = getDestPixel (x); + alphaLevel = (alphaLevel * extraAlpha) >> 8; + x -= xOffset; + + if (repeatPattern) + { + if (alphaLevel < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) alphaLevel)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) + } + else + { + jassert (x >= 0 && x + width <= srcData.width); + + if (alphaLevel < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) alphaLevel)) + else + copyRow (dest, getSrcPixel (x), width); + } + } + + void handleEdgeTableLineFull (int x, int width) const noexcept + { + DestPixelType* dest = getDestPixel (x); + x -= xOffset; + + if (repeatPattern) + { + if (extraAlpha < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) extraAlpha)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) + } + else + { + jassert (x >= 0 && x + width <= srcData.width); + + if (extraAlpha < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) extraAlpha)) + else + copyRow (dest, getSrcPixel (x), width); + } + } + + void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) + { + jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); + SrcPixelType* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); + uint8* mask = (uint8*) (s + x - xOffset); + + if (sizeof (SrcPixelType) == sizeof (PixelARGB)) + mask += PixelARGB::indexA; + + et.clipLineToMask (x, y, mask, sizeof (SrcPixelType), width); + } + + private: + const Image::BitmapData& destData; + const Image::BitmapData& srcData; + const int extraAlpha, xOffset, yOffset; + DestPixelType* linePixels; + SrcPixelType* sourceLineStart; + + forcedinline DestPixelType* getDestPixel (int const x) const noexcept + { + return addBytesToPointer (linePixels, x * destData.pixelStride); + } + + forcedinline SrcPixelType const* getSrcPixel (int const x) const noexcept + { + return addBytesToPointer (sourceLineStart, x * srcData.pixelStride); + } + + forcedinline void copyRow (DestPixelType* dest, SrcPixelType const* src, int width) const noexcept + { + const int destStride = destData.pixelStride; + const int srcStride = srcData.pixelStride; + + if (destStride == srcStride + && srcData.pixelFormat == Image::RGB + && destData.pixelFormat == Image::RGB) + { + memcpy (dest, src, (size_t) (width * srcStride)); + } + else + { + do + { + dest->blend (*src); + dest = addBytesToPointer (dest, destStride); + src = addBytesToPointer (src, srcStride); + } while (--width > 0); + } + } + + JUCE_DECLARE_NON_COPYABLE (ImageFill) + }; + + //============================================================================== + /** Fills an edge-table with a transformed image. */ + template + class TransformedImageFill + { + public: + TransformedImageFill (const Image::BitmapData& dest, const Image::BitmapData& src, + const AffineTransform& transform, const int alpha, const Graphics::ResamplingQuality q) + : interpolator (transform, + q != Graphics::lowResamplingQuality ? 0.5f : 0.0f, + q != Graphics::lowResamplingQuality ? -128 : 0), + destData (dest), + srcData (src), + extraAlpha (alpha + 1), + quality (q), + maxX (src.width - 1), + maxY (src.height - 1), + scratchSize (2048) + { + scratchBuffer.malloc (scratchSize); + } + + forcedinline void setEdgeTableYPos (const int newY) noexcept + { + y = newY; + linePixels = (DestPixelType*) destData.getLinePointer (newY); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) noexcept + { + SrcPixelType p; + generate (&p, x, 1); + + getDestPixel (x)->blend (p, (uint32) (alphaLevel * extraAlpha) >> 8); + } + + forcedinline void handleEdgeTablePixelFull (const int x) noexcept + { + SrcPixelType p; + generate (&p, x, 1); + + getDestPixel (x)->blend (p, (uint32) extraAlpha); + } + + void handleEdgeTableLine (const int x, int width, int alphaLevel) noexcept + { + if (width > (int) scratchSize) + { + scratchSize = (size_t) width; + scratchBuffer.malloc (scratchSize); + } + + SrcPixelType* span = scratchBuffer; + generate (span, x, width); + + DestPixelType* dest = getDestPixel (x); + alphaLevel *= extraAlpha; + alphaLevel >>= 8; + + if (alphaLevel < 0xfe) + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*span++, (uint32) alphaLevel)) + else + JUCE_PERFORM_PIXEL_OP_LOOP (blend (*span++)) + } + + forcedinline void handleEdgeTableLineFull (const int x, int width) noexcept + { + handleEdgeTableLine (x, width, 255); + } + + void clipEdgeTableLine (EdgeTable& et, int x, int y_, int width) + { + if (width > (int) scratchSize) + { + scratchSize = (size_t) width; + scratchBuffer.malloc (scratchSize); + } + + y = y_; + generate (scratchBuffer.getData(), x, width); + + et.clipLineToMask (x, y_, + reinterpret_cast (scratchBuffer.getData()) + SrcPixelType::indexA, + sizeof (SrcPixelType), width); + } + + private: + forcedinline DestPixelType* getDestPixel (const int x) const noexcept + { + return addBytesToPointer (linePixels, x * destData.pixelStride); + } + + //============================================================================== + template + void generate (PixelType* dest, const int x, int numPixels) noexcept + { + this->interpolator.setStartOfLine ((float) x, (float) y, numPixels); + + do + { + int hiResX, hiResY; + this->interpolator.next (hiResX, hiResY); + + int loResX = hiResX >> 8; + int loResY = hiResY >> 8; + + if (repeatPattern) + { + loResX = negativeAwareModulo (loResX, srcData.width); + loResY = negativeAwareModulo (loResY, srcData.height); + } + + if (quality != Graphics::lowResamplingQuality) + { + if (isPositiveAndBelow (loResX, maxX)) + { + if (isPositiveAndBelow (loResY, maxY)) + { + // In the centre of the image.. + render4PixelAverage (dest, this->srcData.getPixelPointer (loResX, loResY), + hiResX & 255, hiResY & 255); + ++dest; + continue; + } + + if (! repeatPattern) + { + // At a top or bottom edge.. + if (loResY < 0) + render2PixelAverageX (dest, this->srcData.getPixelPointer (loResX, 0), hiResX & 255); + else + render2PixelAverageX (dest, this->srcData.getPixelPointer (loResX, maxY), hiResX & 255); + + ++dest; + continue; + } + } + else + { + if (isPositiveAndBelow (loResY, maxY) && ! repeatPattern) + { + // At a left or right hand edge.. + if (loResX < 0) + render2PixelAverageY (dest, this->srcData.getPixelPointer (0, loResY), hiResY & 255); + else + render2PixelAverageY (dest, this->srcData.getPixelPointer (maxX, loResY), hiResY & 255); + + ++dest; + continue; + } + } + } + + if (! repeatPattern) + { + if (loResX < 0) loResX = 0; + if (loResY < 0) loResY = 0; + if (loResX > maxX) loResX = maxX; + if (loResY > maxY) loResY = maxY; + } + + dest->set (*(const PixelType*) this->srcData.getPixelPointer (loResX, loResY)); + ++dest; + + } while (--numPixels > 0); + } + + //============================================================================== + void render4PixelAverage (PixelARGB* const dest, const uint8* src, const int subPixelX, const int subPixelY) noexcept + { + uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; + + uint32 weight = (uint32) ((256 - subPixelX) * (256 - subPixelY)); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + src += this->srcData.pixelStride; + + weight = (uint32) (subPixelX * (256 - subPixelY)); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + src += this->srcData.lineStride; + + weight = (uint32) (subPixelX * subPixelY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + src -= this->srcData.pixelStride; + + weight = (uint32) ((256 - subPixelX) * subPixelY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 16), + (uint8) (c[PixelARGB::indexR] >> 16), + (uint8) (c[PixelARGB::indexG] >> 16), + (uint8) (c[PixelARGB::indexB] >> 16)); + } + + void render2PixelAverageX (PixelARGB* const dest, const uint8* src, const uint32 subPixelX) noexcept + { + uint32 c[4] = { 128, 128, 128, 128 }; + + uint32 weight = 256 - subPixelX; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + src += this->srcData.pixelStride; + + weight = subPixelX; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 8), + (uint8) (c[PixelARGB::indexR] >> 8), + (uint8) (c[PixelARGB::indexG] >> 8), + (uint8) (c[PixelARGB::indexB] >> 8)); + } + + void render2PixelAverageY (PixelARGB* const dest, const uint8* src, const uint32 subPixelY) noexcept + { + uint32 c[4] = { 128, 128, 128, 128 }; + + uint32 weight = 256 - subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + src += this->srcData.lineStride; + + weight = subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + c[3] += weight * src[3]; + + dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 8), + (uint8) (c[PixelARGB::indexR] >> 8), + (uint8) (c[PixelARGB::indexG] >> 8), + (uint8) (c[PixelARGB::indexB] >> 8)); + } + + //============================================================================== + void render4PixelAverage (PixelRGB* const dest, const uint8* src, const uint32 subPixelX, const uint32 subPixelY) noexcept + { + uint32 c[3] = { 256 * 128, 256 * 128, 256 * 128 }; + + uint32 weight = (256 - subPixelX) * (256 - subPixelY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + src += this->srcData.pixelStride; + + weight = subPixelX * (256 - subPixelY); + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + src += this->srcData.lineStride; + + weight = subPixelX * subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + src -= this->srcData.pixelStride; + + weight = (256 - subPixelX) * subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 16), + (uint8) (c[PixelRGB::indexG] >> 16), + (uint8) (c[PixelRGB::indexB] >> 16)); + } + + void render2PixelAverageX (PixelRGB* const dest, const uint8* src, const uint32 subPixelX) noexcept + { + uint32 c[3] = { 128, 128, 128 }; + + const uint32 weight = 256 - subPixelX; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + src += this->srcData.pixelStride; + + c[0] += subPixelX * src[0]; + c[1] += subPixelX * src[1]; + c[2] += subPixelX * src[2]; + + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 8), + (uint8) (c[PixelRGB::indexG] >> 8), + (uint8) (c[PixelRGB::indexB] >> 8)); + } + + void render2PixelAverageY (PixelRGB* const dest, const uint8* src, const uint32 subPixelY) noexcept + { + uint32 c[3] = { 128, 128, 128 }; + + const uint32 weight = 256 - subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + src += this->srcData.lineStride; + + c[0] += subPixelY * src[0]; + c[1] += subPixelY * src[1]; + c[2] += subPixelY * src[2]; + + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 8), + (uint8) (c[PixelRGB::indexG] >> 8), + (uint8) (c[PixelRGB::indexB] >> 8)); + } + + //============================================================================== + void render4PixelAverage (PixelAlpha* const dest, const uint8* src, const uint32 subPixelX, const uint32 subPixelY) noexcept + { + uint32 c = 256 * 128; + c += src[0] * ((256 - subPixelX) * (256 - subPixelY)); + src += this->srcData.pixelStride; + c += src[1] * (subPixelX * (256 - subPixelY)); + src += this->srcData.lineStride; + c += src[1] * (subPixelX * subPixelY); + src -= this->srcData.pixelStride; + + c += src[0] * ((256 - subPixelX) * subPixelY); + + *((uint8*) dest) = (uint8) (c >> 16); + } + + void render2PixelAverageX (PixelAlpha* const dest, const uint8* src, const uint32 subPixelX) noexcept + { + uint32 c = 128; + c += src[0] * (256 - subPixelX); + src += this->srcData.pixelStride; + c += src[0] * subPixelX; + *((uint8*) dest) = (uint8) (c >> 8); + } + + void render2PixelAverageY (PixelAlpha* const dest, const uint8* src, const uint32 subPixelY) noexcept + { + uint32 c = 128; + c += src[0] * (256 - subPixelY); + src += this->srcData.lineStride; + c += src[0] * subPixelY; + *((uint8*) dest) = (uint8) (c >> 8); + } + + //============================================================================== + class TransformedImageSpanInterpolator + { + public: + TransformedImageSpanInterpolator (const AffineTransform& transform, + const float offsetFloat, const int offsetInt) noexcept + : inverseTransform (transform.inverted()), + pixelOffset (offsetFloat), pixelOffsetInt (offsetInt) + {} + + void setStartOfLine (float sx, float sy, const int numPixels) noexcept + { + jassert (numPixels > 0); + + sx += pixelOffset; + sy += pixelOffset; + float x1 = sx, y1 = sy; + sx += numPixels; + inverseTransform.transformPoints (x1, y1, sx, sy); + + xBresenham.set ((int) (x1 * 256.0f), (int) (sx * 256.0f), numPixels, pixelOffsetInt); + yBresenham.set ((int) (y1 * 256.0f), (int) (sy * 256.0f), numPixels, pixelOffsetInt); + } + + void next (int& px, int& py) noexcept + { + px = xBresenham.n; xBresenham.stepToNext(); + py = yBresenham.n; yBresenham.stepToNext(); + } + + private: + class BresenhamInterpolator + { + public: + BresenhamInterpolator() noexcept {} + + void set (const int n1, const int n2, const int steps, const int offsetInt) noexcept + { + numSteps = steps; + step = (n2 - n1) / numSteps; + remainder = modulo = (n2 - n1) % numSteps; + n = n1 + offsetInt; + + if (modulo <= 0) + { + modulo += numSteps; + remainder += numSteps; + --step; + } + + modulo -= numSteps; + } + + forcedinline void stepToNext() noexcept + { + modulo += remainder; + n += step; + + if (modulo > 0) + { + modulo -= numSteps; + ++n; + } + } + + int n; + + private: + int numSteps, step, modulo, remainder; + }; + + const AffineTransform inverseTransform; + BresenhamInterpolator xBresenham, yBresenham; + const float pixelOffset; + const int pixelOffsetInt; + + JUCE_DECLARE_NON_COPYABLE (TransformedImageSpanInterpolator) + }; + + //============================================================================== + TransformedImageSpanInterpolator interpolator; + const Image::BitmapData& destData; + const Image::BitmapData& srcData; + const int extraAlpha; + const Graphics::ResamplingQuality quality; + const int maxX, maxY; + int y; + DestPixelType* linePixels; + HeapBlock scratchBuffer; + size_t scratchSize; + + JUCE_DECLARE_NON_COPYABLE (TransformedImageFill) + }; + + + //============================================================================== + template + void renderImageTransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, + const int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) + { + switch (destData.pixelFormat) + { + case Image::ARGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + } + break; + + case Image::RGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + } + break; + + default: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + break; + } + break; + } + } + + template + void renderImageUntransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) + { + switch (destData.pixelFormat) + { + case Image::ARGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + } + break; + + case Image::RGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + } + break; + + default: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + default: + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + break; + } + break; + } + } + + template + void renderSolidFill (Iterator& iter, const Image::BitmapData& destData, const PixelARGB fillColour, const bool replaceContents, DestPixelType*) + { + if (replaceContents) + { + EdgeTableFillers::SolidColour r (destData, fillColour); + iter.iterate (r); + } + else + { + EdgeTableFillers::SolidColour r (destData, fillColour); + iter.iterate (r); + } + } + + template + void renderGradient (Iterator& iter, const Image::BitmapData& destData, const ColourGradient& g, const AffineTransform& transform, + const PixelARGB* const lookupTable, const int numLookupEntries, const bool isIdentity, DestPixelType*) + { + if (g.isRadial) + { + if (isIdentity) + { + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + else + { + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + } + else + { + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + iter.iterate (renderer); + } + } +} + +//============================================================================== +template +struct ClipRegions +{ + class Base : public SingleThreadedReferenceCountedObject + { + public: + Base() {} + virtual ~Base() {} + + typedef ReferenceCountedObjectPtr Ptr; + + virtual Ptr clone() const = 0; + virtual Ptr applyClipTo (const Ptr& target) const = 0; + + virtual Ptr clipToRectangle (const Rectangle&) = 0; + virtual Ptr clipToRectangleList (const RectangleList&) = 0; + virtual Ptr excludeClipRectangle (const Rectangle&) = 0; + virtual Ptr clipToPath (const Path&, const AffineTransform&) = 0; + virtual Ptr clipToEdgeTable (const EdgeTable& et) = 0; + virtual Ptr clipToImageAlpha (const Image&, const AffineTransform&, const Graphics::ResamplingQuality) = 0; + virtual void translate (Point delta) = 0; + + virtual bool clipRegionIntersects (const Rectangle&) const = 0; + virtual Rectangle getClipBounds() const = 0; + + virtual void fillRectWithColour (SavedStateType&, const Rectangle&, const PixelARGB colour, bool replaceContents) const = 0; + virtual void fillRectWithColour (SavedStateType&, const Rectangle&, const PixelARGB colour) const = 0; + virtual void fillAllWithColour (SavedStateType&, const PixelARGB colour, bool replaceContents) const = 0; + virtual void fillAllWithGradient (SavedStateType&, ColourGradient&, const AffineTransform&, bool isIdentity) const = 0; + virtual void renderImageTransformed (SavedStateType&, const Image&, const int alpha, const AffineTransform&, Graphics::ResamplingQuality, bool tiledFill) const = 0; + virtual void renderImageUntransformed (SavedStateType&, const Image&, const int alpha, int x, int y, bool tiledFill) const = 0; + }; + + //============================================================================== + class EdgeTableRegion : public Base + { + public: + EdgeTableRegion (const EdgeTable& e) : edgeTable (e) {} + EdgeTableRegion (const Rectangle& r) : edgeTable (r) {} + EdgeTableRegion (const Rectangle& r) : edgeTable (r) {} + EdgeTableRegion (const RectangleList& r) : edgeTable (r) {} + EdgeTableRegion (const RectangleList& r) : edgeTable (r) {} + EdgeTableRegion (const Rectangle& bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} + EdgeTableRegion (const EdgeTableRegion& other) : Base(), edgeTable (other.edgeTable) {} + + typedef typename Base::Ptr Ptr; + + Ptr clone() const { return new EdgeTableRegion (*this); } + Ptr applyClipTo (const Ptr& target) const { return target->clipToEdgeTable (edgeTable); } + + Ptr clipToRectangle (const Rectangle& r) + { + edgeTable.clipToRectangle (r); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToRectangleList (const RectangleList& r) + { + RectangleList inverse (edgeTable.getMaximumBounds()); + + if (inverse.subtract (r)) + for (const Rectangle* i = inverse.begin(), * const e = inverse.end(); i != e; ++i) + edgeTable.excludeRectangle (*i); + + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr excludeClipRectangle (const Rectangle& r) + { + edgeTable.excludeRectangle (r); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToPath (const Path& p, const AffineTransform& transform) + { + EdgeTable et (edgeTable.getMaximumBounds(), p, transform); + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToEdgeTable (const EdgeTable& et) + { + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const Graphics::ResamplingQuality quality) + { + const Image::BitmapData srcData (image, Image::BitmapData::readOnly); + + if (transform.isOnlyTranslation()) + { + // If our translation doesn't involve any distortion, just use a simple blit.. + const int tx = (int) (transform.getTranslationX() * 256.0f); + const int ty = (int) (transform.getTranslationY() * 256.0f); + + if (quality == Graphics::lowResamplingQuality || ((tx | ty) & 224) == 0) + { + const int imageX = ((tx + 128) >> 8); + const int imageY = ((ty + 128) >> 8); + + if (image.getFormat() == Image::ARGB) + straightClipImage (srcData, imageX, imageY, (PixelARGB*) 0); + else + straightClipImage (srcData, imageX, imageY, (PixelAlpha*) 0); + + return edgeTable.isEmpty() ? nullptr : this; + } + } + + if (transform.isSingularity()) + return Ptr(); + + { + Path p; + p.addRectangle (0, 0, (float) srcData.width, (float) srcData.height); + EdgeTable et2 (edgeTable.getMaximumBounds(), p, transform); + edgeTable.clipToEdgeTable (et2); + } + + if (! edgeTable.isEmpty()) + { + if (image.getFormat() == Image::ARGB) + transformedClipImage (srcData, transform, quality, (PixelARGB*) 0); + else + transformedClipImage (srcData, transform, quality, (PixelAlpha*) 0); + } + + return edgeTable.isEmpty() ? nullptr : this; + } + + void translate (Point delta) + { + edgeTable.translate ((float) delta.x, delta.y); + } + + bool clipRegionIntersects (const Rectangle& r) const + { + return edgeTable.getMaximumBounds().intersects (r); + } + + Rectangle getClipBounds() const + { + return edgeTable.getMaximumBounds(); + } + + void fillRectWithColour (SavedStateType& state, const Rectangle& area, const PixelARGB colour, bool replaceContents) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds()); + const Rectangle clipped (totalClip.getIntersection (area)); + + if (! clipped.isEmpty()) + { + EdgeTableRegion et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + state.fillWithSolidColour (et.edgeTable, colour, replaceContents); + } + } + + void fillRectWithColour (SavedStateType& state, const Rectangle& area, const PixelARGB colour) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds().toFloat()); + const Rectangle clipped (totalClip.getIntersection (area)); + + if (! clipped.isEmpty()) + { + EdgeTableRegion et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + state.fillWithSolidColour (et.edgeTable, colour, false); + } + } + + void fillAllWithColour (SavedStateType& state, const PixelARGB colour, bool replaceContents) const + { + state.fillWithSolidColour (edgeTable, colour, replaceContents); + } + + void fillAllWithGradient (SavedStateType& state, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + state.fillWithGradient (edgeTable, gradient, transform, isIdentity); + } + + void renderImageTransformed (SavedStateType& state, const Image& src, const int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) const + { + state.renderImageTransformed (edgeTable, src, alpha, transform, quality, tiledFill); + } + + void renderImageUntransformed (SavedStateType& state, const Image& src, const int alpha, int x, int y, bool tiledFill) const + { + state.renderImageUntransformed (edgeTable, src, alpha, x, y, tiledFill); + } + + EdgeTable edgeTable; + + private: + template + void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const Graphics::ResamplingQuality quality, const SrcPixelType*) + { + EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); + + for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) + renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), + edgeTable.getMaximumBounds().getWidth()); + } + + template + void straightClipImage (const Image::BitmapData& srcData, int imageX, int imageY, const SrcPixelType*) + { + Rectangle r (imageX, imageY, srcData.width, srcData.height); + edgeTable.clipToRectangle (r); + + EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); + + for (int y = 0; y < r.getHeight(); ++y) + renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); + } + + EdgeTableRegion& operator= (const EdgeTableRegion&); + }; + + //============================================================================== + class RectangleListRegion : public Base + { + public: + RectangleListRegion (const Rectangle& r) : clip (r) {} + RectangleListRegion (const RectangleList& r) : clip (r) {} + RectangleListRegion (const RectangleListRegion& other) : Base(), clip (other.clip) {} + + typedef typename Base::Ptr Ptr; + + Ptr clone() const { return new RectangleListRegion (*this); } + Ptr applyClipTo (const Ptr& target) const { return target->clipToRectangleList (clip); } + + Ptr clipToRectangle (const Rectangle& r) + { + clip.clipTo (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr clipToRectangleList (const RectangleList& r) + { + clip.clipTo (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr excludeClipRectangle (const Rectangle& r) + { + clip.subtract (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr clipToPath (const Path& p, const AffineTransform& transform) { return toEdgeTable()->clipToPath (p, transform); } + Ptr clipToEdgeTable (const EdgeTable& et) { return toEdgeTable()->clipToEdgeTable (et); } + + Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const Graphics::ResamplingQuality quality) + { + return toEdgeTable()->clipToImageAlpha (image, transform, quality); + } + + void translate (Point delta) { clip.offsetAll (delta); } + bool clipRegionIntersects (const Rectangle& r) const { return clip.intersects (r); } + Rectangle getClipBounds() const { return clip.getBounds(); } + + void fillRectWithColour (SavedStateType& state, const Rectangle& area, const PixelARGB colour, bool replaceContents) const + { + SubRectangleIterator iter (clip, area); + state.fillWithSolidColour (iter, colour, replaceContents); + } + + void fillRectWithColour (SavedStateType& state, const Rectangle& area, const PixelARGB colour) const + { + SubRectangleIteratorFloat iter (clip, area); + state.fillWithSolidColour (iter, colour, false); + } + + void fillAllWithColour (SavedStateType& state, const PixelARGB colour, bool replaceContents) const + { + state.fillWithSolidColour (*this, colour, replaceContents); + } + + void fillAllWithGradient (SavedStateType& state, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + state.fillWithGradient (*this, gradient, transform, isIdentity); + } + + void renderImageTransformed (SavedStateType& state, const Image& src, const int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) const + { + state.renderImageTransformed (*this, src, alpha, transform, quality, tiledFill); + } + + void renderImageUntransformed (SavedStateType& state, const Image& src, const int alpha, int x, int y, bool tiledFill) const + { + state.renderImageUntransformed (*this, src, alpha, x, y, tiledFill); + } + + RectangleList clip; + + //============================================================================== + template + void iterate (Renderer& r) const noexcept + { + for (const Rectangle* i = clip.begin(), * const e = clip.end(); i != e; ++i) + { + const int x = i->getX(); + const int w = i->getWidth(); + jassert (w > 0); + const int bottom = i->getBottom(); + + for (int y = i->getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } + + private: + //============================================================================== + class SubRectangleIterator + { + public: + SubRectangleIterator (const RectangleList& clipList, const Rectangle& clipBounds) + : clip (clipList), area (clipBounds) + {} + + template + void iterate (Renderer& r) const noexcept + { + for (const Rectangle* i = clip.begin(), * const e = clip.end(); i != e; ++i) + { + const Rectangle rect (i->getIntersection (area)); + + if (! rect.isEmpty()) + { + const int x = rect.getX(); + const int w = rect.getWidth(); + const int bottom = rect.getBottom(); + + for (int y = rect.getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } + } + + private: + const RectangleList& clip; + const Rectangle area; + + JUCE_DECLARE_NON_COPYABLE (SubRectangleIterator) + }; + + //============================================================================== + class SubRectangleIteratorFloat + { + public: + SubRectangleIteratorFloat (const RectangleList& clipList, const Rectangle& clipBounds) noexcept + : clip (clipList), area (clipBounds) + { + } + + template + void iterate (Renderer& r) const noexcept + { + const RenderingHelpers::FloatRectangleRasterisingInfo f (area); + + for (const Rectangle* i = clip.begin(), * const e = clip.end(); i != e; ++i) + { + const int clipLeft = i->getX(); + const int clipRight = i->getRight(); + const int clipTop = i->getY(); + const int clipBottom = i->getBottom(); + + if (f.totalBottom > clipTop && f.totalTop < clipBottom + && f.totalRight > clipLeft && f.totalLeft < clipRight) + { + if (f.isOnePixelWide()) + { + if (f.topAlpha != 0 && f.totalTop >= clipTop) + { + r.setEdgeTableYPos (f.totalTop); + r.handleEdgeTablePixel (f.left, f.topAlpha); + } + + const int endY = jmin (f.bottom, clipBottom); + for (int y = jmax (clipTop, f.top); y < endY; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTablePixelFull (f.left); + } + + if (f.bottomAlpha != 0 && f.bottom < clipBottom) + { + r.setEdgeTableYPos (f.bottom); + r.handleEdgeTablePixel (f.left, f.bottomAlpha); + } + } + else + { + const int clippedLeft = jmax (f.left, clipLeft); + const int clippedWidth = jmin (f.right, clipRight) - clippedLeft; + const bool doLeftAlpha = f.leftAlpha != 0 && f.totalLeft >= clipLeft; + const bool doRightAlpha = f.rightAlpha != 0 && f.right < clipRight; + + if (f.topAlpha != 0 && f.totalTop >= clipTop) + { + r.setEdgeTableYPos (f.totalTop); + + if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.getTopLeftCornerAlpha()); + if (clippedWidth > 0) r.handleEdgeTableLine (clippedLeft, clippedWidth, f.topAlpha); + if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.getTopRightCornerAlpha()); + } + + const int endY = jmin (f.bottom, clipBottom); + for (int y = jmax (clipTop, f.top); y < endY; ++y) + { + r.setEdgeTableYPos (y); + + if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.leftAlpha); + if (clippedWidth > 0) r.handleEdgeTableLineFull (clippedLeft, clippedWidth); + if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.rightAlpha); + } + + if (f.bottomAlpha != 0 && f.bottom < clipBottom) + { + r.setEdgeTableYPos (f.bottom); + + if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.getBottomLeftCornerAlpha()); + if (clippedWidth > 0) r.handleEdgeTableLine (clippedLeft, clippedWidth, f.bottomAlpha); + if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.getBottomRightCornerAlpha()); + } + } + } + } + } + + private: + const RectangleList& clip; + const Rectangle& area; + + JUCE_DECLARE_NON_COPYABLE (SubRectangleIteratorFloat) + }; + + Ptr toEdgeTable() const { return new EdgeTableRegion (clip); } + + RectangleListRegion& operator= (const RectangleListRegion&); + }; +}; + +//============================================================================== +template +class SavedStateBase +{ +public: + typedef typename ClipRegions::Base BaseRegionType; + typedef typename ClipRegions::EdgeTableRegion EdgeTableRegionType; + typedef typename ClipRegions::RectangleListRegion RectangleListRegionType; + + SavedStateBase (const Rectangle& initialClip) + : clip (new RectangleListRegionType (initialClip)), transform (Point()), + interpolationQuality (Graphics::mediumResamplingQuality), transparencyLayerAlpha (1.0f) + { + } + + SavedStateBase (const RectangleList& clipList, Point origin) + : clip (new RectangleListRegionType (clipList)), transform (origin), + interpolationQuality (Graphics::mediumResamplingQuality), transparencyLayerAlpha (1.0f) + { + } + + SavedStateBase (const SavedStateBase& other) + : clip (other.clip), transform (other.transform), fillType (other.fillType), + interpolationQuality (other.interpolationQuality), + transparencyLayerAlpha (other.transparencyLayerAlpha) + { + } + + SavedStateType& getThis() noexcept { return *static_cast (this); } + + bool clipToRectangle (const Rectangle& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToRectangle (transform.translated (r)); + } + else if (! transform.isRotated) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToRectangle (transform.transformed (r)); + } + else + { + Path p; + p.addRectangle (r); + clipToPath (p, AffineTransform::identity); + } + } + + return clip != nullptr; + } + + bool clipToRectangleList (const RectangleList& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + cloneClipIfMultiplyReferenced(); + RectangleList offsetList (r); + offsetList.offsetAll (transform.offset.x, transform.offset.y); + clip = clip->clipToRectangleList (offsetList); + } + else if (! transform.isRotated) + { + cloneClipIfMultiplyReferenced(); + RectangleList scaledList; + + for (const Rectangle* i = r.begin(), * const e = r.end(); i != e; ++i) + scaledList.add (transform.transformed (*i)); + + clip = clip->clipToRectangleList (scaledList); + } + else + { + clipToPath (r.toPath(), AffineTransform::identity); + } + } + + return clip != nullptr; + } + + static Rectangle getLargestIntegerWithin (Rectangle r) + { + const int x1 = (int) std::ceil (r.getX()); + const int y1 = (int) std::ceil (r.getY()); + const int x2 = (int) std::floor (r.getRight()); + const int y2 = (int) std::floor (r.getBottom()); + + return Rectangle (x1, y1, x2 - x1, y2 - y1); + } + + bool excludeClipRectangle (const Rectangle& r) + { + if (clip != nullptr) + { + cloneClipIfMultiplyReferenced(); + + if (transform.isOnlyTranslated) + { + clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.translated (r.toFloat()))); + } + else if (! transform.isRotated) + { + clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.transformed (r.toFloat()))); + } + else + { + Path p; + p.addRectangle (r.toFloat()); + p.applyTransform (transform.complexTransform); + p.addRectangle (clip->getClipBounds().toFloat()); + p.setUsingNonZeroWinding (false); + clip = clip->clipToPath (p, AffineTransform::identity); + } + } + + return clip != nullptr; + } + + void clipToPath (const Path& p, const AffineTransform& t) + { + if (clip != nullptr) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToPath (p, transform.getTransformWith (t)); + } + } + + void clipToImageAlpha (const Image& sourceImage, const AffineTransform& t) + { + if (clip != nullptr) + { + if (sourceImage.hasAlphaChannel()) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToImageAlpha (sourceImage, transform.getTransformWith (t), interpolationQuality); + } + else + { + Path p; + p.addRectangle (sourceImage.getBounds()); + clipToPath (p, t); + } + } + } + + bool clipRegionIntersects (const Rectangle& r) const + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + return clip->clipRegionIntersects (transform.translated (r)); + + return getClipBounds().intersects (r); + } + + return false; + } + + Rectangle getClipBounds() const + { + return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()) + : Rectangle(); + } + + void setFillType (const FillType& newFill) + { + fillType = newFill; + } + + void fillTargetRect (const Rectangle& r, const bool replaceContents) + { + if (fillType.isColour()) + { + clip->fillRectWithColour (getThis(), r, fillType.colour.getPixelARGB(), replaceContents); + } + else + { + const Rectangle clipped (clip->getClipBounds().getIntersection (r)); + + if (! clipped.isEmpty()) + fillShape (new RectangleListRegionType (clipped), false); + } + } + + void fillTargetRect (const Rectangle& r) + { + if (fillType.isColour()) + { + clip->fillRectWithColour (getThis(), r, fillType.colour.getPixelARGB()); + } + else + { + const Rectangle clipped (clip->getClipBounds().toFloat().getIntersection (r)); + + if (! clipped.isEmpty()) + fillShape (new EdgeTableRegionType (clipped), false); + } + } + + template + void fillRectAsPath (const Rectangle& r) + { + Path p; + p.addRectangle (r); + fillPath (p, AffineTransform::identity); + } + + void fillRect (const Rectangle& r, const bool replaceContents) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + fillTargetRect (transform.translated (r), replaceContents); + } + else if (! transform.isRotated) + { + fillTargetRect (transform.transformed (r), replaceContents); + } + else + { + jassert (! replaceContents); // not implemented.. + fillRectAsPath (r); + } + } + } + + void fillRect (const Rectangle& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + fillTargetRect (transform.translated (r)); + else if (! transform.isRotated) + fillTargetRect (transform.transformed (r)); + else + fillRectAsPath (r); + } + } + + void fillRectList (const RectangleList& list) + { + if (clip != nullptr) + { + if (! transform.isRotated) + { + RectangleList transformed (list); + + if (transform.isOnlyTranslated) + transformed.offsetAll (transform.offset.toFloat()); + else + transformed.transformAll (transform.getTransform()); + + fillShape (new EdgeTableRegionType (transformed), false); + } + else + { + fillPath (list.toPath(), AffineTransform::identity); + } + } + } + + void fillPath (const Path& path, const AffineTransform& t) + { + if (clip != nullptr) + fillShape (new EdgeTableRegionType (clip->getClipBounds(), path, transform.getTransformWith (t)), false); + } + + void fillEdgeTable (const EdgeTable& edgeTable, const float x, const int y) + { + if (clip != nullptr) + { + EdgeTableRegionType* edgeTableClip = new EdgeTableRegionType (edgeTable); + edgeTableClip->edgeTable.translate (x, y); + + if (fillType.isColour()) + { + float brightness = fillType.colour.getBrightness() - 0.5f; + + if (brightness > 0.0f) + edgeTableClip->edgeTable.multiplyLevels (1.0f + 1.6f * brightness); + } + + fillShape (edgeTableClip, false); + } + } + + void drawLine (const Line& line) + { + Path p; + p.addLineSegment (line, 1.0f); + fillPath (p, AffineTransform::identity); + } + + void drawImage (const Image& sourceImage, const AffineTransform& trans) + { + if (clip != nullptr && ! fillType.colour.isTransparent()) + renderImage (sourceImage, trans, nullptr); + } + + static bool isOnlyTranslationAllowingError (const AffineTransform& t) + { + return (std::abs (t.mat01) < 0.002) + && (std::abs (t.mat10) < 0.002) + && (std::abs (t.mat00 - 1.0f) < 0.002) + && (std::abs (t.mat11 - 1.0f) < 0.002); + } + + void renderImage (const Image& sourceImage, const AffineTransform& trans, + const BaseRegionType* const tiledFillClipRegion) + { + const AffineTransform t (transform.getTransformWith (trans)); + + const int alpha = fillType.colour.getAlpha(); + + if (isOnlyTranslationAllowingError (t)) + { + // If our translation doesn't involve any distortion, just use a simple blit.. + int tx = (int) (t.getTranslationX() * 256.0f); + int ty = (int) (t.getTranslationY() * 256.0f); + + if (interpolationQuality == Graphics::lowResamplingQuality || ((tx | ty) & 224) == 0) + { + tx = ((tx + 128) >> 8); + ty = ((ty + 128) >> 8); + + if (tiledFillClipRegion != nullptr) + { + tiledFillClipRegion->renderImageUntransformed (getThis(), sourceImage, alpha, tx, ty, true); + } + else + { + Rectangle area (tx, ty, sourceImage.getWidth(), sourceImage.getHeight()); + area = area.getIntersection (getThis().getMaximumBounds()); + + if (! area.isEmpty()) + if (typename BaseRegionType::Ptr c = clip->applyClipTo (new EdgeTableRegionType (area))) + c->renderImageUntransformed (getThis(), sourceImage, alpha, tx, ty, false); + } + + return; + } + } + + if (! t.isSingularity()) + { + if (tiledFillClipRegion != nullptr) + { + tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, true); + } + else + { + Path p; + p.addRectangle (sourceImage.getBounds()); + + typename BaseRegionType::Ptr c (clip->clone()); + c = c->clipToPath (p, t); + + if (c != nullptr) + c->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, false); + } + } + } + + void fillShape (typename BaseRegionType::Ptr shapeToFill, const bool replaceContents) + { + jassert (clip != nullptr); + + shapeToFill = clip->applyClipTo (shapeToFill); + + if (shapeToFill != nullptr) + { + if (fillType.isGradient()) + { + jassert (! replaceContents); // that option is just for solid colours + + ColourGradient g2 (*(fillType.gradient)); + g2.multiplyOpacity (fillType.getOpacity()); + AffineTransform t (transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f)); + + const bool isIdentity = t.isOnlyTranslation(); + + if (isIdentity) + { + // If our translation doesn't involve any distortion, we can speed it up.. + g2.point1.applyTransform (t); + g2.point2.applyTransform (t); + t = AffineTransform::identity; + } + + shapeToFill->fillAllWithGradient (getThis(), g2, t, isIdentity); + } + else if (fillType.isTiledImage()) + { + renderImage (fillType.image, fillType.transform, shapeToFill); + } + else + { + shapeToFill->fillAllWithColour (getThis(), fillType.colour.getPixelARGB(), replaceContents); + } + } + } + + void cloneClipIfMultiplyReferenced() + { + if (clip->getReferenceCount() > 1) + clip = clip->clone(); + } + + typename BaseRegionType::Ptr clip; + RenderingHelpers::TranslationOrTransform transform; + FillType fillType; + Graphics::ResamplingQuality interpolationQuality; + float transparencyLayerAlpha; +}; + +//============================================================================== +class SoftwareRendererSavedState : public SavedStateBase +{ + typedef SavedStateBase BaseClass; + +public: + SoftwareRendererSavedState (const Image& im, const Rectangle& clipBounds) + : BaseClass (clipBounds), image (im) + { + } + + SoftwareRendererSavedState (const Image& im, const RectangleList& clipList, Point origin) + : BaseClass (clipList, origin), image (im) + { + } + + SoftwareRendererSavedState (const SoftwareRendererSavedState& other) + : BaseClass (other), image (other.image), font (other.font) + { + } + + SoftwareRendererSavedState* beginTransparencyLayer (float opacity) + { + SoftwareRendererSavedState* s = new SoftwareRendererSavedState (*this); + + if (clip != nullptr) + { + const Rectangle layerBounds (clip->getClipBounds()); + + s->image = Image (Image::ARGB, layerBounds.getWidth(), layerBounds.getHeight(), true); + s->transparencyLayerAlpha = opacity; + s->transform.moveOriginInDeviceSpace (-layerBounds.getPosition()); + s->cloneClipIfMultiplyReferenced(); + s->clip->translate (-layerBounds.getPosition()); + } + + return s; + } + + void endTransparencyLayer (SoftwareRendererSavedState& finishedLayerState) + { + if (clip != nullptr) + { + const Rectangle layerBounds (clip->getClipBounds()); + + const ScopedPointer g (image.createLowLevelContext()); + g->setOpacity (finishedLayerState.transparencyLayerAlpha); + g->drawImage (finishedLayerState.image, AffineTransform::translation (layerBounds.getPosition())); + } + } + + typedef GlyphCache, SoftwareRendererSavedState> GlyphCacheType; + + static void clearGlyphCache() + { + GlyphCacheType::getInstance().reset(); + } + + //============================================================================== + void drawGlyph (int glyphNumber, const AffineTransform& trans) + { + if (clip != nullptr) + { + if (trans.isOnlyTranslation() && ! transform.isRotated) + { + GlyphCacheType& cache = GlyphCacheType::getInstance(); + + Point pos (trans.getTranslationX(), trans.getTranslationY()); + + if (transform.isOnlyTranslated) + { + cache.drawGlyph (*this, font, glyphNumber, pos + transform.offset.toFloat()); + } + else + { + pos = transform.transformed (pos); + + Font f (font); + f.setHeight (font.getHeight() * transform.complexTransform.mat11); + + const float xScale = transform.complexTransform.mat00 / transform.complexTransform.mat11; + if (std::abs (xScale - 1.0f) > 0.01f) + f.setHorizontalScale (xScale); + + cache.drawGlyph (*this, f, glyphNumber, pos); + } + } + else + { + const float fontHeight = font.getHeight(); + + AffineTransform t (transform.getTransformWith (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) + .followedBy (trans))); + + const ScopedPointer et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t, fontHeight)); + + if (et != nullptr) + fillShape (new EdgeTableRegionType (*et), false); + } + } + } + + Rectangle getMaximumBounds() const { return image.getBounds(); } + + //============================================================================== + template + void renderImageTransformed (IteratorType& iter, const Image& src, const int alpha, const AffineTransform& trans, Graphics::ResamplingQuality quality, bool tiledFill) const + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + const Image::BitmapData srcData (src, Image::BitmapData::readOnly); + EdgeTableFillers::renderImageTransformed (iter, destData, srcData, alpha, trans, quality, tiledFill); + } + + template + void renderImageUntransformed (IteratorType& iter, const Image& src, const int alpha, int x, int y, bool tiledFill) const + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + const Image::BitmapData srcData (src, Image::BitmapData::readOnly); + EdgeTableFillers::renderImageUntransformed (iter, destData, srcData, alpha, x, y, tiledFill); + } + + template + void fillWithSolidColour (IteratorType& iter, const PixelARGB colour, bool replaceContents) const + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } + + template + void fillWithGradient (IteratorType& iter, ColourGradient& gradient, const AffineTransform& trans, bool isIdentity) const + { + HeapBlock lookupTable; + const int numLookupEntries = gradient.createLookupTable (trans, lookupTable); + jassert (numLookupEntries > 0); + + Image::BitmapData destData (image, Image::BitmapData::readWrite); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + } + } + + //============================================================================== + Image image; + Font font; + +private: + SoftwareRendererSavedState& operator= (const SoftwareRendererSavedState&); +}; + +//============================================================================== +template +class SavedStateStack +{ +public: + SavedStateStack (StateObjectType* const initialState) noexcept + : currentState (initialState) + {} + + SavedStateStack() noexcept {} + + void initialise (StateObjectType* state) + { + currentState = state; + } + + inline StateObjectType* operator->() const noexcept { return currentState; } + inline StateObjectType& operator*() const noexcept { return *currentState; } + + void save() + { + stack.add (new StateObjectType (*currentState)); + } + + void restore() + { + if (StateObjectType* const top = stack.getLast()) + { + currentState = top; + stack.removeLast (1, false); + } + else + { + jassertfalse; // trying to pop with an empty stack! + } + } + + void beginTransparencyLayer (float opacity) + { + save(); + currentState = currentState->beginTransparencyLayer (opacity); + } + + void endTransparencyLayer() + { + const ScopedPointer finishedTransparencyLayer (currentState); + restore(); + currentState->endTransparencyLayer (*finishedTransparencyLayer); + } + +private: + ScopedPointer currentState; + OwnedArray stack; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedStateStack) +}; + +//============================================================================== +template +class StackBasedLowLevelGraphicsContext : public LowLevelGraphicsContext +{ +public: + bool isVectorDevice() const override { return false; } + void setOrigin (Point o) override { stack->transform.setOrigin (o); } + void addTransform (const AffineTransform& t) override { stack->transform.addTransform (t); } + float getPhysicalPixelScaleFactor() override { return stack->transform.getPhysicalPixelScaleFactor(); } + Rectangle getClipBounds() const override { return stack->getClipBounds(); } + bool isClipEmpty() const override { return stack->clip == nullptr; } + bool clipRegionIntersects (const Rectangle& r) override { return stack->clipRegionIntersects (r); } + bool clipToRectangle (const Rectangle& r) override { return stack->clipToRectangle (r); } + bool clipToRectangleList (const RectangleList& r) override { return stack->clipToRectangleList (r); } + void excludeClipRectangle (const Rectangle& r) override { stack->excludeClipRectangle (r); } + void clipToPath (const Path& path, const AffineTransform& t) override { stack->clipToPath (path, t); } + void clipToImageAlpha (const Image& im, const AffineTransform& t) override { stack->clipToImageAlpha (im, t); } + void saveState() override { stack.save(); } + void restoreState() override { stack.restore(); } + void beginTransparencyLayer (float opacity) override { stack.beginTransparencyLayer (opacity); } + void endTransparencyLayer() override { stack.endTransparencyLayer(); } + void setFill (const FillType& fillType) override { stack->setFillType (fillType); } + void setOpacity (float newOpacity) override { stack->fillType.setOpacity (newOpacity); } + void setInterpolationQuality (Graphics::ResamplingQuality quality) override { stack->interpolationQuality = quality; } + void fillRect (const Rectangle& r, bool replace) override { stack->fillRect (r, replace); } + void fillRect (const Rectangle& r) override { stack->fillRect (r); } + void fillRectList (const RectangleList& list) override { stack->fillRectList (list); } + void fillPath (const Path& path, const AffineTransform& t) override { stack->fillPath (path, t); } + void drawImage (const Image& im, const AffineTransform& t) override { stack->drawImage (im, t); } + void drawGlyph (int glyphNumber, const AffineTransform& t) override { stack->drawGlyph (glyphNumber, t); } + void drawLine (const Line& line) override { stack->drawLine (line); } + void setFont (const Font& newFont) override { stack->font = newFont; } + const Font& getFont() override { return stack->font; } + +protected: + StackBasedLowLevelGraphicsContext (SavedStateType* initialState) : stack (initialState) {} + StackBasedLowLevelGraphicsContext() {} + + RenderingHelpers::SavedStateStack stack; +}; + +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + +#endif // JUCE_RENDERINGHELPERS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp new file mode 100644 index 0000000..29702a2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -0,0 +1,334 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct DefaultFontNames +{ + DefaultFontNames() + : defaultSans ("sans"), + defaultSerif ("serif"), + defaultFixed ("monospace"), + defaultFallback ("sans") + { + } + + String getRealFontName (const String& faceName) const + { + if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans; + if (faceName == Font::getDefaultSerifFontName()) return defaultSerif; + if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed; + + return faceName; + } + + String defaultSans, defaultSerif, defaultFixed, defaultFallback; +}; + +Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +{ + static DefaultFontNames defaultNames; + + Font f (font); + f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName())); + return Typeface::createSystemTypefaceFor (f); +} + +//============================================================================== +#if JUCE_USE_FREETYPE + +StringArray FTTypefaceList::getDefaultFontDirectories() +{ + return StringArray ("/system/fonts"); +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) +{ + return new FreeTypeTypeface (font); +} + +void Typeface::scanFolderForFonts (const File& folder) +{ + FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); +} + +StringArray Font::findAllTypefaceNames() +{ + return FTTypefaceList::getInstance()->findAllFamilyNames(); +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + return FTTypefaceList::getInstance()->findAllTypefaceStyles (family); +} + +bool TextLayout::createNativeLayout (const AttributedString&) +{ + return false; +} + +#else + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \ + STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ + +DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface"); +#undef JNI_CLASS_MEMBERS + +//============================================================================== +StringArray Font::findAllTypefaceNames() +{ + StringArray results; + + Array fonts; + File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, "*.ttf"); + + for (int i = 0; i < fonts.size(); ++i) + results.addIfNotAlreadyThere (fonts.getReference(i).getFileNameWithoutExtension() + .upToLastOccurrenceOf ("-", false, false)); + + return results; +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + StringArray results ("Regular"); + + Array fonts; + File ("/system/fonts").findChildFiles (fonts, File::findFiles, false, family + "-*.ttf"); + + for (int i = 0; i < fonts.size(); ++i) + results.addIfNotAlreadyThere (fonts.getReference(i).getFileNameWithoutExtension() + .fromLastOccurrenceOf ("-", false, false)); + + return results; +} + +const float referenceFontSize = 256.0f; +const float referenceFontToUnits = 1.0f / referenceFontSize; + +//============================================================================== +class AndroidTypeface : public Typeface +{ +public: + AndroidTypeface (const Font& font) + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), + ascent (0), descent (0), heightToPointsFactor (1.0f) + { + JNIEnv* const env = getEnv(); + + const bool isBold = style.contains ("Bold"); + const bool isItalic = style.contains ("Italic"); + + File fontFile (getFontFile (name, style)); + + if (! fontFile.exists()) + fontFile = findFontFile (name, isBold, isItalic); + + if (fontFile.exists()) + typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, + javaString (fontFile.getFullPathName()).get())); + else + typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create, + javaString (getName()).get(), + (isBold ? 1 : 0) + (isItalic ? 2 : 0))); + + rect = GlobalRef (env->NewObject (RectClass, RectClass.constructor, 0, 0, 0, 0)); + + paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality)); + const LocalRef ignored (paint.callObjectMethod (Paint.setTypeface, typeface.get())); + + paint.callVoidMethod (Paint.setTextSize, referenceFontSize); + + const float fullAscent = std::abs (paint.callFloatMethod (Paint.ascent)); + const float fullDescent = paint.callFloatMethod (Paint.descent); + const float totalHeight = fullAscent + fullDescent; + + ascent = fullAscent / totalHeight; + descent = fullDescent / totalHeight; + heightToPointsFactor = referenceFontSize / totalHeight; + } + + float getAscent() const override { return ascent; } + float getDescent() const override { return descent; } + float getHeightToPointsFactor() const override { return heightToPointsFactor; } + + float getStringWidth (const String& text) override + { + JNIEnv* env = getEnv(); + const int numChars = text.length(); + jfloatArray widths = env->NewFloatArray (numChars); + + const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths); + + HeapBlock localWidths (numDone); + env->GetFloatArrayRegion (widths, 0, numDone, localWidths); + env->DeleteLocalRef (widths); + + float x = 0; + for (int i = 0; i < numDone; ++i) + x += localWidths[i]; + + return x * referenceFontToUnits; + } + + void getGlyphPositions (const String& text, Array& glyphs, Array& xOffsets) override + { + JNIEnv* env = getEnv(); + const int numChars = text.length(); + jfloatArray widths = env->NewFloatArray (numChars); + + const int numDone = paint.callIntMethod (Paint.getTextWidths, javaString (text).get(), widths); + + HeapBlock localWidths (numDone); + env->GetFloatArrayRegion (widths, 0, numDone, localWidths); + env->DeleteLocalRef (widths); + + String::CharPointerType s (text.getCharPointer()); + + xOffsets.add (0); + + float x = 0; + for (int i = 0; i < numDone; ++i) + { + glyphs.add ((int) s.getAndAdvance()); + x += localWidths[i]; + xOffsets.add (x * referenceFontToUnits); + } + } + + bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override + { + return false; + } + + EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override + { + JNIEnv* env = getEnv(); + + jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t)); + jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, (jchar) glyphNumber, paint.get(), matrix, rect.get()); + + env->DeleteLocalRef (matrix); + + const int left = env->GetIntField (rect.get(), RectClass.left); + const int top = env->GetIntField (rect.get(), RectClass.top); + const int right = env->GetIntField (rect.get(), RectClass.right); + const int bottom = env->GetIntField (rect.get(), RectClass.bottom); + + const Rectangle bounds (left, top, right - left, bottom - top); + + EdgeTable* et = nullptr; + + if (! bounds.isEmpty()) + { + et = new EdgeTable (bounds); + + jint* const maskDataElements = env->GetIntArrayElements (maskData, 0); + const jint* mask = maskDataElements; + + for (int y = top; y < bottom; ++y) + { + #if JUCE_LITTLE_ENDIAN + const uint8* const lineBytes = ((const uint8*) mask) + 3; + #else + const uint8* const lineBytes = (const uint8*) mask; + #endif + + et->clipLineToMask (left, y, lineBytes, 4, bounds.getWidth()); + mask += bounds.getWidth(); + } + + env->ReleaseIntArrayElements (maskData, maskDataElements, 0); + } + + env->DeleteLocalRef (maskData); + return et; + } + + GlobalRef typeface, paint, rect; + float ascent, descent, heightToPointsFactor; + +private: + static File findFontFile (const String& family, + const bool bold, const bool italic) + { + File file; + + if (bold || italic) + { + String suffix; + if (bold) suffix = "Bold"; + if (italic) suffix << "Italic"; + + file = getFontFile (family, suffix); + + if (file.exists()) + return file; + } + + file = getFontFile (family, "Regular"); + + if (! file.exists()) + file = getFontFile (family, String()); + + return file; + } + + static File getFontFile (const String& family, const String& style) + { + String path ("/system/fonts/" + family); + + if (style.isNotEmpty()) + path << '-' << style; + + return File (path + ".ttf"); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface) +}; + +//============================================================================== +Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) +{ + return new AndroidTypeface (font); +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const void*, size_t) +{ + jassertfalse; // not yet implemented! + return nullptr; +} + +void Typeface::scanFolderForFonts (const File&) +{ + jassertfalse; // not available unless using FreeType +} + +bool TextLayout::createNativeLayout (const AttributedString&) +{ + return false; +} + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_GraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_android_GraphicsContext.cpp new file mode 100644 index 0000000..9382c1d --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_android_GraphicsContext.cpp @@ -0,0 +1,60 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +namespace GraphicsHelpers +{ + jobject createPaint (Graphics::ResamplingQuality quality) + { + jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/ + | 4 /*DITHER_FLAG*/ + | 128 /*SUBPIXEL_TEXT_FLAG*/; + + if (quality > Graphics::lowResamplingQuality) + constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/ + + return getEnv()->NewObject (Paint, Paint.constructor, constructorFlags); + } + + const jobject createMatrix (JNIEnv* env, const AffineTransform& t) + { + jobject m = env->NewObject (Matrix, Matrix.constructor); + + jfloat values[9] = { t.mat00, t.mat01, t.mat02, + t.mat10, t.mat11, t.mat12, + 0.0f, 0.0f, 1.0f }; + + jfloatArray javaArray = env->NewFloatArray (9); + env->SetFloatArrayRegion (javaArray, 0, 9, values); + + env->CallVoidMethod (m, Matrix.setValues, javaArray); + env->DeleteLocalRef (javaArray); + + return m; + } +} + +ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const +{ + return SoftwareImageType().create (format, width, height, clearImage); +} diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp new file mode 100644 index 0000000..d124eaf --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp @@ -0,0 +1,455 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +struct FTLibWrapper : public ReferenceCountedObject +{ + FTLibWrapper() : library (0) + { + if (FT_Init_FreeType (&library) != 0) + { + library = 0; + DBG ("Failed to initialize FreeType"); + } + } + + ~FTLibWrapper() + { + if (library != 0) + FT_Done_FreeType (library); + } + + FT_Library library; + + typedef ReferenceCountedObjectPtr Ptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTLibWrapper) +}; + +//============================================================================== +struct FTFaceWrapper : public ReferenceCountedObject +{ + FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const File& file, int faceIndex) + : face (0), library (ftLib) + { + if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &face) != 0) + face = 0; + } + + FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const void* data, size_t dataSize, int faceIndex) + : face (0), library (ftLib), savedFaceData (data, dataSize) + { + if (FT_New_Memory_Face (ftLib->library, (const FT_Byte*) savedFaceData.getData(), + (FT_Long) savedFaceData.getSize(), faceIndex, &face) != 0) + face = 0; + } + + ~FTFaceWrapper() + { + if (face != 0) + FT_Done_Face (face); + } + + FT_Face face; + FTLibWrapper::Ptr library; + MemoryBlock savedFaceData; + + typedef ReferenceCountedObjectPtr Ptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper) +}; + +//============================================================================== +class FTTypefaceList : private DeletedAtShutdown +{ +public: + FTTypefaceList() : library (new FTLibWrapper()) + { + scanFontPaths (getDefaultFontDirectories()); + } + + ~FTTypefaceList() + { + clearSingletonInstance(); + } + + //============================================================================== + struct KnownTypeface + { + KnownTypeface (const File& f, const int index, const FTFaceWrapper& face) + : file (f), + family (face.face->family_name), + style (face.face->style_name), + faceIndex (index), + isMonospaced ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0), + isSansSerif (isFaceSansSerif (family)) + { + } + + const File file; + const String family, style; + const int faceIndex; + const bool isMonospaced, isSansSerif; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownTypeface) + }; + + //============================================================================== + static FTFaceWrapper::Ptr selectUnicodeCharmap (FTFaceWrapper* face) + { + if (face != nullptr) + if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) + FT_Set_Charmap (face->face, face->face->charmaps[0]); + + return face; + } + + FTFaceWrapper::Ptr createFace (const void* data, size_t dataSize, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, data, dataSize, index)); + } + + FTFaceWrapper::Ptr createFace (const File& file, int index) + { + return selectUnicodeCharmap (new FTFaceWrapper (library, file, index)); + } + + FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle) + { + const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle); + + if (ftFace == nullptr) ftFace = matchTypeface (fontName, "Regular"); + if (ftFace == nullptr) ftFace = matchTypeface (fontName, String()); + + if (ftFace != nullptr) + return createFace (ftFace->file, ftFace->faceIndex); + + return nullptr; + } + + //============================================================================== + StringArray findAllFamilyNames() const + { + StringArray s; + + for (int i = 0; i < faces.size(); ++i) + s.addIfNotAlreadyThere (faces.getUnchecked(i)->family); + + return s; + } + + static int indexOfRegularStyle (const StringArray& styles) + { + int i = styles.indexOf ("Regular", true); + + if (i < 0) + for (i = 0; i < styles.size(); ++i) + if (! (styles[i].containsIgnoreCase ("Bold") || styles[i].containsIgnoreCase ("Italic"))) + break; + + return i; + } + + StringArray findAllTypefaceStyles (const String& family) const + { + StringArray s; + + for (int i = 0; i < faces.size(); ++i) + { + const KnownTypeface* const face = faces.getUnchecked(i); + + if (face->family == family) + s.addIfNotAlreadyThere (face->style); + } + + // try to get a regular style to be first in the list + const int regular = indexOfRegularStyle (s); + if (regular > 0) + s.strings.swap (0, regular); + + return s; + } + + void scanFontPaths (const StringArray& paths) + { + for (int i = 0; i < paths.size(); ++i) + { + DirectoryIterator iter (File::getCurrentWorkingDirectory() + .getChildFile (paths[i]), true); + + while (iter.next()) + if (iter.getFile().hasFileExtension ("ttf;pfb;pcf;otf")) + scanFont (iter.getFile()); + } + } + + void getMonospacedNames (StringArray& monoSpaced) const + { + for (int i = 0; i < faces.size(); ++i) + if (faces.getUnchecked(i)->isMonospaced) + monoSpaced.addIfNotAlreadyThere (faces.getUnchecked(i)->family); + } + + void getSerifNames (StringArray& serif) const + { + for (int i = 0; i < faces.size(); ++i) + if (! faces.getUnchecked(i)->isSansSerif) + serif.addIfNotAlreadyThere (faces.getUnchecked(i)->family); + } + + void getSansSerifNames (StringArray& sansSerif) const + { + for (int i = 0; i < faces.size(); ++i) + if (faces.getUnchecked(i)->isSansSerif) + sansSerif.addIfNotAlreadyThere (faces.getUnchecked(i)->family); + } + + juce_DeclareSingleton_SingleThreaded_Minimal (FTTypefaceList) + +private: + FTLibWrapper::Ptr library; + OwnedArray faces; + + static StringArray getDefaultFontDirectories(); + + void scanFont (const File& file) + { + int faceIndex = 0; + int numFaces = 0; + + do + { + FTFaceWrapper face (library, file, faceIndex); + + if (face.face != 0) + { + if (faceIndex == 0) + numFaces = face.face->num_faces; + + if ((face.face->face_flags & FT_FACE_FLAG_SCALABLE) != 0) + faces.add (new KnownTypeface (file, faceIndex, face)); + } + + ++faceIndex; + } + while (faceIndex < numFaces); + } + + const KnownTypeface* matchTypeface (const String& familyName, const String& style) const noexcept + { + for (int i = 0; i < faces.size(); ++i) + { + const KnownTypeface* const face = faces.getUnchecked(i); + + if (face->family == familyName + && (face->style.equalsIgnoreCase (style) || style.isEmpty())) + return face; + } + + return nullptr; + } + + static bool isFaceSansSerif (const String& family) + { + static const char* sansNames[] = { "Sans", "Verdana", "Arial", "Ubuntu" }; + + for (int i = 0; i < numElementsInArray (sansNames); ++i) + if (family.containsIgnoreCase (sansNames[i])) + return true; + + return false; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTTypefaceList) +}; + +juce_ImplementSingleton_SingleThreaded (FTTypefaceList) + + +//============================================================================== +class FreeTypeTypeface : public CustomTypeface +{ +public: + FreeTypeTypeface (const Font& font) + : faceWrapper (FTTypefaceList::getInstance()->createFace (font.getTypefaceName(), + font.getTypefaceStyle())) + { + if (faceWrapper != nullptr) + initialiseCharacteristics (font.getTypefaceName(), + font.getTypefaceStyle()); + } + + FreeTypeTypeface (const void* data, size_t dataSize) + : faceWrapper (FTTypefaceList::getInstance()->createFace (data, dataSize, 0)) + { + if (faceWrapper != nullptr) + initialiseCharacteristics (faceWrapper->face->family_name, + faceWrapper->face->style_name); + } + + void initialiseCharacteristics (const String& name, const String& style) + { + setCharacteristics (name, style, + faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), + L' '); + } + + bool loadGlyphIfPossible (const juce_wchar character) + { + if (faceWrapper != nullptr) + { + FT_Face face = faceWrapper->face; + const unsigned int glyphIndex = FT_Get_Char_Index (face, character); + + if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING) == 0 + && face->glyph->format == ft_glyph_format_outline) + { + const float scale = 1.0f / (float) (face->ascender - face->descender); + Path destShape; + + if (getGlyphShape (destShape, face->glyph->outline, scale)) + { + addGlyph (character, destShape, face->glyph->metrics.horiAdvance * scale); + + if ((face->face_flags & FT_FACE_FLAG_KERNING) != 0) + addKerning (face, character, glyphIndex); + + return true; + } + } + } + + return false; + } + +private: + FTFaceWrapper::Ptr faceWrapper; + + bool getGlyphShape (Path& destShape, const FT_Outline& outline, const float scaleX) + { + const float scaleY = -scaleX; + const short* const contours = outline.contours; + const char* const tags = outline.tags; + const FT_Vector* const points = outline.points; + + for (int c = 0; c < outline.n_contours; ++c) + { + const int startPoint = (c == 0) ? 0 : contours [c - 1] + 1; + const int endPoint = contours[c]; + + for (int p = startPoint; p <= endPoint; ++p) + { + const float x = scaleX * points[p].x; + const float y = scaleY * points[p].y; + + if (p == startPoint) + { + if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic) + { + float x2 = scaleX * points [endPoint].x; + float y2 = scaleY * points [endPoint].y; + + if (FT_CURVE_TAG (tags[endPoint]) != FT_Curve_Tag_On) + { + x2 = (x + x2) * 0.5f; + y2 = (y + y2) * 0.5f; + } + + destShape.startNewSubPath (x2, y2); + } + else + { + destShape.startNewSubPath (x, y); + } + } + + if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_On) + { + if (p != startPoint) + destShape.lineTo (x, y); + } + else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic) + { + const int nextIndex = (p == endPoint) ? startPoint : p + 1; + float x2 = scaleX * points [nextIndex].x; + float y2 = scaleY * points [nextIndex].y; + + if (FT_CURVE_TAG (tags [nextIndex]) == FT_Curve_Tag_Conic) + { + x2 = (x + x2) * 0.5f; + y2 = (y + y2) * 0.5f; + } + else + { + ++p; + } + + destShape.quadraticTo (x, y, x2, y2); + } + else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Cubic) + { + const int next1 = p + 1; + const int next2 = (p == (endPoint - 1)) ? startPoint : (p + 2); + + if (p >= endPoint + || FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic + || FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On) + return false; + + const float x2 = scaleX * points [next1].x; + const float y2 = scaleY * points [next1].y; + const float x3 = scaleX * points [next2].x; + const float y3 = scaleY * points [next2].y; + + destShape.cubicTo (x, y, x2, y2, x3, y3); + p += 2; + } + } + + destShape.closeSubPath(); + } + + return true; + } + + void addKerning (FT_Face face, const uint32 character, const uint32 glyphIndex) + { + const float height = (float) (face->ascender - face->descender); + + uint32 rightGlyphIndex; + uint32 rightCharCode = FT_Get_First_Char (face, &rightGlyphIndex); + + while (rightGlyphIndex != 0) + { + FT_Vector kerning; + + if (FT_Get_Kerning (face, glyphIndex, rightGlyphIndex, ft_kerning_unscaled, &kerning) == 0 + && kerning.x != 0) + addKerningPair (character, rightCharCode, kerning.x / height); + + rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex); + } + } + + JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface) +}; diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp new file mode 100644 index 0000000..0869ec7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp @@ -0,0 +1,180 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +StringArray FTTypefaceList::getDefaultFontDirectories() +{ + StringArray fontDirs; + + fontDirs.addTokens (String (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH"))), ";,", ""); + fontDirs.removeEmptyStrings (true); + + if (fontDirs.size() == 0) + { + const ScopedPointer fontsInfo (XmlDocument::parse (File ("/etc/fonts/fonts.conf"))); + + if (fontsInfo != nullptr) + { + forEachXmlChildElementWithTagName (*fontsInfo, e, "dir") + { + String fontPath (e->getAllSubText().trim()); + + if (fontPath.isNotEmpty()) + { + if (e->getStringAttribute ("prefix") == "xdg") + { + String xdgDataHome (SystemStats::getEnvironmentVariable ("XDG_DATA_HOME", String())); + + if (xdgDataHome.trimStart().isEmpty()) + xdgDataHome = "~/.local/share"; + + fontPath = File (xdgDataHome).getChildFile (fontPath).getFullPathName(); + } + + fontDirs.add (fontPath); + } + } + } + } + + if (fontDirs.size() == 0) + fontDirs.add ("/usr/X11R6/lib/X11/fonts"); + + fontDirs.removeDuplicates (false); + return fontDirs; +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) +{ + return new FreeTypeTypeface (font); +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new FreeTypeTypeface (data, dataSize); +} + +void Typeface::scanFolderForFonts (const File& folder) +{ + FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); +} + +StringArray Font::findAllTypefaceNames() +{ + return FTTypefaceList::getInstance()->findAllFamilyNames(); +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + return FTTypefaceList::getInstance()->findAllTypefaceStyles (family); +} + +bool TextLayout::createNativeLayout (const AttributedString&) +{ + return false; +} + +//============================================================================== +struct DefaultFontNames +{ + DefaultFontNames() + : defaultSans (getDefaultSansSerifFontName()), + defaultSerif (getDefaultSerifFontName()), + defaultFixed (getDefaultMonospacedFontName()) + { + } + + String getRealFontName (const String& faceName) const + { + if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans; + if (faceName == Font::getDefaultSerifFontName()) return defaultSerif; + if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed; + + return faceName; + } + + String defaultSans, defaultSerif, defaultFixed; + +private: + static String pickBestFont (const StringArray& names, const char* const* choicesArray) + { + const StringArray choices (choicesArray); + + for (int j = 0; j < choices.size(); ++j) + if (names.contains (choices[j], true)) + return choices[j]; + + for (int j = 0; j < choices.size(); ++j) + for (int i = 0; i < names.size(); ++i) + if (names[i].startsWithIgnoreCase (choices[j])) + return names[i]; + + for (int j = 0; j < choices.size(); ++j) + for (int i = 0; i < names.size(); ++i) + if (names[i].containsIgnoreCase (choices[j])) + return names[i]; + + return names[0]; + } + + static String getDefaultSansSerifFontName() + { + StringArray allFonts; + FTTypefaceList::getInstance()->getSansSerifNames (allFonts); + + static const char* targets[] = { "Verdana", "Bitstream Vera Sans", "Luxi Sans", + "Liberation Sans", "DejaVu Sans", "Sans", nullptr }; + return pickBestFont (allFonts, targets); + } + + static String getDefaultSerifFontName() + { + StringArray allFonts; + FTTypefaceList::getInstance()->getSerifNames (allFonts); + + static const char* targets[] = { "Bitstream Vera Serif", "Times", "Nimbus Roman", + "Liberation Serif", "DejaVu Serif", "Serif", nullptr }; + return pickBestFont (allFonts, targets); + } + + static String getDefaultMonospacedFontName() + { + StringArray allFonts; + FTTypefaceList::getInstance()->getMonospacedNames (allFonts); + + static const char* targets[] = { "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Sans Mono", + "Liberation Mono", "Courier", "DejaVu Mono", "Mono", nullptr }; + return pickBestFont (allFonts, targets); + } + + JUCE_DECLARE_NON_COPYABLE (DefaultFontNames) +}; + +Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +{ + static DefaultFontNames defaultNames; + + Font f (font); + f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName())); + return Typeface::createSystemTypefaceFor (f); +} diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h new file mode 100644 index 0000000..bf26ce8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h @@ -0,0 +1,121 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED +#define JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED + +//============================================================================== +class CoreGraphicsContext : public LowLevelGraphicsContext +{ +public: + CoreGraphicsContext (CGContextRef context, const float flipHeight, const float targetScale); + ~CoreGraphicsContext(); + + //============================================================================== + bool isVectorDevice() const override { return false; } + + void setOrigin (Point) override; + void addTransform (const AffineTransform&) override; + float getPhysicalPixelScaleFactor() override; + bool clipToRectangle (const Rectangle&) override; + bool clipToRectangleList (const RectangleList&) override; + void excludeClipRectangle (const Rectangle&) override; + void clipToPath (const Path&, const AffineTransform&) override; + void clipToImageAlpha (const Image&, const AffineTransform&) override; + bool clipRegionIntersects (const Rectangle&) override; + Rectangle getClipBounds() const override; + bool isClipEmpty() const override; + + //============================================================================== + void saveState() override; + void restoreState() override; + void beginTransparencyLayer (float opacity) override; + void endTransparencyLayer() override; + + //============================================================================== + void setFill (const FillType&) override; + void setOpacity (float) override; + void setInterpolationQuality (Graphics::ResamplingQuality) override; + + //============================================================================== + void fillRect (const Rectangle&, bool replaceExistingContents) override; + void fillRect (const Rectangle&) override; + void fillRectList (const RectangleList&) override; + void fillPath (const Path&, const AffineTransform&) override; + void drawImage (const Image& sourceImage, const AffineTransform&) override; + + //============================================================================== + void drawLine (const Line&) override; + void setFont (const Font&) override; + const Font& getFont() override; + void drawGlyph (int glyphNumber, const AffineTransform&) override; + bool drawTextLayout (const AttributedString&, const Rectangle&) override; + +private: + CGContextRef context; + const CGFloat flipHeight; + float targetScale; + CGColorSpaceRef rgbColourSpace, greyColourSpace; + CGFunctionCallbacks gradientCallbacks; + mutable Rectangle lastClipRect; + mutable bool lastClipRectIsValid; + + struct SavedState + { + SavedState(); + SavedState (const SavedState&); + ~SavedState(); + + void setFill (const FillType& newFill); + CGShadingRef getShading (CoreGraphicsContext& owner); + + static void gradientCallback (void* info, const CGFloat* inData, CGFloat* outData); + + FillType fillType; + Font font; + CGFontRef fontRef; + CGAffineTransform fontTransform; + + private: + CGShadingRef shading; + HeapBlock gradientLookupTable; + int numGradientLookupEntries; + }; + + ScopedPointer state; + OwnedArray stateStack; + + void drawGradient(); + void createPath (const Path&) const; + void createPath (const Path&, const AffineTransform&) const; + void flip() const; + void applyTransform (const AffineTransform&) const; + void drawImage (const Image&, const AffineTransform&, bool fillEntireClipAsTiles); + bool clipToRectangleListWithoutTest (const RectangleList&); + void fillCGRect (const CGRect&, bool replaceExistingContents); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext) +}; + +#endif // JUCE_MAC_COREGRAPHICSCONTEXT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm new file mode 100644 index 0000000..44e344f --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -0,0 +1,927 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#include "juce_mac_CoreGraphicsContext.h" + +//============================================================================== +class CoreGraphicsImage : public ImagePixelData +{ +public: + CoreGraphicsImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage) + : ImagePixelData (format, w, h), cachedImageRef (0) + { + pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1); + lineStride = (pixelStride * jmax (1, width) + 3) & ~3; + + imageData.allocate ((size_t) (lineStride * jmax (1, height)), clearImage); + + CGColorSpaceRef colourSpace = (format == Image::SingleChannel) ? CGColorSpaceCreateDeviceGray() + : CGColorSpaceCreateDeviceRGB(); + + context = CGBitmapContextCreate (imageData, (size_t) width, (size_t) height, 8, (size_t) lineStride, + colourSpace, getCGImageFlags (format)); + + CGColorSpaceRelease (colourSpace); + } + + ~CoreGraphicsImage() + { + freeCachedImageRef(); + CGContextRelease (context); + } + + LowLevelGraphicsContext* createLowLevelContext() override + { + freeCachedImageRef(); + sendDataChangeMessage(); + return new CoreGraphicsContext (context, height, 1.0f); + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override + { + bitmap.data = imageData + x * pixelStride + y * lineStride; + bitmap.pixelFormat = pixelFormat; + bitmap.lineStride = lineStride; + bitmap.pixelStride = pixelStride; + + if (mode != Image::BitmapData::readOnly) + { + freeCachedImageRef(); + sendDataChangeMessage(); + } + } + + ImagePixelData* clone() override + { + CoreGraphicsImage* im = new CoreGraphicsImage (pixelFormat, width, height, false); + memcpy (im->imageData, imageData, (size_t) (lineStride * height)); + return im; + } + + ImageType* createType() const override { return new NativeImageType(); } + + //============================================================================== + static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace) + { + CoreGraphicsImage* const cgim = dynamic_cast (juceImage.getPixelData()); + + if (cgim != nullptr && cgim->cachedImageRef != 0) + { + CGImageRetain (cgim->cachedImageRef); + return cgim->cachedImageRef; + } + + CGImageRef ref = createImage (juceImage, colourSpace, false); + + if (cgim != nullptr) + { + CGImageRetain (ref); + cgim->cachedImageRef = ref; + } + + return ref; + } + + static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, const bool mustOutliveSource) + { + const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly); + CGDataProviderRef provider; + + if (mustOutliveSource) + { + CFDataRef data = CFDataCreate (0, (const UInt8*) srcData.data, (CFIndex) (srcData.lineStride * srcData.height)); + provider = CGDataProviderCreateWithCFData (data); + CFRelease (data); + } + else + { + provider = CGDataProviderCreateWithData (0, srcData.data, (size_t) (srcData.lineStride * srcData.height), 0); + } + + CGImageRef imageRef = CGImageCreate ((size_t) srcData.width, + (size_t) srcData.height, + 8, (size_t) srcData.pixelStride * 8, + (size_t) srcData.lineStride, + colourSpace, getCGImageFlags (juceImage.getFormat()), provider, + 0, true, kCGRenderingIntentDefault); + + CGDataProviderRelease (provider); + return imageRef; + } + + //============================================================================== + CGContextRef context; + CGImageRef cachedImageRef; + HeapBlock imageData; + int pixelStride, lineStride; + +private: + void freeCachedImageRef() + { + if (cachedImageRef != 0) + { + CGImageRelease (cachedImageRef); + cachedImageRef = 0; + } + } + + static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) + { + #if JUCE_BIG_ENDIAN + return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; + #else + return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; + #endif + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImage) +}; + +ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const +{ + return new CoreGraphicsImage (format == Image::RGB ? Image::ARGB : format, width, height, clearImage); +} + +//============================================================================== +CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, const float h, const float scale) + : context (c), + flipHeight (h), + targetScale (scale), + lastClipRectIsValid (false), + state (new SavedState()) +{ + CGContextRetain (context); + CGContextSaveGState(context); + CGContextSetShouldSmoothFonts (context, true); + CGContextSetShouldAntialias (context, true); + CGContextSetBlendMode (context, kCGBlendModeNormal); + rgbColourSpace = CGColorSpaceCreateDeviceRGB(); + greyColourSpace = CGColorSpaceCreateDeviceGray(); + gradientCallbacks.version = 0; + gradientCallbacks.evaluate = SavedState::gradientCallback; + gradientCallbacks.releaseInfo = 0; + setFont (Font()); +} + +CoreGraphicsContext::~CoreGraphicsContext() +{ + CGContextRestoreGState (context); + CGContextRelease (context); + CGColorSpaceRelease (rgbColourSpace); + CGColorSpaceRelease (greyColourSpace); +} + +//============================================================================== +void CoreGraphicsContext::setOrigin (Point o) +{ + CGContextTranslateCTM (context, o.x, -o.y); + + if (lastClipRectIsValid) + lastClipRect.translate (-o.x, -o.y); +} + +void CoreGraphicsContext::addTransform (const AffineTransform& transform) +{ + applyTransform (AffineTransform::verticalFlip ((float) flipHeight) + .followedBy (transform) + .translated (0, (float) -flipHeight) + .scaled (1.0f, -1.0f)); + lastClipRectIsValid = false; + + jassert (getPhysicalPixelScaleFactor() > 0.0f); + jassert (getPhysicalPixelScaleFactor() > 0.0f); +} + +float CoreGraphicsContext::getPhysicalPixelScaleFactor() +{ + const CGAffineTransform t = CGContextGetCTM (context); + + return targetScale * (float) (juce_hypot (t.a, t.c) + juce_hypot (t.b, t.d)) / 2.0f; + +// return targetScale * (float) (t.a + t.d) / 2.0f; +} + +bool CoreGraphicsContext::clipToRectangle (const Rectangle& r) +{ + CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); + + if (lastClipRectIsValid) + { + // This is actually incorrect, because the actual clip region may be complex, and + // clipping its bounds to a rect may not be right... But, removing this shortcut + // doesn't actually fix anything because CoreGraphics also ignores complex regions + // when calculating the resultant clip bounds, and makes the same mistake! + lastClipRect = lastClipRect.getIntersection (r); + return ! lastClipRect.isEmpty(); + } + + return ! isClipEmpty(); +} + +bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList& clipRegion) +{ + if (clipRegion.isEmpty()) + { + CGContextClipToRect (context, CGRectZero); + lastClipRectIsValid = true; + lastClipRect = Rectangle(); + return false; + } + else + { + const size_t numRects = (size_t) clipRegion.getNumRectangles(); + HeapBlock rects (numRects); + + int i = 0; + for (const Rectangle* r = clipRegion.begin(), * const e = clipRegion.end(); r != e; ++r) + rects[i++] = CGRectMake (r->getX(), flipHeight - r->getBottom(), r->getWidth(), r->getHeight()); + + CGContextClipToRects (context, rects, numRects); + lastClipRectIsValid = false; + return true; + } +} + +bool CoreGraphicsContext::clipToRectangleList (const RectangleList& clipRegion) +{ + return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty(); +} + +void CoreGraphicsContext::excludeClipRectangle (const Rectangle& r) +{ + RectangleList remaining (getClipBounds()); + remaining.subtract (r); + clipToRectangleListWithoutTest (remaining); +} + +void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) +{ + createPath (path, transform); + CGContextClip (context); + lastClipRectIsValid = false; +} + +void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) +{ + if (! transform.isSingularity()) + { + Image singleChannelImage (sourceImage); + + if (sourceImage.getFormat() != Image::SingleChannel) + singleChannelImage = sourceImage.convertedToFormat (Image::SingleChannel); + + CGImageRef image = CoreGraphicsImage::createImage (singleChannelImage, greyColourSpace, true); + + flip(); + AffineTransform t (AffineTransform::verticalFlip (sourceImage.getHeight()).followedBy (transform)); + applyTransform (t); + + CGRect r = convertToCGRect (sourceImage.getBounds()); + CGContextClipToMask (context, r, image); + + applyTransform (t.inverted()); + flip(); + + CGImageRelease (image); + lastClipRectIsValid = false; + } +} + +bool CoreGraphicsContext::clipRegionIntersects (const Rectangle& r) +{ + return getClipBounds().intersects (r); +} + +Rectangle CoreGraphicsContext::getClipBounds() const +{ + if (! lastClipRectIsValid) + { + CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); + + lastClipRectIsValid = true; + lastClipRect.setBounds (roundToInt (bounds.origin.x), + roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), + roundToInt (bounds.size.width), + roundToInt (bounds.size.height)); + } + + return lastClipRect; +} + +bool CoreGraphicsContext::isClipEmpty() const +{ + return getClipBounds().isEmpty(); +} + +//============================================================================== +void CoreGraphicsContext::saveState() +{ + CGContextSaveGState (context); + stateStack.add (new SavedState (*state)); +} + +void CoreGraphicsContext::restoreState() +{ + CGContextRestoreGState (context); + + if (SavedState* const top = stateStack.getLast()) + { + state = top; + stateStack.removeLast (1, false); + lastClipRectIsValid = false; + } + else + { + jassertfalse; // trying to pop with an empty stack! + } +} + +void CoreGraphicsContext::beginTransparencyLayer (float opacity) +{ + saveState(); + CGContextSetAlpha (context, opacity); + CGContextBeginTransparencyLayer (context, 0); +} + +void CoreGraphicsContext::endTransparencyLayer() +{ + CGContextEndTransparencyLayer (context); + restoreState(); +} + +//============================================================================== +void CoreGraphicsContext::setFill (const FillType& fillType) +{ + state->setFill (fillType); + + if (fillType.isColour()) + { + CGContextSetRGBFillColor (context, fillType.colour.getFloatRed(), fillType.colour.getFloatGreen(), + fillType.colour.getFloatBlue(), fillType.colour.getFloatAlpha()); + CGContextSetAlpha (context, 1.0f); + } +} + +void CoreGraphicsContext::setOpacity (float newOpacity) +{ + state->fillType.setOpacity (newOpacity); + setFill (state->fillType); +} + +void CoreGraphicsContext::setInterpolationQuality (Graphics::ResamplingQuality quality) +{ + CGContextSetInterpolationQuality (context, quality == Graphics::lowResamplingQuality + ? kCGInterpolationLow + : kCGInterpolationHigh); +} + +//============================================================================== +void CoreGraphicsContext::fillRect (const Rectangle& r, const bool replaceExistingContents) +{ + fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), replaceExistingContents); +} + +void CoreGraphicsContext::fillRect (const Rectangle& r) +{ + fillCGRect (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()), false); +} + +void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, const bool replaceExistingContents) +{ + if (replaceExistingContents) + { + #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 + CGContextClearRect (context, cgRect); + #else + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + if (CGContextDrawLinearGradient == 0) // (just a way of checking whether we're running in 10.5 or later) + CGContextClearRect (context, cgRect); + else + #endif + CGContextSetBlendMode (context, kCGBlendModeCopy); + #endif + + fillCGRect (cgRect, false); + CGContextSetBlendMode (context, kCGBlendModeNormal); + } + else + { + if (state->fillType.isColour()) + { + CGContextFillRect (context, cgRect); + } + else if (state->fillType.isGradient()) + { + CGContextSaveGState (context); + CGContextClipToRect (context, cgRect); + drawGradient(); + CGContextRestoreGState (context); + } + else + { + CGContextSaveGState (context); + CGContextClipToRect (context, cgRect); + drawImage (state->fillType.image, state->fillType.transform, true); + CGContextRestoreGState (context); + } + } +} + +void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform) +{ + CGContextSaveGState (context); + + if (state->fillType.isColour()) + { + flip(); + applyTransform (transform); + createPath (path); + + if (path.isUsingNonZeroWinding()) + CGContextFillPath (context); + else + CGContextEOFillPath (context); + } + else + { + createPath (path, transform); + + if (path.isUsingNonZeroWinding()) + CGContextClip (context); + else + CGContextEOClip (context); + + if (state->fillType.isGradient()) + drawGradient(); + else + drawImage (state->fillType.image, state->fillType.transform, true); + } + + CGContextRestoreGState (context); +} + +void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) +{ + drawImage (sourceImage, transform, false); +} + +void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform, const bool fillEntireClipAsTiles) +{ + const int iw = sourceImage.getWidth(); + const int ih = sourceImage.getHeight(); + CGImageRef image = CoreGraphicsImage::getCachedImageRef (sourceImage, rgbColourSpace); + + CGContextSaveGState (context); + CGContextSetAlpha (context, state->fillType.getOpacity()); + + flip(); + applyTransform (AffineTransform::verticalFlip (ih).followedBy (transform)); + CGRect imageRect = CGRectMake (0, 0, iw, ih); + + if (fillEntireClipAsTiles) + { + #if JUCE_IOS + CGContextDrawTiledImage (context, imageRect, image); + #else + #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // There's a bug in CGContextDrawTiledImage that makes it incredibly slow + // if it's doing a transformation - it's quicker to just draw lots of images manually + if (CGContextDrawTiledImage != 0 && transform.isOnlyTranslation()) + CGContextDrawTiledImage (context, imageRect, image); + else + #endif + { + // Fallback to manually doing a tiled fill on 10.4 + CGRect clip = CGRectIntegral (CGContextGetClipBoundingBox (context)); + + int x = 0, y = 0; + while (x > clip.origin.x) x -= iw; + while (y > clip.origin.y) y -= ih; + + const int right = (int) (clip.origin.x + clip.size.width); + const int bottom = (int) (clip.origin.y + clip.size.height); + + while (y < bottom) + { + for (int x2 = x; x2 < right; x2 += iw) + CGContextDrawImage (context, CGRectMake (x2, y, iw, ih), image); + + y += ih; + } + } + #endif + } + else + { + CGContextDrawImage (context, imageRect, image); + } + + CGImageRelease (image); // (This causes a memory bug in iOS sim 3.0 - try upgrading to a later version if you hit this) + CGContextRestoreGState (context); +} + +//============================================================================== +void CoreGraphicsContext::drawLine (const Line& line) +{ + if (state->fillType.isColour()) + { + CGContextSetLineCap (context, kCGLineCapSquare); + CGContextSetLineWidth (context, 1.0f); + CGContextSetRGBStrokeColor (context, + state->fillType.colour.getFloatRed(), state->fillType.colour.getFloatGreen(), + state->fillType.colour.getFloatBlue(), state->fillType.colour.getFloatAlpha()); + + CGPoint cgLine[] = { { (CGFloat) line.getStartX(), flipHeight - (CGFloat) line.getStartY() }, + { (CGFloat) line.getEndX(), flipHeight - (CGFloat) line.getEndY() } }; + + CGContextStrokeLineSegments (context, cgLine, 1); + } + else + { + Path p; + p.addLineSegment (line, 1.0f); + fillPath (p, AffineTransform::identity); + } +} + +void CoreGraphicsContext::fillRectList (const RectangleList& list) +{ + HeapBlock rects ((size_t) list.getNumRectangles()); + + size_t num = 0; + for (const Rectangle* r = list.begin(), * const e = list.end(); r != e; ++r) + rects[num++] = CGRectMake (r->getX(), flipHeight - r->getBottom(), r->getWidth(), r->getHeight()); + + if (state->fillType.isColour()) + { + CGContextFillRects (context, rects, num); + } + else if (state->fillType.isGradient()) + { + CGContextSaveGState (context); + CGContextClipToRects (context, rects, num); + drawGradient(); + CGContextRestoreGState (context); + } + else + { + CGContextSaveGState (context); + CGContextClipToRects (context, rects, num); + drawImage (state->fillType.image, state->fillType.transform, true); + CGContextRestoreGState (context); + } +} + +void CoreGraphicsContext::setFont (const Font& newFont) +{ + if (state->font != newFont) + { + state->fontRef = 0; + state->font = newFont; + + if (OSXTypeface* osxTypeface = dynamic_cast (state->font.getTypeface())) + { + state->fontRef = osxTypeface->fontRef; + CGContextSetFont (context, state->fontRef); + CGContextSetFontSize (context, state->font.getHeight() * osxTypeface->fontHeightToPointsFactor); + + state->fontTransform = osxTypeface->renderingTransform; + state->fontTransform.a *= state->font.getHorizontalScale(); + CGContextSetTextMatrix (context, state->fontTransform); + } + } +} + +const Font& CoreGraphicsContext::getFont() +{ + return state->font; +} + +void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform) +{ + if (state->fontRef != 0 && state->fillType.isColour()) + { + #if JUCE_CLANG + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif + + if (transform.isOnlyTranslation()) + { + CGContextSetTextMatrix (context, state->fontTransform); // have to set this each time, as it's not saved as part of the state + + CGGlyph g = (CGGlyph) glyphNumber; + CGContextShowGlyphsAtPoint (context, transform.getTranslationX(), + flipHeight - roundToInt (transform.getTranslationY()), &g, 1); + } + else + { + CGContextSaveGState (context); + flip(); + applyTransform (transform); + + CGAffineTransform t = state->fontTransform; + t.d = -t.d; + CGContextSetTextMatrix (context, t); + + CGGlyph g = (CGGlyph) glyphNumber; + CGContextShowGlyphsAtPoint (context, 0, 0, &g, 1); + + CGContextRestoreGState (context); + } + + #if JUCE_CLANG + #pragma clang diagnostic pop + #endif + } + else + { + Path p; + Font& f = state->font; + f.getTypeface()->getOutlineForGlyph (glyphNumber, p); + + fillPath (p, AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight()) + .followedBy (transform)); + } +} + +bool CoreGraphicsContext::drawTextLayout (const AttributedString& text, const Rectangle& area) +{ + #if JUCE_CORETEXT_AVAILABLE + CoreTextTypeLayout::drawToCGContext (text, area, context, (float) flipHeight); + return true; + #else + (void) text; (void) area; + return false; + #endif +} + +CoreGraphicsContext::SavedState::SavedState() + : font (1.0f), fontRef (0), fontTransform (CGAffineTransformIdentity), + shading (0), numGradientLookupEntries (0) +{ +} + +CoreGraphicsContext::SavedState::SavedState (const SavedState& other) + : fillType (other.fillType), font (other.font), fontRef (other.fontRef), + fontTransform (other.fontTransform), shading (0), + gradientLookupTable ((size_t) other.numGradientLookupEntries), + numGradientLookupEntries (other.numGradientLookupEntries) +{ + memcpy (gradientLookupTable, other.gradientLookupTable, sizeof (PixelARGB) * (size_t) numGradientLookupEntries); +} + +CoreGraphicsContext::SavedState::~SavedState() +{ + if (shading != 0) + CGShadingRelease (shading); +} + +void CoreGraphicsContext::SavedState::setFill (const FillType& newFill) +{ + fillType = newFill; + + if (fillType.isGradient() && shading != 0) + { + CGShadingRelease (shading); + shading = 0; + } +} + +CGShadingRef CoreGraphicsContext::SavedState::getShading (CoreGraphicsContext& owner) +{ + if (shading == 0) + { + ColourGradient& g = *(fillType.gradient); + numGradientLookupEntries = g.createLookupTable (fillType.transform, gradientLookupTable) - 1; + + CGFunctionRef function = CGFunctionCreate (this, 1, 0, 4, 0, &(owner.gradientCallbacks)); + CGPoint p1 (convertToCGPoint (g.point1)); + + if (g.isRadial) + { + shading = CGShadingCreateRadial (owner.rgbColourSpace, p1, 0, + p1, g.point1.getDistanceFrom (g.point2), + function, true, true); + } + else + { + shading = CGShadingCreateAxial (owner.rgbColourSpace, p1, + convertToCGPoint (g.point2), + function, true, true); + } + + CGFunctionRelease (function); + } + + return shading; +} + +void CoreGraphicsContext::SavedState::gradientCallback (void* info, const CGFloat* inData, CGFloat* outData) +{ + const SavedState* const s = static_cast (info); + + const int index = roundToInt (s->numGradientLookupEntries * inData[0]); + PixelARGB colour (s->gradientLookupTable [jlimit (0, s->numGradientLookupEntries, index)]); + colour.unpremultiply(); + + outData[0] = colour.getRed() / 255.0f; + outData[1] = colour.getGreen() / 255.0f; + outData[2] = colour.getBlue() / 255.0f; + outData[3] = colour.getAlpha() / 255.0f; +} + +void CoreGraphicsContext::drawGradient() +{ + flip(); + applyTransform (state->fillType.transform); + + CGContextSetInterpolationQuality (context, kCGInterpolationDefault); // (This is required for 10.4, where there's a crash if + // you draw a gradient with high quality interp enabled). + CGContextSetAlpha (context, state->fillType.getOpacity()); + CGContextDrawShading (context, state->getShading (*this)); +} + +void CoreGraphicsContext::createPath (const Path& path) const +{ + CGContextBeginPath (context); + Path::Iterator i (path); + + while (i.next()) + { + switch (i.elementType) + { + case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context, i.x1, i.y1); break; + case Path::Iterator::lineTo: CGContextAddLineToPoint (context, i.x1, i.y1); break; + case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context, i.x1, i.y1, i.x2, i.y2); break; + case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break; + case Path::Iterator::closePath: CGContextClosePath (context); break; + default: jassertfalse; break; + } + } +} + +void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const +{ + CGContextBeginPath (context); + Path::Iterator i (path); + + while (i.next()) + { + switch (i.elementType) + { + case Path::Iterator::startNewSubPath: + transform.transformPoint (i.x1, i.y1); + CGContextMoveToPoint (context, i.x1, flipHeight - i.y1); + break; + case Path::Iterator::lineTo: + transform.transformPoint (i.x1, i.y1); + CGContextAddLineToPoint (context, i.x1, flipHeight - i.y1); + break; + case Path::Iterator::quadraticTo: + transform.transformPoints (i.x1, i.y1, i.x2, i.y2); + CGContextAddQuadCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2); + break; + case Path::Iterator::cubicTo: + transform.transformPoints (i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); + CGContextAddCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3); + break; + case Path::Iterator::closePath: + CGContextClosePath (context); break; + default: + jassertfalse; + break; + } + } +} + +void CoreGraphicsContext::flip() const +{ + CGContextConcatCTM (context, CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight)); +} + +void CoreGraphicsContext::applyTransform (const AffineTransform& transform) const +{ + CGAffineTransform t; + t.a = transform.mat00; + t.b = transform.mat10; + t.c = transform.mat01; + t.d = transform.mat11; + t.tx = transform.mat02; + t.ty = transform.mat12; + CGContextConcatCTM (context, t); +} + +//============================================================================== +#if USE_COREGRAPHICS_RENDERING && JUCE_USE_COREIMAGE_LOADER +Image juce_loadWithCoreImage (InputStream& input) +{ + MemoryBlock data; + input.readIntoMemoryBlock (data, -1); + + #if JUCE_IOS + JUCE_AUTORELEASEPOOL + #endif + { + #if JUCE_IOS + if (UIImage* uiImage = [UIImage imageWithData: [NSData dataWithBytesNoCopy: data.getData() + length: data.getSize() + freeWhenDone: NO]]) + { + CGImageRef loadedImage = uiImage.CGImage; + + #else + CGDataProviderRef provider = CGDataProviderCreateWithData (0, data.getData(), data.getSize(), 0); + CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider (provider, 0); + CGDataProviderRelease (provider); + + if (imageSource != 0) + { + CGImageRef loadedImage = CGImageSourceCreateImageAtIndex (imageSource, 0, 0); + CFRelease (imageSource); + #endif + + if (loadedImage != 0) + { + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage); + const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone + && alphaInfo != kCGImageAlphaNoneSkipLast + && alphaInfo != kCGImageAlphaNoneSkipFirst); + + Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images) + (int) CGImageGetWidth (loadedImage), + (int) CGImageGetHeight (loadedImage), + hasAlphaChan)); + + CoreGraphicsImage* const cgImage = dynamic_cast (image.getPixelData()); + jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsImage class should have been used. + + CGContextDrawImage (cgImage->context, convertToCGRect (image.getBounds()), loadedImage); + CGContextFlush (cgImage->context); + + #if ! JUCE_IOS + CFRelease (loadedImage); + #endif + + // Because it's impossible to create a truly 24-bit CG image, this flag allows a user + // to find out whether the file they just loaded the image from had an alpha channel or not. + image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan); + return image; + } + } + } + + return Image::null; +} +#endif + +#if JUCE_MAC +Image juce_createImageFromCIImage (CIImage*, int, int); +Image juce_createImageFromCIImage (CIImage* im, int w, int h) +{ + CoreGraphicsImage* cgImage = new CoreGraphicsImage (Image::ARGB, w, h, false); + + CIContext* cic = [CIContext contextWithCGContext: cgImage->context options: nil]; + [cic drawImage: im inRect: CGRectMake (0, 0, w, h) fromRect: CGRectMake (0, 0, w, h)]; + CGContextFlush (cgImage->context); + + return Image (cgImage); +} + +CGImageRef juce_createCoreGraphicsImage (const Image& juceImage, CGColorSpaceRef colourSpace, + const bool mustOutliveSource) +{ + return CoreGraphicsImage::createImage (juceImage, colourSpace, mustOutliveSource); +} + +CGContextRef juce_getImageContext (const Image& image) +{ + if (CoreGraphicsImage* const cgi = dynamic_cast (image.getPixelData())) + return cgi->context; + + jassertfalse; + return 0; +} + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h new file mode 100644 index 0000000..ca6f699 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h @@ -0,0 +1,60 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED +#define JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED + + +//============================================================================== +namespace +{ + template + Rectangle convertToRectInt (const RectType& r) noexcept + { + return Rectangle ((int) r.origin.x, (int) r.origin.y, (int) r.size.width, (int) r.size.height); + } + + template + Rectangle convertToRectFloat (const RectType& r) noexcept + { + return Rectangle (r.origin.x, r.origin.y, r.size.width, r.size.height); + } + + template + CGRect convertToCGRect (const RectType& r) noexcept + { + return CGRectMake ((CGFloat) r.getX(), (CGFloat) r.getY(), (CGFloat) r.getWidth(), (CGFloat) r.getHeight()); + } + + template + CGPoint convertToCGPoint (const PointType& p) noexcept + { + return CGPointMake ((CGFloat) p.x, (CGFloat) p.y); + } +} + +extern CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource); +extern CGContextRef juce_getImageContext (const Image&); + +#endif // JUCE_MAC_COREGRAPHICSHELPERS_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm new file mode 100644 index 0000000..81a29a8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -0,0 +1,1321 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if (! defined (JUCE_CORETEXT_AVAILABLE)) \ + && (JUCE_IOS || (JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4)) + #define JUCE_CORETEXT_AVAILABLE 1 +#endif + +const float referenceFontSize = 1024.0f; + +#if JUCE_CORETEXT_AVAILABLE + +#if JUCE_MAC && MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5 +extern "C" +{ + void CTRunGetAdvances (CTRunRef, CFRange, CGSize buffer[]); + const CGSize* CTRunGetAdvancesPtr (CTRunRef); +} +#endif + +static CTFontRef getCTFontFromTypeface (const Font& f); + +namespace CoreTextTypeLayout +{ + static String findBestAvailableStyle (const Font& font, CGAffineTransform& requiredTransform) + { + const StringArray availableStyles (Font::findAllTypefaceStyles (font.getTypefaceName())); + const String style (font.getTypefaceStyle()); + + if (! availableStyles.contains (style)) + { + if (font.isItalic()) // Fake-up an italic font if there isn't a real one. + requiredTransform = CGAffineTransformMake (1.0f, 0, 0.25f, 1.0f, 0, 0); + + return availableStyles[0]; + } + + return style; + } + + // Workaround for Apple bug in CTFontCreateWithFontDescriptor in Garageband/Logic on 10.6 + #if JUCE_MAC && ((! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7) + static CTFontRef getFontWithTrait (CTFontRef ctFontRef, CTFontSymbolicTraits trait) + { + if (CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0f, nullptr, trait, trait)) + { + CFRelease (ctFontRef); + return newFont; + } + + return ctFontRef; + } + + static CTFontRef useStyleFallbackIfNecessary (CTFontRef ctFontRef, CFStringRef cfFontFamily, + const float fontSizePoints, const Font& font) + { + CFStringRef cfActualFontFamily = (CFStringRef) CTFontCopyAttribute (ctFontRef, kCTFontFamilyNameAttribute); + + if (CFStringCompare (cfFontFamily, cfActualFontFamily, 0) != kCFCompareEqualTo) + { + CFRelease (ctFontRef); + ctFontRef = CTFontCreateWithName (cfFontFamily, fontSizePoints, nullptr); + + if (font.isItalic()) ctFontRef = getFontWithTrait (ctFontRef, kCTFontItalicTrait); + if (font.isBold()) ctFontRef = getFontWithTrait (ctFontRef, kCTFontBoldTrait); + } + + CFRelease (cfActualFontFamily); + return ctFontRef; + } + #endif + + static float getFontTotalHeight (CTFontRef font) + { + return std::abs ((float) CTFontGetAscent (font)) + std::abs ((float) CTFontGetDescent (font)); + } + + static float getHeightToPointsFactor (CTFontRef font) + { + return referenceFontSize / getFontTotalHeight (font); + } + + static CTFontRef getFontWithPointSize (CTFontRef font, float size) + { + CTFontRef newFont = CTFontCreateCopyWithAttributes (font, size, nullptr, nullptr); + CFRelease (font); + return newFont; + } + + static CTFontRef createCTFont (const Font& font, const float fontSizePoints, CGAffineTransform& transformRequired) + { + CFStringRef cfFontFamily = FontStyleHelpers::getConcreteFamilyName (font).toCFString(); + CFStringRef cfFontStyle = findBestAvailableStyle (font, transformRequired).toCFString(); + CFStringRef keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute }; + CFTypeRef values[] = { cfFontFamily, cfFontStyle }; + + CFDictionaryRef fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, + (const void**) &values, + numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFRelease (cfFontStyle); + + CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); + CFRelease (fontDescAttributes); + + CTFontRef ctFontRef = CTFontCreateWithFontDescriptor (ctFontDescRef, fontSizePoints, nullptr); + CFRelease (ctFontDescRef); + + #if JUCE_MAC && ((! defined (MAC_OS_X_VERSION_10_7)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7) + ctFontRef = useStyleFallbackIfNecessary (ctFontRef, cfFontFamily, fontSizePoints, font); + #endif + + CFRelease (cfFontFamily); + + return ctFontRef; + } + + //============================================================================== + struct Advances + { + Advances (CTRunRef run, const CFIndex numGlyphs) + : advances (CTRunGetAdvancesPtr (run)) + { + if (advances == nullptr) + { + local.malloc ((size_t) numGlyphs); + CTRunGetAdvances (run, CFRangeMake (0, 0), local); + advances = local; + } + } + + const CGSize* advances; + HeapBlock local; + }; + + struct Glyphs + { + Glyphs (CTRunRef run, const size_t numGlyphs) + : glyphs (CTRunGetGlyphsPtr (run)) + { + if (glyphs == nullptr) + { + local.malloc (numGlyphs); + CTRunGetGlyphs (run, CFRangeMake (0, 0), local); + glyphs = local; + } + } + + const CGGlyph* glyphs; + HeapBlock local; + }; + + struct Positions + { + Positions (CTRunRef run, const size_t numGlyphs) + : points (CTRunGetPositionsPtr (run)) + { + if (points == nullptr) + { + local.malloc (numGlyphs); + CTRunGetPositions (run, CFRangeMake (0, 0), local); + points = local; + } + } + + const CGPoint* points; + HeapBlock local; + }; + + struct LineInfo + { + LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex) + { + CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin); + CTLineGetTypographicBounds (line, &ascent, &descent, &leading); + } + + CGPoint origin; + CGFloat ascent, descent, leading; + }; + + static CTFontRef getOrCreateFont (const Font& f) + { + if (CTFontRef ctf = getCTFontFromTypeface (f)) + { + CFRetain (ctf); + return ctf; + } + + CGAffineTransform transform; + return createCTFont (f, referenceFontSize, transform); + } + + //============================================================================== + static CFAttributedStringRef createCFAttributedString (const AttributedString& text) + { + #if JUCE_IOS + CGColorSpaceRef rgbColourSpace = CGColorSpaceCreateDeviceRGB(); + #endif + + CFStringRef cfText = text.getText().toCFString(); + CFMutableAttributedStringRef attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); + CFAttributedStringReplaceString (attribString, CFRangeMake(0, 0), cfText); + CFRelease (cfText); + + const int numCharacterAttributes = text.getNumAttributes(); + + for (int i = 0; i < numCharacterAttributes; ++i) + { + const AttributedString::Attribute& attr = *text.getAttribute (i); + + if (attr.range.getStart() > CFAttributedStringGetLength (attribString)) + continue; + + Range range (attr.range); + range.setEnd (jmin (range.getEnd(), (int) CFAttributedStringGetLength (attribString))); + + if (const Font* const f = attr.getFont()) + { + if (CTFontRef ctFontRef = getOrCreateFont (*f)) + { + ctFontRef = getFontWithPointSize (ctFontRef, f->getHeight() * getHeightToPointsFactor (ctFontRef)); + + CFAttributedStringSetAttribute (attribString, CFRangeMake (range.getStart(), range.getLength()), + kCTFontAttributeName, ctFontRef); + CFRelease (ctFontRef); + } + } + + if (const Colour* const col = attr.getColour()) + { + #if JUCE_IOS + const CGFloat components[] = { col->getFloatRed(), + col->getFloatGreen(), + col->getFloatBlue(), + col->getFloatAlpha() }; + CGColorRef colour = CGColorCreate (rgbColourSpace, components); + #else + CGColorRef colour = CGColorCreateGenericRGB (col->getFloatRed(), + col->getFloatGreen(), + col->getFloatBlue(), + col->getFloatAlpha()); + #endif + + CFAttributedStringSetAttribute (attribString, + CFRangeMake (range.getStart(), range.getLength()), + kCTForegroundColorAttributeName, colour); + CGColorRelease (colour); + } + } + + // Paragraph Attributes + CTTextAlignment ctTextAlignment = kCTLeftTextAlignment; + CTLineBreakMode ctLineBreakMode = kCTLineBreakByWordWrapping; + const CGFloat ctLineSpacing = text.getLineSpacing(); + + switch (text.getJustification().getOnlyHorizontalFlags()) + { + case Justification::left: break; + case Justification::right: ctTextAlignment = kCTRightTextAlignment; break; + case Justification::horizontallyCentred: ctTextAlignment = kCTCenterTextAlignment; break; + case Justification::horizontallyJustified: ctTextAlignment = kCTJustifiedTextAlignment; break; + default: jassertfalse; break; // Illegal justification flags + } + + switch (text.getWordWrap()) + { + case AttributedString::byWord: break; + case AttributedString::none: ctLineBreakMode = kCTLineBreakByClipping; break; + case AttributedString::byChar: ctLineBreakMode = kCTLineBreakByCharWrapping; break; + default: break; + } + + CTParagraphStyleSetting settings[] = + { + { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment }, + { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode }, + + #if defined (MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing } + #else + { kCTParagraphStyleSpecifierLineSpacing, sizeof (CGFloat), &ctLineSpacing } + #endif + }; + + CTParagraphStyleRef ctParagraphStyleRef = CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)); + CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), + kCTParagraphStyleAttributeName, ctParagraphStyleRef); + CFRelease (ctParagraphStyleRef); + #if JUCE_IOS + CGColorSpaceRelease (rgbColourSpace); + #endif + return attribString; + } + + static CTFrameRef createCTFrame (const AttributedString& text, CGRect bounds) + { + CFAttributedStringRef attribString = createCFAttributedString (text); + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString (attribString); + CFRelease (attribString); + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect (path, nullptr, bounds); + + CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr); + CFRelease (framesetter); + CGPathRelease (path); + + return frame; + } + + static Range getLineVerticalRange (CTFrameRef frame, CFArrayRef lines, int lineIndex) + { + LineInfo info (frame, (CTLineRef) CFArrayGetValueAtIndex (lines, lineIndex), lineIndex); + return Range ((float) (info.origin.y - info.descent), + (float) (info.origin.y + info.ascent)); + } + + static float findCTFrameHeight (CTFrameRef frame) + { + CFArrayRef lines = CTFrameGetLines (frame); + const CFIndex numLines = CFArrayGetCount (lines); + + if (numLines == 0) + return 0; + + Range range (getLineVerticalRange (frame, lines, 0)); + + if (numLines > 1) + range = range.getUnionWith (getLineVerticalRange (frame, lines, (int) numLines - 1)); + + return range.getLength(); + } + + static void drawToCGContext (const AttributedString& text, const Rectangle& area, + const CGContextRef& context, const float flipHeight) + { + CTFrameRef frame = createCTFrame (text, CGRectMake ((CGFloat) area.getX(), flipHeight - (CGFloat) area.getBottom(), + (CGFloat) area.getWidth(), (CGFloat) area.getHeight())); + + const int verticalJustification = text.getJustification().getOnlyVerticalFlags(); + + if (verticalJustification == Justification::verticallyCentred + || verticalJustification == Justification::bottom) + { + float adjust = area.getHeight() - findCTFrameHeight (frame); + + if (verticalJustification == Justification::verticallyCentred) + adjust *= 0.5f; + + CGContextSaveGState (context); + CGContextTranslateCTM (context, 0, -adjust); + CTFrameDraw (frame, context); + CGContextRestoreGState (context); + } + else + { + CTFrameDraw (frame, context); + } + + CFRelease (frame); + } + + static void createLayout (TextLayout& glyphLayout, const AttributedString& text) + { + const CGFloat boundsHeight = 1.0e6f; + CTFrameRef frame = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); + + CFArrayRef lines = CTFrameGetLines (frame); + const CFIndex numLines = CFArrayGetCount (lines); + + glyphLayout.ensureStorageAllocated ((int) numLines); + + for (CFIndex i = 0; i < numLines; ++i) + { + CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex (lines, i); + + CFArrayRef runs = CTLineGetGlyphRuns (line); + const CFIndex numRuns = CFArrayGetCount (runs); + + const CFRange cfrlineStringRange = CTLineGetStringRange (line); + const CFIndex lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1; + const Range lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); + + LineInfo lineInfo (frame, line, i); + + TextLayout::Line* const glyphLine = new TextLayout::Line (lineStringRange, + Point ((float) lineInfo.origin.x, + (float) (boundsHeight - lineInfo.origin.y)), + (float) lineInfo.ascent, + (float) lineInfo.descent, + (float) lineInfo.leading, + (int) numRuns); + glyphLayout.addLine (glyphLine); + + for (CFIndex j = 0; j < numRuns; ++j) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runs, j); + const CFIndex numGlyphs = CTRunGetGlyphCount (run); + const CFRange runStringRange = CTRunGetStringRange (run); + + TextLayout::Run* const glyphRun = new TextLayout::Run (Range ((int) runStringRange.location, + (int) (runStringRange.location + runStringRange.length - 1)), + (int) numGlyphs); + glyphLine->runs.add (glyphRun); + + CFDictionaryRef runAttributes = CTRunGetAttributes (run); + + CTFontRef ctRunFont; + if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void **) &ctRunFont)) + { + CFStringRef cfsFontName = CTFontCopyPostScriptName (ctRunFont); + CTFontRef ctFontRef = CTFontCreateWithName (cfsFontName, referenceFontSize, nullptr); + CFRelease (cfsFontName); + + const float fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef); + CFRelease (ctFontRef); + + CFStringRef cfsFontFamily = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute); + CFStringRef cfsFontStyle = (CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute); + + glyphRun->font = Font (String::fromCFString (cfsFontFamily), + String::fromCFString (cfsFontStyle), + (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); + + CFRelease (cfsFontStyle); + CFRelease (cfsFontFamily); + } + + CGColorRef cgRunColor; + if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor) + && CGColorGetNumberOfComponents (cgRunColor) == 4) + { + const CGFloat* const components = CGColorGetComponents (cgRunColor); + + glyphRun->colour = Colour::fromFloatRGBA ((float) components[0], + (float) components[1], + (float) components[2], + (float) components[3]); + } + + const Glyphs glyphs (run, (size_t) numGlyphs); + const Advances advances (run, numGlyphs); + const Positions positions (run, (size_t) numGlyphs); + + for (CFIndex k = 0; k < numGlyphs; ++k) + glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], Point ((float) positions.points[k].x, + (float) positions.points[k].y), + (float) advances.advances[k].width)); + } + } + + CFRelease (frame); + } +} + + +//============================================================================== +class OSXTypeface : public Typeface +{ +public: + OSXTypeface (const Font& font) + : Typeface (font.getTypefaceName(), + font.getTypefaceStyle()), + fontRef (nullptr), + ctFontRef (nullptr), + fontHeightToPointsFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + isMemoryFont (false), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + ctFontRef = CoreTextTypeLayout::createCTFont (font, referenceFontSize, renderingTransform); + + if (ctFontRef != nullptr) + { + fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); + initialiseMetrics(); + } + } + + OSXTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontRef (nullptr), + ctFontRef (nullptr), + fontHeightToPointsFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + isMemoryFont (true), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + CFDataRef cfData = CFDataCreate (kCFAllocatorDefault, (const UInt8*) data, (CFIndex) dataSize); + CGDataProviderRef provider = CGDataProviderCreateWithCFData (cfData); + CFRelease (cfData); + + fontRef = CGFontCreateWithDataProvider (provider); + CGDataProviderRelease (provider); + + if (fontRef != nullptr) + { + ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr); + + if (ctFontRef != nullptr) + { + if (CFStringRef fontName = CTFontCopyName (ctFontRef, kCTFontFamilyNameKey)) + { + name = String::fromCFString (fontName); + CFRelease (fontName); + } + + if (CFStringRef fontStyle = CTFontCopyName (ctFontRef, kCTFontStyleNameKey)) + { + style = String::fromCFString (fontStyle); + CFRelease (fontStyle); + } + + initialiseMetrics(); + } + } + } + + void initialiseMetrics() + { + const float ctAscent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float ctDescent = std::abs ((float) CTFontGetDescent (ctFontRef)); + const float ctTotalHeight = ctAscent + ctDescent; + + ascent = ctAscent / ctTotalHeight; + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); + + fontHeightToPointsFactor = referenceFontSize / ctTotalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + + ~OSXTypeface() + { + if (attributedStringAtts != nullptr) + CFRelease (attributedStringAtts); + + if (fontRef != nullptr) + CGFontRelease (fontRef); + + if (ctFontRef != nullptr) + CFRelease (ctFontRef); + } + + float getAscent() const override { return ascent; } + float getDescent() const override { return 1.0f - ascent; } + float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; } + + float getStringWidth (const String& text) override + { + float x = 0; + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + CFStringRef cfText = text.toCFString(); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + + const CoreTextTypeLayout::Advances advances (run, length); + + for (int j = 0; j < length; ++j) + x += (float) advances.advances[j].width; + } + + CFRelease (line); + CFRelease (attribString); + + x *= unitsToHeightScaleFactor; + } + + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) override + { + xOffsets.add (0); + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + float x = 0; + + CFStringRef cfText = text.toCFString(); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + + const CoreTextTypeLayout::Advances advances (run, length); + const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length); + + for (int j = 0; j < length; ++j) + { + x += (float) advances.advances[j].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphs.glyphs[j]); + } + } + + CFRelease (line); + CFRelease (attribString); + } + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) override + { + jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty + + CGPathRef pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform); + if (pathRef == 0) + return false; + + CGPathApply (pathRef, &path, pathApplier); + CFRelease (pathRef); + + if (! pathTransform.isIdentity()) + path.applyTransform (pathTransform); + + return true; + } + + //============================================================================== + CGFontRef fontRef; + CTFontRef ctFontRef; + + float fontHeightToPointsFactor; + CGAffineTransform renderingTransform; + + bool isMemoryFont; + +private: + CFDictionaryRef attributedStringAtts; + float ascent, unitsToHeightScaleFactor; + AffineTransform pathTransform; + + static void pathApplier (void* info, const CGPathElement* const element) + { + Path& path = *static_cast (info); + const CGPoint* const p = element->points; + + switch (element->type) + { + case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y); break; + case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y, + (float) p[2].x, (float) -p[2].y); break; + case kCGPathElementCloseSubpath: path.closeSubPath(); break; + default: jassertfalse; break; + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface) +}; + +CTFontRef getCTFontFromTypeface (const Font& f) +{ + if (OSXTypeface* tf = dynamic_cast (f.getTypeface())) + return tf->ctFontRef; + + return 0; +} + +StringArray Font::findAllTypefaceNames() +{ + StringArray names; + + #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5 && ! JUCE_IOS + // CTFontManager only exists on OS X 10.6 and later, it does not exist on iOS + CFArrayRef fontFamilyArray = CTFontManagerCopyAvailableFontFamilyNames(); + + for (CFIndex i = 0; i < CFArrayGetCount (fontFamilyArray); ++i) + { + const String family (String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (fontFamilyArray, i))); + + if (! family.startsWithChar ('.')) // ignore fonts that start with a '.' + names.addIfNotAlreadyThere (family); + } + + CFRelease (fontFamilyArray); + #else + CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateFromAvailableFonts (nullptr); + CFArrayRef fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); + CFRelease (fontCollectionRef); + + for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) + { + CTFontDescriptorRef ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); + CFStringRef cfsFontFamily = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontFamilyNameAttribute); + + names.addIfNotAlreadyThere (String::fromCFString (cfsFontFamily)); + + CFRelease (cfsFontFamily); + } + + CFRelease (fontDescriptorArray); + #endif + + names.sort (true); + return names; +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + if (FontStyleHelpers::isPlaceholderFamilyName (family)) + return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); + + StringArray results; + + CFStringRef cfsFontFamily = family.toCFString(); + CFStringRef keys[] = { kCTFontFamilyNameAttribute }; + CFTypeRef values[] = { cfsFontFamily }; + + CFDictionaryRef fontDescAttributes = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (cfsFontFamily); + + CTFontDescriptorRef ctFontDescRef = CTFontDescriptorCreateWithAttributes (fontDescAttributes); + CFRelease (fontDescAttributes); + + CFArrayRef fontFamilyArray = CFArrayCreate (kCFAllocatorDefault, (const void**) &ctFontDescRef, 1, &kCFTypeArrayCallBacks); + CFRelease (ctFontDescRef); + + CTFontCollectionRef fontCollectionRef = CTFontCollectionCreateWithFontDescriptors (fontFamilyArray, nullptr); + CFRelease (fontFamilyArray); + + CFArrayRef fontDescriptorArray = CTFontCollectionCreateMatchingFontDescriptors (fontCollectionRef); + CFRelease (fontCollectionRef); + + if (fontDescriptorArray != nullptr) + { + for (CFIndex i = 0; i < CFArrayGetCount (fontDescriptorArray); ++i) + { + CTFontDescriptorRef ctFontDescriptorRef = (CTFontDescriptorRef) CFArrayGetValueAtIndex (fontDescriptorArray, i); + CFStringRef cfsFontStyle = (CFStringRef) CTFontDescriptorCopyAttribute (ctFontDescriptorRef, kCTFontStyleNameAttribute); + results.add (String::fromCFString (cfsFontStyle)); + CFRelease (cfsFontStyle); + } + + CFRelease (fontDescriptorArray); + } + + return results; +} + +#else + +//============================================================================== +// The stuff that follows is a mash-up that supports pre-OSX 10.5 APIs. +// (Hopefully all of this can be ditched at some point in the future). + +//============================================================================== +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #define SUPPORT_10_4_FONTS 1 + #define NEW_CGFONT_FUNCTIONS_UNAVAILABLE (CGFontCreateWithFontName == 0) + + #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 + #define SUPPORT_ONLY_10_4_FONTS 1 + #endif + + } // (juce namespace) + + @interface NSFont (PrivateHack) + - (NSGlyph) _defaultGlyphForChar: (unichar) theChar; + @end + + namespace juce + { +#endif + +//============================================================================== +class OSXTypeface : public Typeface +{ +public: + OSXTypeface (const Font& font) + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()) + { + JUCE_AUTORELEASEPOOL + { + renderingTransform = CGAffineTransformIdentity; + + NSDictionary* nsDict = [NSDictionary dictionaryWithObjectsAndKeys: + juceStringToNS (name), NSFontFamilyAttribute, + juceStringToNS (style), NSFontFaceAttribute, nil]; + + NSFontDescriptor* nsFontDesc = [NSFontDescriptor fontDescriptorWithFontAttributes: nsDict]; + nsFont = [NSFont fontWithDescriptor: nsFontDesc size: referenceFontSize]; + + [nsFont retain]; + + #if SUPPORT_ONLY_10_4_FONTS + initWithATSFont(); + #else + #if SUPPORT_10_4_FONTS + if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) + { + initWithATSFont(); + } + else + #endif + { + fontRef = CGFontCreateWithFontName ((CFStringRef) [nsFont fontName]); + + const float absAscent = std::abs ((float) CGFontGetAscent (fontRef)); + const float totalHeight = absAscent + std::abs ((float) CGFontGetDescent (fontRef)); + + ascent = absAscent / totalHeight; + unitsToHeightScaleFactor = 1.0f / totalHeight; + + const float nsFontAscent = std::abs ([nsFont ascender]); + const float nsFontDescent = std::abs ([nsFont descender]); + + fontHeightToPointsFactor = referenceFontSize / (nsFontAscent + nsFontDescent); + } + #endif + + pathTransform = AffineTransform::identity.scale (unitsToHeightScaleFactor); + } + } + + ~OSXTypeface() + { + #if ! JUCE_IOS + [nsFont release]; + #endif + + if (fontRef != 0) + CGFontRelease (fontRef); + } + + #if SUPPORT_10_4_FONTS + void initWithATSFont() + { + ATSFontRef atsFont = ATSFontFindFromName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault); + + if (atsFont == 0) + atsFont = ATSFontFindFromPostScriptName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault); + + fontRef = CGFontCreateWithPlatformFont (&atsFont); + + const float absAscent = std::abs ([nsFont ascender]); + const float absDescent = std::abs ([nsFont descender]); + const float totalHeight = absAscent + absDescent; + + unitsToHeightScaleFactor = 1.0f / totalHeight; + fontHeightToPointsFactor = referenceFontSize / totalHeight; + ascent = absAscent / totalHeight; + } + #endif + + + float getAscent() const override { return ascent; } + float getDescent() const override { return 1.0f - ascent; } + float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; } + + float getStringWidth (const String& text) override + { + if (fontRef == 0 || text.isEmpty()) + return 0; + + const int length = text.length(); + HeapBlock glyphs; + createGlyphsForString (text.getCharPointer(), length, glyphs); + + float x = 0; + +#if SUPPORT_ONLY_10_4_FONTS + HeapBlock advances (length); + [nsFont getAdvancements: advances forGlyphs: reinterpret_cast (glyphs.getData()) count: length]; + + for (int i = 0; i < length; ++i) + x += advances[i].width; +#else + #if SUPPORT_10_4_FONTS + if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) + { + HeapBlock advances (length); + [nsFont getAdvancements: advances forGlyphs: reinterpret_cast (glyphs.getData()) count: length]; + + for (int i = 0; i < length; ++i) + x += advances[i].width; + } + else + #endif + { + HeapBlock advances (length); + + if (CGFontGetGlyphAdvances (fontRef, glyphs, length, advances)) + for (int i = 0; i < length; ++i) + x += advances[i]; + } +#endif + + return x * unitsToHeightScaleFactor; + } + + void getGlyphPositions (const String& text, Array& resultGlyphs, Array& xOffsets) override + { + xOffsets.add (0); + + if (fontRef == 0 || text.isEmpty()) + return; + + const int length = text.length(); + HeapBlock glyphs; + createGlyphsForString (text.getCharPointer(), length, glyphs); + +#if SUPPORT_ONLY_10_4_FONTS + HeapBlock advances (length); + [nsFont getAdvancements: advances forGlyphs: reinterpret_cast (glyphs.getData()) count: length]; + + int x = 0; + for (int i = 0; i < length; ++i) + { + x += advances[i].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (reinterpret_cast (glyphs.getData())[i]); + } + +#else + #if SUPPORT_10_4_FONTS + if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) + { + HeapBlock advances (length); + NSGlyph* const nsGlyphs = reinterpret_cast (glyphs.getData()); + [nsFont getAdvancements: advances forGlyphs: nsGlyphs count: length]; + + float x = 0; + for (int i = 0; i < length; ++i) + { + x += advances[i].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (nsGlyphs[i]); + } + } + else + #endif + { + HeapBlock advances (length); + + if (CGFontGetGlyphAdvances (fontRef, glyphs, length, advances)) + { + int x = 0; + for (int i = 0; i < length; ++i) + { + x += advances [i]; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphs[i]); + } + } + } +#endif + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) override + { + #if JUCE_IOS + return false; + #else + if (nsFont == nil) + return false; + + // we might need to apply a transform to the path, so it mustn't have anything else in it + jassert (path.isEmpty()); + + JUCE_AUTORELEASEPOOL + { + NSBezierPath* bez = [NSBezierPath bezierPath]; + [bez moveToPoint: NSMakePoint (0, 0)]; + [bez appendBezierPathWithGlyph: (NSGlyph) glyphNumber + inFont: nsFont]; + + for (int i = 0; i < [bez elementCount]; ++i) + { + NSPoint p[3]; + switch ([bez elementAtIndex: i associatedPoints: p]) + { + case NSMoveToBezierPathElement: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; + case NSLineToBezierPathElement: path.lineTo ((float) p[0].x, (float) -p[0].y); break; + case NSCurveToBezierPathElement: path.cubicTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y, + (float) p[2].x, (float) -p[2].y); break; + case NSClosePathBezierPathElement: path.closeSubPath(); break; + default: jassertfalse; break; + } + } + + path.applyTransform (pathTransform); + } + return true; + #endif + } + + //============================================================================== + CGFontRef fontRef; + float fontHeightToPointsFactor; + CGAffineTransform renderingTransform; + +private: + float ascent, unitsToHeightScaleFactor; + + #if ! JUCE_IOS + NSFont* nsFont; + AffineTransform pathTransform; + #endif + + void createGlyphsForString (String::CharPointerType text, const int length, HeapBlock & glyphs) + { + #if SUPPORT_10_4_FONTS + #if ! SUPPORT_ONLY_10_4_FONTS + if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) + #endif + { + glyphs.malloc (sizeof (NSGlyph) * length, 1); + NSGlyph* const nsGlyphs = reinterpret_cast (glyphs.getData()); + + for (int i = 0; i < length; ++i) + nsGlyphs[i] = (NSGlyph) [nsFont _defaultGlyphForChar: text.getAndAdvance()]; + + return; + } + #endif + + #if ! SUPPORT_ONLY_10_4_FONTS + if (charToGlyphMapper == nullptr) + charToGlyphMapper = new CharToGlyphMapper (fontRef); + + glyphs.malloc (length); + + for (int i = 0; i < length; ++i) + glyphs[i] = (CGGlyph) charToGlyphMapper->getGlyphForCharacter (text.getAndAdvance()); + #endif + } + + #if ! SUPPORT_ONLY_10_4_FONTS + // Reads a CGFontRef's character map table to convert unicode into glyph numbers + class CharToGlyphMapper + { + public: + CharToGlyphMapper (CGFontRef cgFontRef) + : segCount (0), endCode (0), startCode (0), idDelta (0), + idRangeOffset (0), glyphIndexes (0) + { + CFDataRef cmapTable = CGFontCopyTableForTag (cgFontRef, 'cmap'); + + if (cmapTable != 0) + { + const int numSubtables = getValue16 (cmapTable, 2); + + for (int i = 0; i < numSubtables; ++i) + { + if (getValue16 (cmapTable, i * 8 + 4) == 0) // check for platform ID of 0 + { + const int offset = getValue32 (cmapTable, i * 8 + 8); + + if (getValue16 (cmapTable, offset) == 4) // check that it's format 4.. + { + const int length = getValue16 (cmapTable, offset + 2); + const int segCountX2 = getValue16 (cmapTable, offset + 6); + segCount = segCountX2 / 2; + const int endCodeOffset = offset + 14; + const int startCodeOffset = endCodeOffset + 2 + segCountX2; + const int idDeltaOffset = startCodeOffset + segCountX2; + const int idRangeOffsetOffset = idDeltaOffset + segCountX2; + const int glyphIndexesOffset = idRangeOffsetOffset + segCountX2; + + endCode = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + endCodeOffset, segCountX2); + startCode = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + startCodeOffset, segCountX2); + idDelta = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + idDeltaOffset, segCountX2); + idRangeOffset = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + idRangeOffsetOffset, segCountX2); + glyphIndexes = CFDataCreate (kCFAllocatorDefault, CFDataGetBytePtr (cmapTable) + glyphIndexesOffset, offset + length - glyphIndexesOffset); + } + + break; + } + } + + CFRelease (cmapTable); + } + } + + ~CharToGlyphMapper() + { + if (endCode != 0) + { + CFRelease (endCode); + CFRelease (startCode); + CFRelease (idDelta); + CFRelease (idRangeOffset); + CFRelease (glyphIndexes); + } + } + + int getGlyphForCharacter (const juce_wchar c) const + { + for (int i = 0; i < segCount; ++i) + { + if (getValue16 (endCode, i * 2) >= c) + { + const int start = getValue16 (startCode, i * 2); + if (start > c) + break; + + const int delta = getValue16 (idDelta, i * 2); + const int rangeOffset = getValue16 (idRangeOffset, i * 2); + + if (rangeOffset == 0) + return delta + c; + + return getValue16 (glyphIndexes, 2 * ((rangeOffset / 2) + (c - start) - (segCount - i))); + } + } + + // If we failed to find it "properly", this dodgy fall-back seems to do the trick for most fonts! + return jmax (-1, (int) c - 29); + } + + private: + int segCount; + CFDataRef endCode, startCode, idDelta, idRangeOffset, glyphIndexes; + + static uint16 getValue16 (CFDataRef data, const int index) + { + return CFSwapInt16BigToHost (*(UInt16*) (CFDataGetBytePtr (data) + index)); + } + + static uint32 getValue32 (CFDataRef data, const int index) + { + return CFSwapInt32BigToHost (*(UInt32*) (CFDataGetBytePtr (data) + index)); + } + }; + + ScopedPointer charToGlyphMapper; + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXTypeface) +}; + +StringArray Font::findAllTypefaceNames() +{ + StringArray names; + + JUCE_AUTORELEASEPOOL + { + #if JUCE_IOS + for (NSString* name in [UIFont familyNames]) + #else + for (NSString* name in [[NSFontManager sharedFontManager] availableFontFamilies]) + #endif + names.add (nsStringToJuce (name)); + + names.sort (true); + } + + return names; +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + if (FontStyleHelpers::isPlaceholderFamilyName (family)) + return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); + + StringArray results; + + JUCE_AUTORELEASEPOOL + { + for (NSArray* style in [[NSFontManager sharedFontManager] availableMembersOfFontFamily: juceStringToNS (family)]) + results.add (nsStringToJuce ((NSString*) [style objectAtIndex: 1])); + } + + return results; +} + +#endif + +//============================================================================== +Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) +{ + return new OSXTypeface (font); +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + #if JUCE_CORETEXT_AVAILABLE + return new OSXTypeface (data, dataSize); + #else + jassertfalse; // You need CoreText enabled to use this feature! + return nullptr; + #endif +} + +void Typeface::scanFolderForFonts (const File&) +{ + jassertfalse; // not implemented on this platform +} + +struct DefaultFontNames +{ + DefaultFontNames() + #if JUCE_IOS + : defaultSans ("Helvetica"), + defaultSerif ("Times New Roman"), + defaultFixed ("Courier New"), + #else + : defaultSans ("Lucida Grande"), + defaultSerif ("Times New Roman"), + defaultFixed ("Menlo"), + #endif + defaultFallback ("Arial Unicode MS") + { + } + + String defaultSans, defaultSerif, defaultFixed, defaultFallback; +}; + +Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +{ + static DefaultFontNames defaultNames; + + Font newFont (font); + const String& faceName = font.getTypefaceName(); + + if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans); + else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif); + else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed); + + if (font.getTypefaceStyle() == getDefaultStyle()) + newFont.setTypefaceStyle ("Regular"); + + return Typeface::createSystemTypefaceFor (newFont); +} + +#if JUCE_CORETEXT_AVAILABLE +static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) +{ + const int numCharacterAttributes = text.getNumAttributes(); + + for (int i = 0; i < numCharacterAttributes; ++i) + { + if (const Font* const f = text.getAttribute (i)->getFont()) + { + if (OSXTypeface* tf = dynamic_cast (f->getTypeface())) + { + if (tf->isMemoryFont) + return false; + } + else if (dynamic_cast (f->getTypeface()) != nullptr) + { + return false; + } + } + } + + return true; +} +#endif + +bool TextLayout::createNativeLayout (const AttributedString& text) +{ + #if JUCE_CORETEXT_AVAILABLE + // Seems to be an unfathomable bug in CoreText which prevents the layout working with + // typefaces that were loaded from memory, so have to fallback if we hit any of those.. + if (canAllTypefacesBeUsedInLayout (text)) + { + CoreTextTypeLayout::createLayout (*this, text); + return true; + } + #endif + + (void) text; + return false; +} diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp new file mode 100644 index 0000000..e4356b7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp @@ -0,0 +1,838 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class Direct2DLowLevelGraphicsContext : public LowLevelGraphicsContext +{ +public: + Direct2DLowLevelGraphicsContext (HWND hwnd_) + : hwnd (hwnd_), + currentState (nullptr) + { + RECT windowRect; + GetClientRect (hwnd, &windowRect); + D2D1_SIZE_U size = { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; + bounds.setSize (size.width, size.height); + + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(); + D2D1_HWND_RENDER_TARGET_PROPERTIES propsHwnd = D2D1::HwndRenderTargetProperties (hwnd, size); + + if (factories->d2dFactory != nullptr) + { + HRESULT hr = factories->d2dFactory->CreateHwndRenderTarget (props, propsHwnd, renderingTarget.resetAndGetPointerAddress()); + jassert (SUCCEEDED (hr)); (void) hr; + hr = renderingTarget->CreateSolidColorBrush (D2D1::ColorF::ColorF (0.0f, 0.0f, 0.0f, 1.0f), colourBrush.resetAndGetPointerAddress()); + } + } + + ~Direct2DLowLevelGraphicsContext() + { + states.clear(); + } + + void resized() + { + RECT windowRect; + GetClientRect (hwnd, &windowRect); + D2D1_SIZE_U size = { windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; + + renderingTarget->Resize (size); + bounds.setSize (size.width, size.height); + } + + void clear() + { + renderingTarget->Clear (D2D1::ColorF (D2D1::ColorF::White, 0.0f)); // xxx why white and not black? + } + + void start() + { + renderingTarget->BeginDraw(); + saveState(); + } + + void end() + { + states.clear(); + currentState = 0; + renderingTarget->EndDraw(); + renderingTarget->CheckWindowState(); + } + + bool isVectorDevice() const { return false; } + + void setOrigin (Point o) + { + addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); + } + + void addTransform (const AffineTransform& transform) + { + currentState->transform = transform.followedBy (currentState->transform); + } + + float getPhysicalPixelScaleFactor() + { + return currentState->transform.getScaleFactor(); + } + + bool clipToRectangle (const Rectangle& r) + { + currentState->clipToRectangle (r); + return ! isClipEmpty(); + } + + bool clipToRectangleList (const RectangleList& clipRegion) + { + currentState->clipToRectList (rectListToPathGeometry (clipRegion)); + return ! isClipEmpty(); + } + + void excludeClipRectangle (const Rectangle&) + { + //xxx + } + + void clipToPath (const Path& path, const AffineTransform& transform) + { + currentState->clipToPath (pathToPathGeometry (path, transform)); + } + + void clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) + { + currentState->clipToImage (sourceImage, transform); + } + + bool clipRegionIntersects (const Rectangle& r) + { + return currentState->clipRect.intersects (r.toFloat().transformed (currentState->transform).getSmallestIntegerContainer()); + } + + Rectangle getClipBounds() const + { + // xxx could this take into account complex clip regions? + return currentState->clipRect.toFloat().transformed (currentState->transform.inverted()).getSmallestIntegerContainer(); + } + + bool isClipEmpty() const + { + return currentState->clipRect.isEmpty(); + } + + void saveState() + { + states.add (new SavedState (*this)); + currentState = states.getLast(); + } + + void restoreState() + { + jassert (states.size() > 1) //you should never pop the last state! + states.removeLast (1); + currentState = states.getLast(); + } + + void beginTransparencyLayer (float /*opacity*/) + { + jassertfalse; //xxx todo + } + + void endTransparencyLayer() + { + jassertfalse; //xxx todo + } + + void setFill (const FillType& fillType) + { + currentState->setFill (fillType); + } + + void setOpacity (float newOpacity) + { + currentState->setOpacity (newOpacity); + } + + void setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) + { + } + + void fillRect (const Rectangle& r, bool /*replaceExistingContents*/) + { + fillRect (r.toFloat()); + } + + void fillRect (const Rectangle& r) + { + renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + currentState->createBrush(); + renderingTarget->FillRectangle (rectangleToRectF (r), currentState->currentBrush); + renderingTarget->SetTransform (D2D1::IdentityMatrix()); + } + + void fillRectList (const RectangleList& list) + { + for (const Rectangle* r = list.begin(), * const e = list.end(); r != e; ++r) + fillRect (*r); + } + + void fillPath (const Path& p, const AffineTransform& transform) + { + currentState->createBrush(); + ComSmartPtr geometry (pathToPathGeometry (p, transform.followedBy (currentState->transform))); + + if (renderingTarget != nullptr) + renderingTarget->FillGeometry (geometry, currentState->currentBrush); + } + + void drawImage (const Image& image, const AffineTransform& transform) + { + renderingTarget->SetTransform (transformToMatrix (transform.followedBy (currentState->transform))); + + D2D1_SIZE_U size; + size.width = image.getWidth(); + size.height = image.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + Image img (image.convertedToFormat (Image::ARGB)); + Image::BitmapData bd (img, Image::BitmapData::readOnly); + bp.pixelFormat = renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + { + ComSmartPtr tempBitmap; + renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, tempBitmap.resetAndGetPointerAddress()); + if (tempBitmap != nullptr) + renderingTarget->DrawBitmap (tempBitmap); + } + + renderingTarget->SetTransform (D2D1::IdentityMatrix()); + } + + void drawLine (const Line & line) + { + // xxx doesn't seem to be correctly aligned, may need nudging by 0.5 to match the software renderer's behaviour + renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + currentState->createBrush(); + + renderingTarget->DrawLine (D2D1::Point2F (line.getStartX(), line.getStartY()), + D2D1::Point2F (line.getEndX(), line.getEndY()), + currentState->currentBrush); + renderingTarget->SetTransform (D2D1::IdentityMatrix()); + } + + void setFont (const Font& newFont) + { + currentState->setFont (newFont); + } + + const Font& getFont() + { + return currentState->font; + } + + void drawGlyph (int glyphNumber, const AffineTransform& transform) + { + currentState->createBrush(); + currentState->createFont(); + + float hScale = currentState->font.getHorizontalScale(); + + renderingTarget->SetTransform (transformToMatrix (AffineTransform::scale (hScale, 1.0f) + .followedBy (transform) + .followedBy (currentState->transform))); + + const UINT16 glyphIndices = (UINT16) glyphNumber; + const FLOAT glyphAdvances = 0; + DWRITE_GLYPH_OFFSET offset; + offset.advanceOffset = 0; + offset.ascenderOffset = 0; + + DWRITE_GLYPH_RUN glyphRun; + glyphRun.fontFace = currentState->currentFontFace; + glyphRun.fontEmSize = (FLOAT) (currentState->font.getHeight() * currentState->fontHeightToEmSizeFactor); + glyphRun.glyphCount = 1; + glyphRun.glyphIndices = &glyphIndices; + glyphRun.glyphAdvances = &glyphAdvances; + glyphRun.glyphOffsets = &offset; + glyphRun.isSideways = FALSE; + glyphRun.bidiLevel = 0; + + renderingTarget->DrawGlyphRun (D2D1::Point2F (0, 0), &glyphRun, currentState->currentBrush); + renderingTarget->SetTransform (D2D1::IdentityMatrix()); + } + + bool drawTextLayout (const AttributedString& text, const Rectangle& area) + { + renderingTarget->SetTransform (transformToMatrix (currentState->transform)); + + DirectWriteTypeLayout::drawToD2DContext (text, area, renderingTarget, factories->directWriteFactory, + factories->d2dFactory, factories->systemFonts); + + renderingTarget->SetTransform (D2D1::IdentityMatrix()); + return true; + } + + //============================================================================== + class SavedState + { + public: + SavedState (Direct2DLowLevelGraphicsContext& owner_) + : owner (owner_), currentBrush (0), + fontHeightToEmSizeFactor (1.0f), currentFontFace (0), + clipsRect (false), shouldClipRect (false), + clipsRectList (false), shouldClipRectList (false), + clipsComplex (false), shouldClipComplex (false), + clipsBitmap (false), shouldClipBitmap (false) + { + if (owner.currentState != nullptr) + { + // xxx seems like a very slow way to create one of these, and this is a performance + // bottleneck.. Can the same internal objects be shared by multiple state objects, maybe using copy-on-write? + setFill (owner.currentState->fillType); + currentBrush = owner.currentState->currentBrush; + clipRect = owner.currentState->clipRect; + transform = owner.currentState->transform; + + font = owner.currentState->font; + currentFontFace = owner.currentState->currentFontFace; + } + else + { + const D2D1_SIZE_U size (owner.renderingTarget->GetPixelSize()); + clipRect.setSize (size.width, size.height); + setFill (FillType (Colours::black)); + } + } + + ~SavedState() + { + clearClip(); + clearFont(); + clearFill(); + clearPathClip(); + clearImageClip(); + complexClipLayer = 0; + bitmapMaskLayer = 0; + } + + void clearClip() + { + popClips(); + shouldClipRect = false; + } + + void clipToRectangle (const Rectangle& r) + { + clearClip(); + clipRect = r.toFloat().transformed (transform).getSmallestIntegerContainer(); + shouldClipRect = true; + pushClips(); + } + + void clearPathClip() + { + popClips(); + + if (shouldClipComplex) + { + complexClipGeometry = 0; + shouldClipComplex = false; + } + } + + void clipToPath (ID2D1Geometry* geometry) + { + clearPathClip(); + + if (complexClipLayer == 0) + owner.renderingTarget->CreateLayer (complexClipLayer.resetAndGetPointerAddress()); + + complexClipGeometry = geometry; + shouldClipComplex = true; + pushClips(); + } + + void clearRectListClip() + { + popClips(); + + if (shouldClipRectList) + { + rectListGeometry = 0; + shouldClipRectList = false; + } + } + + void clipToRectList (ID2D1Geometry* geometry) + { + clearRectListClip(); + + if (rectListLayer == 0) + owner.renderingTarget->CreateLayer (rectListLayer.resetAndGetPointerAddress()); + + rectListGeometry = geometry; + shouldClipRectList = true; + pushClips(); + } + + void clearImageClip() + { + popClips(); + + if (shouldClipBitmap) + { + maskBitmap = 0; + bitmapMaskBrush = 0; + shouldClipBitmap = false; + } + } + + void clipToImage (const Image& image, const AffineTransform& transform) + { + clearImageClip(); + + if (bitmapMaskLayer == 0) + owner.renderingTarget->CreateLayer (bitmapMaskLayer.resetAndGetPointerAddress()); + + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = 1; + brushProps.transform = transformToMatrix (transform); + + D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP); + + D2D1_SIZE_U size; + size.width = image.getWidth(); + size.height = image.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + maskImage = image.convertedToFormat (Image::ARGB); + Image::BitmapData bd (this->image, Image::BitmapData::readOnly); // xxx should be maskImage? + bp.pixelFormat = owner.renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, maskBitmap.resetAndGetPointerAddress()); + hr = owner.renderingTarget->CreateBitmapBrush (maskBitmap, bmProps, brushProps, bitmapMaskBrush.resetAndGetPointerAddress()); + + imageMaskLayerParams = D2D1::LayerParameters(); + imageMaskLayerParams.opacityBrush = bitmapMaskBrush; + + shouldClipBitmap = true; + pushClips(); + } + + void popClips() + { + if (clipsBitmap) + { + owner.renderingTarget->PopLayer(); + clipsBitmap = false; + } + + if (clipsComplex) + { + owner.renderingTarget->PopLayer(); + clipsComplex = false; + } + + if (clipsRectList) + { + owner.renderingTarget->PopLayer(); + clipsRectList = false; + } + + if (clipsRect) + { + owner.renderingTarget->PopAxisAlignedClip(); + clipsRect = false; + } + } + + void pushClips() + { + if (shouldClipRect && ! clipsRect) + { + owner.renderingTarget->PushAxisAlignedClip (rectangleToRectF (clipRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + clipsRect = true; + } + + if (shouldClipRectList && ! clipsRectList) + { + D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); + rectListGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); + layerParams.geometricMask = rectListGeometry; + owner.renderingTarget->PushLayer (layerParams, rectListLayer); + clipsRectList = true; + } + + if (shouldClipComplex && ! clipsComplex) + { + D2D1_LAYER_PARAMETERS layerParams = D2D1::LayerParameters(); + complexClipGeometry->GetBounds (D2D1::IdentityMatrix(), &layerParams.contentBounds); + layerParams.geometricMask = complexClipGeometry; + owner.renderingTarget->PushLayer (layerParams, complexClipLayer); + clipsComplex = true; + } + + if (shouldClipBitmap && ! clipsBitmap) + { + owner.renderingTarget->PushLayer (imageMaskLayerParams, bitmapMaskLayer); + clipsBitmap = true; + } + } + + void setFill (const FillType& newFillType) + { + if (fillType != newFillType) + { + fillType = newFillType; + clearFill(); + } + } + + void clearFont() + { + currentFontFace = localFontFace = 0; + } + + void setFont (const Font& newFont) + { + if (font != newFont) + { + font = newFont; + clearFont(); + } + } + + void createFont() + { + if (currentFontFace == nullptr) + { + WindowsDirectWriteTypeface* typeface = dynamic_cast (font.getTypeface()); + currentFontFace = typeface->getIDWriteFontFace(); + fontHeightToEmSizeFactor = typeface->unitsToHeightScaleFactor(); + } + } + + void setOpacity (float newOpacity) + { + fillType.setOpacity (newOpacity); + + if (currentBrush != nullptr) + currentBrush->SetOpacity (newOpacity); + } + + void clearFill() + { + gradientStops = 0; + linearGradient = 0; + radialGradient = 0; + bitmap = 0; + bitmapBrush = 0; + currentBrush = 0; + } + + void createBrush() + { + if (currentBrush == 0) + { + if (fillType.isColour()) + { + D2D1_COLOR_F colour = colourToD2D (fillType.colour); + owner.colourBrush->SetColor (colour); + currentBrush = owner.colourBrush; + } + else if (fillType.isTiledImage()) + { + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = fillType.getOpacity(); + brushProps.transform = transformToMatrix (fillType.transform); + + D2D1_BITMAP_BRUSH_PROPERTIES bmProps = D2D1::BitmapBrushProperties (D2D1_EXTEND_MODE_WRAP,D2D1_EXTEND_MODE_WRAP); + + image = fillType.image; + + D2D1_SIZE_U size; + size.width = image.getWidth(); + size.height = image.getHeight(); + + D2D1_BITMAP_PROPERTIES bp = D2D1::BitmapProperties(); + + this->image = image.convertedToFormat (Image::ARGB); + Image::BitmapData bd (this->image, Image::BitmapData::readOnly); + bp.pixelFormat = owner.renderingTarget->GetPixelFormat(); + bp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + + HRESULT hr = owner.renderingTarget->CreateBitmap (size, bd.data, bd.lineStride, bp, bitmap.resetAndGetPointerAddress()); + hr = owner.renderingTarget->CreateBitmapBrush (bitmap, bmProps, brushProps, bitmapBrush.resetAndGetPointerAddress()); + + currentBrush = bitmapBrush; + } + else if (fillType.isGradient()) + { + gradientStops = 0; + + D2D1_BRUSH_PROPERTIES brushProps; + brushProps.opacity = fillType.getOpacity(); + brushProps.transform = transformToMatrix (fillType.transform.followedBy (transform)); + + const int numColors = fillType.gradient->getNumColours(); + + HeapBlock stops (numColors); + + for (int i = fillType.gradient->getNumColours(); --i >= 0;) + { + stops[i].color = colourToD2D (fillType.gradient->getColour(i)); + stops[i].position = (FLOAT) fillType.gradient->getColourPosition(i); + } + + owner.renderingTarget->CreateGradientStopCollection (stops.getData(), numColors, gradientStops.resetAndGetPointerAddress()); + + if (fillType.gradient->isRadial) + { + radialGradient = 0; + + const Point p1 = fillType.gradient->point1; + const Point p2 = fillType.gradient->point2; + float r = p1.getDistanceFrom (p2); + + D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = + D2D1::RadialGradientBrushProperties (D2D1::Point2F (p1.x, p1.y), + D2D1::Point2F (0, 0), + r, r); + + owner.renderingTarget->CreateRadialGradientBrush (props, brushProps, gradientStops, radialGradient.resetAndGetPointerAddress()); + currentBrush = radialGradient; + } + else + { + linearGradient = 0; + + const Point p1 = fillType.gradient->point1; + const Point p2 = fillType.gradient->point2; + + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = + D2D1::LinearGradientBrushProperties (D2D1::Point2F (p1.x, p1.y), + D2D1::Point2F (p2.x, p2.y)); + + owner.renderingTarget->CreateLinearGradientBrush (props, brushProps, gradientStops, linearGradient.resetAndGetPointerAddress()); + + currentBrush = linearGradient; + } + } + } + } + + //============================================================================== + //xxx most of these members should probably be private... + + Direct2DLowLevelGraphicsContext& owner; + + AffineTransform transform; + + Font font; + float fontHeightToEmSizeFactor; + IDWriteFontFace* currentFontFace; + ComSmartPtr localFontFace; + + FillType fillType; + + Image image; + ComSmartPtr bitmap; // xxx needs a better name - what is this for?? + + Rectangle clipRect; + bool clipsRect, shouldClipRect; + + ComSmartPtr complexClipGeometry; + D2D1_LAYER_PARAMETERS complexClipLayerParams; + ComSmartPtr complexClipLayer; + bool clipsComplex, shouldClipComplex; + + ComSmartPtr rectListGeometry; + D2D1_LAYER_PARAMETERS rectListLayerParams; + ComSmartPtr rectListLayer; + bool clipsRectList, shouldClipRectList; + + Image maskImage; + D2D1_LAYER_PARAMETERS imageMaskLayerParams; + ComSmartPtr bitmapMaskLayer; + ComSmartPtr maskBitmap; + ComSmartPtr bitmapMaskBrush; + bool clipsBitmap, shouldClipBitmap; + + ID2D1Brush* currentBrush; + ComSmartPtr bitmapBrush; + ComSmartPtr linearGradient; + ComSmartPtr radialGradient; + ComSmartPtr gradientStops; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedState) + }; + + //============================================================================== +private: + SharedResourcePointer factories; + HWND hwnd; + ComSmartPtr renderingTarget; + ComSmartPtr colourBrush; + Rectangle bounds; + + SavedState* currentState; + OwnedArray states; + + //============================================================================== + template + static D2D1_RECT_F rectangleToRectF (const Rectangle& r) + { + return D2D1::RectF ((float) r.getX(), (float) r.getY(), (float) r.getRight(), (float) r.getBottom()); + } + + static D2D1_COLOR_F colourToD2D (Colour c) + { + return D2D1::ColorF::ColorF (c.getFloatRed(), c.getFloatGreen(), c.getFloatBlue(), c.getFloatAlpha()); + } + + static D2D1_POINT_2F pointTransformed (int x, int y, const AffineTransform& transform) + { + transform.transformPoint (x, y); + return D2D1::Point2F ((FLOAT) x, (FLOAT) y); + } + + static void rectToGeometrySink (const Rectangle& rect, ID2D1GeometrySink* sink) + { + sink->BeginFigure (pointTransformed (rect.getX(), rect.getY()), D2D1_FIGURE_BEGIN_FILLED); + sink->AddLine (pointTransformed (rect.getRight(), rect.getY())); + sink->AddLine (pointTransformed (rect.getRight(), rect.getBottom())); + sink->AddLine (pointTransformed (rect.getX(), rect.getBottom())); + sink->EndFigure (D2D1_FIGURE_END_CLOSED); + } + + static ID2D1PathGeometry* rectListToPathGeometry (const RectangleList& clipRegion) + { + ID2D1PathGeometry* p = nullptr; + factories->d2dFactory->CreatePathGeometry (&p); + + ComSmartPtr sink; + HRESULT hr = p->Open (sink.resetAndGetPointerAddress()); // xxx handle error + sink->SetFillMode (D2D1_FILL_MODE_WINDING); + + for (int i = clipRegion.getNumRectangles(); --i >= 0;) + rectToGeometrySink (clipRegion.getRectangle(i), sink); + + hr = sink->Close(); + return p; + } + + static void pathToGeometrySink (const Path& path, ID2D1GeometrySink* sink, const AffineTransform& transform) + { + Path::Iterator it (path); + + while (it.next()) + { + switch (it.elementType) + { + case Path::Iterator::cubicTo: + { + D2D1_BEZIER_SEGMENT seg; + + transform.transformPoint (it.x1, it.y1); + seg.point1 = D2D1::Point2F (it.x1, it.y1); + + transform.transformPoint (it.x2, it.y2); + seg.point2 = D2D1::Point2F (it.x2, it.y2); + + transform.transformPoint(it.x3, it.y3); + seg.point3 = D2D1::Point2F (it.x3, it.y3); + + sink->AddBezier (seg); + break; + } + + case Path::Iterator::lineTo: + { + transform.transformPoint (it.x1, it.y1); + sink->AddLine (D2D1::Point2F (it.x1, it.y1)); + break; + } + + case Path::Iterator::quadraticTo: + { + D2D1_QUADRATIC_BEZIER_SEGMENT seg; + + transform.transformPoint (it.x1, it.y1); + seg.point1 = D2D1::Point2F (it.x1, it.y1); + + transform.transformPoint (it.x2, it.y2); + seg.point2 = D2D1::Point2F (it.x2, it.y2); + + sink->AddQuadraticBezier (seg); + break; + } + + case Path::Iterator::closePath: + { + sink->EndFigure (D2D1_FIGURE_END_CLOSED); + break; + } + + case Path::Iterator::startNewSubPath: + { + transform.transformPoint (it.x1, it.y1); + sink->BeginFigure (D2D1::Point2F (it.x1, it.y1), D2D1_FIGURE_BEGIN_FILLED); + break; + } + } + } + } + + static ID2D1PathGeometry* pathToPathGeometry (const Path& path, const AffineTransform& transform) + { + ID2D1PathGeometry* p = nullptr; + factories->d2dFactory->CreatePathGeometry (&p); + + ComSmartPtr sink; + HRESULT hr = p->Open (sink.resetAndGetPointerAddress()); + sink->SetFillMode (D2D1_FILL_MODE_WINDING); // xxx need to check Path::isUsingNonZeroWinding() + + pathToGeometrySink (path, sink, transform); + + hr = sink->Close(); + return p; + } + + static D2D1::Matrix3x2F transformToMatrix (const AffineTransform& transform) + { + D2D1::Matrix3x2F matrix; + matrix._11 = transform.mat00; + matrix._12 = transform.mat10; + matrix._21 = transform.mat01; + matrix._22 = transform.mat11; + matrix._31 = transform.mat02; + matrix._32 = transform.mat12; + return matrix; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DLowLevelGraphicsContext) +}; diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp new file mode 100644 index 0000000..83e3c9d --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp @@ -0,0 +1,436 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +//================================================================================================== +#if JUCE_USE_DIRECTWRITE +namespace DirectWriteTypeLayout +{ + class CustomDirectWriteTextRenderer : public ComBaseClassHelper + { + public: + CustomDirectWriteTextRenderer (IDWriteFontCollection* const fonts, const AttributedString& as) + : ComBaseClassHelper (0), + attributedString (as), + fontCollection (fonts), + currentLine (-1), + lastOriginY (-10000.0f) + { + } + + JUCE_COMRESULT QueryInterface (REFIID refId, void** result) + { + if (refId == __uuidof (IDWritePixelSnapping)) + return castToType (result); + + return ComBaseClassHelper::QueryInterface (refId, result); + } + + JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled) + { + *isDisabled = FALSE; + return S_OK; + } + + JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX*) { return S_OK; } + JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT*) { return S_OK; } + JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) { return S_OK; } + JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) { return S_OK; } + JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) { return E_NOTIMPL; } + + JUCE_COMRESULT DrawGlyphRun (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE, + DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription, + IUnknown* clientDrawingEffect) + { + TextLayout* const layout = static_cast (clientDrawingContext); + + if (! (baselineOriginY >= -1.0e10f && baselineOriginY <= 1.0e10f)) + baselineOriginY = 0; // DirectWrite sometimes sends NaNs in this parameter + + if (baselineOriginY != lastOriginY) + { + lastOriginY = baselineOriginY; + ++currentLine; + + if (currentLine >= layout->getNumLines()) + { + jassert (currentLine == layout->getNumLines()); + TextLayout::Line* const newLine = new TextLayout::Line(); + layout->addLine (newLine); + + newLine->lineOrigin = Point (baselineOriginX, baselineOriginY); + } + } + + TextLayout::Line& glyphLine = layout->getLine (currentLine); + + DWRITE_FONT_METRICS dwFontMetrics; + glyphRun->fontFace->GetMetrics (&dwFontMetrics); + + glyphLine.ascent = jmax (glyphLine.ascent, scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, glyphRun)); + glyphLine.descent = jmax (glyphLine.descent, scaledFontSize (dwFontMetrics.descent, dwFontMetrics, glyphRun)); + + TextLayout::Run* const glyphRunLayout = new TextLayout::Run (Range (runDescription->textPosition, + runDescription->textPosition + runDescription->stringLength), + glyphRun->glyphCount); + glyphLine.runs.add (glyphRunLayout); + + glyphRun->fontFace->GetMetrics (&dwFontMetrics); + const float totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent); + const float fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight; + + glyphRunLayout->font = getFontForRun (glyphRun, glyphRun->fontEmSize / fontHeightToEmSizeFactor); + glyphRunLayout->colour = getColourOf (static_cast (clientDrawingEffect)); + + const Point lineOrigin (layout->getLine (currentLine).lineOrigin); + float x = baselineOriginX - lineOrigin.x; + + for (UINT32 i = 0; i < glyphRun->glyphCount; ++i) + { + const float advance = glyphRun->glyphAdvances[i]; + + if ((glyphRun->bidiLevel & 1) != 0) + x -= advance; // RTL text + + glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i], + Point (x, baselineOriginY - lineOrigin.y), + advance)); + + if ((glyphRun->bidiLevel & 1) == 0) + x += advance; // LTR text + } + + return S_OK; + } + + private: + const AttributedString& attributedString; + IDWriteFontCollection* const fontCollection; + int currentLine; + float lastOriginY; + + static float scaledFontSize (int n, const DWRITE_FONT_METRICS& metrics, const DWRITE_GLYPH_RUN* glyphRun) noexcept + { + return (std::abs ((float) n) / (float) metrics.designUnitsPerEm) * glyphRun->fontEmSize; + } + + static Colour getColourOf (ID2D1SolidColorBrush* d2dBrush) + { + if (d2dBrush == nullptr) + return Colours::black; + + const D2D1_COLOR_F colour (d2dBrush->GetColor()); + return Colour::fromFloatRGBA (colour.r, colour.g, colour.b, colour.a); + } + + Font getFontForRun (DWRITE_GLYPH_RUN const* glyphRun, float fontHeight) + { + for (int i = 0; i < attributedString.getNumAttributes(); ++i) + if (const Font* font = attributedString.getAttribute(i)->getFont()) + if (WindowsDirectWriteTypeface* wt = dynamic_cast (font->getTypeface())) + if (wt->getIDWriteFontFace() == glyphRun->fontFace) + return font->withHeight (fontHeight); + + ComSmartPtr dwFont; + HRESULT hr = fontCollection->GetFontFromFontFace (glyphRun->fontFace, dwFont.resetAndGetPointerAddress()); + jassert (dwFont != nullptr); + + ComSmartPtr dwFontFamily; + hr = dwFont->GetFontFamily (dwFontFamily.resetAndGetPointerAddress()); + + return Font (getFontFamilyName (dwFontFamily), getFontFaceName (dwFont), fontHeight); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer) + }; + + //================================================================================================== + static float getFontHeightToEmSizeFactor (IDWriteFont* const dwFont) + { + ComSmartPtr dwFontFace; + dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress()); + + if (dwFontFace == nullptr) + return 1.0f; + + DWRITE_FONT_METRICS dwFontMetrics; + dwFontFace->GetMetrics (&dwFontMetrics); + + const float totalHeight = (float) (std::abs (dwFontMetrics.ascent) + std::abs (dwFontMetrics.descent)); + return dwFontMetrics.designUnitsPerEm / totalHeight; + } + + void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat* const format) + { + DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING; + DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP; + + switch (text.getJustification().getOnlyHorizontalFlags()) + { + case Justification::left: break; + case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; + case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break; + case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment + default: jassertfalse; break; // Illegal justification flags + } + + switch (text.getWordWrap()) + { + case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break; + case AttributedString::byWord: break; + case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap + default: jassertfalse; break; // Illegal flags! + } + + // DirectWrite does not automatically set reading direction + // This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic) + if (text.getReadingDirection() == AttributedString::rightToLeft) + { + format->SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); + + switch (text.getJustification().getOnlyHorizontalFlags()) + { + case Justification::left: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; + case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_LEADING; break; + default: break; + } + } + + format->SetTextAlignment (alignment); + format->SetWordWrapping (wrapType); + } + + void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout* textLayout, + const int textLen, ID2D1RenderTarget* const renderTarget, IDWriteFontCollection* const fontCollection) + { + DWRITE_TEXT_RANGE range; + range.startPosition = attr.range.getStart(); + range.length = jmin (attr.range.getLength(), textLen - attr.range.getStart()); + + if (const Font* const font = attr.getFont()) + { + const String familyName (FontStyleHelpers::getConcreteFamilyName (*font)); + + BOOL fontFound = false; + uint32 fontIndex; + fontCollection->FindFamilyName (familyName.toWideCharPointer(), + &fontIndex, &fontFound); + + if (! fontFound) + fontIndex = 0; + + ComSmartPtr fontFamily; + HRESULT hr = fontCollection->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress()); + + ComSmartPtr dwFont; + uint32 fontFacesCount = 0; + fontFacesCount = fontFamily->GetFontCount(); + + for (int i = fontFacesCount; --i >= 0;) + { + hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress()); + + if (font->getTypefaceStyle() == getFontFaceName (dwFont)) + break; + } + + textLayout->SetFontFamilyName (familyName.toWideCharPointer(), range); + textLayout->SetFontWeight (dwFont->GetWeight(), range); + textLayout->SetFontStretch (dwFont->GetStretch(), range); + textLayout->SetFontStyle (dwFont->GetStyle(), range); + + const float fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont); + textLayout->SetFontSize (font->getHeight() * fontHeightToEmSizeFactor, range); + } + + if (const Colour* const colour = attr.getColour()) + { + ComSmartPtr d2dBrush; + renderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF (colour->getFloatRed(), + colour->getFloatGreen(), + colour->getFloatBlue(), + colour->getFloatAlpha())), + d2dBrush.resetAndGetPointerAddress()); + + // We need to call SetDrawingEffect with a legimate brush to get DirectWrite to break text based on colours + textLayout->SetDrawingEffect (d2dBrush, range); + } + } + + bool setupLayout (const AttributedString& text, const float maxWidth, const float maxHeight, + ID2D1RenderTarget* const renderTarget, IDWriteFactory* const directWriteFactory, + IDWriteFontCollection* const fontCollection, ComSmartPtr& textLayout) + { + // To add color to text, we need to create a D2D render target + // Since we are not actually rendering to a D2D context we create a temporary GDI render target + + Font defaultFont; + BOOL fontFound = false; + uint32 fontIndex; + fontCollection->FindFamilyName (defaultFont.getTypeface()->getName().toWideCharPointer(), &fontIndex, &fontFound); + + if (! fontFound) + fontIndex = 0; + + ComSmartPtr dwFontFamily; + HRESULT hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); + + ComSmartPtr dwFont; + hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, + dwFont.resetAndGetPointerAddress()); + + const float defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (dwFont); + + jassert (directWriteFactory != nullptr); + + ComSmartPtr dwTextFormat; + hr = directWriteFactory->CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), fontCollection, + DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, + defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, + L"en-us", dwTextFormat.resetAndGetPointerAddress()); + + setTextFormatProperties (text, dwTextFormat); + + const int textLen = text.getText().length(); + + hr = directWriteFactory->CreateTextLayout (text.getText().toWideCharPointer(), textLen, dwTextFormat, + maxWidth, maxHeight, textLayout.resetAndGetPointerAddress()); + + if (FAILED (hr) || textLayout == nullptr) + return false; + + const int numAttributes = text.getNumAttributes(); + + for (int i = 0; i < numAttributes; ++i) + addAttributedRange (*text.getAttribute (i), textLayout, textLen, renderTarget, fontCollection); + + return true; + } + + void createLayout (TextLayout& layout, const AttributedString& text, IDWriteFactory* const directWriteFactory, + ID2D1Factory* const direct2dFactory, IDWriteFontCollection* const fontCollection) + { + // To add color to text, we need to create a D2D render target + // Since we are not actually rendering to a D2D context we create a temporary GDI render target + + D2D1_RENDER_TARGET_PROPERTIES d2dRTProp = D2D1::RenderTargetProperties (D2D1_RENDER_TARGET_TYPE_SOFTWARE, + D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE), + 0, 0, + D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, + D2D1_FEATURE_LEVEL_DEFAULT); + ComSmartPtr renderTarget; + HRESULT hr = direct2dFactory->CreateDCRenderTarget (&d2dRTProp, renderTarget.resetAndGetPointerAddress()); + + ComSmartPtr dwTextLayout; + + if (! setupLayout (text, layout.getWidth(), 1.0e7f, renderTarget, directWriteFactory, fontCollection, dwTextLayout)) + return; + + UINT32 actualLineCount = 0; + hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount); + + layout.ensureStorageAllocated (actualLineCount); + + { + ComSmartPtr textRenderer (new CustomDirectWriteTextRenderer (fontCollection, text)); + hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0); + } + + HeapBlock dwLineMetrics (actualLineCount); + hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount); + int lastLocation = 0; + const int numLines = jmin ((int) actualLineCount, layout.getNumLines()); + + for (int i = 0; i < numLines; ++i) + { + layout.getLine(i).stringRange = Range (lastLocation, (int) lastLocation + dwLineMetrics[i].length); + lastLocation += dwLineMetrics[i].length; + } + } + + void drawToD2DContext (const AttributedString& text, const Rectangle& area, ID2D1RenderTarget* const renderTarget, + IDWriteFactory* const directWriteFactory, IDWriteFontCollection* const fontCollection) + { + ComSmartPtr dwTextLayout; + + if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget, directWriteFactory, fontCollection, dwTextLayout)) + { + ComSmartPtr d2dBrush; + renderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f)), + d2dBrush.resetAndGetPointerAddress()); + + renderTarget->DrawTextLayout (D2D1::Point2F ((float) area.getX(), (float) area.getY()), + dwTextLayout, d2dBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP); + } + } +} + +static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) +{ + const int numCharacterAttributes = text.getNumAttributes(); + + for (int i = 0; i < numCharacterAttributes; ++i) + if (const Font* const font = text.getAttribute (i)->getFont()) + if (dynamic_cast (font->getTypeface()) == nullptr) + return false; + + return true; +} + +#endif + +bool TextLayout::createNativeLayout (const AttributedString& text) +{ + #if JUCE_USE_DIRECTWRITE + if (! canAllTypefacesBeUsedInLayout (text)) + return false; + + SharedResourcePointer factories; + + if (factories->d2dFactory != nullptr && factories->systemFonts != nullptr) + { + #if JUCE_64BIT + // There's a mysterious bug in 64-bit Windows that causes garbage floating-point + // values to be returned to DrawGlyphRun the first time that it gets used. + // In lieu of a better plan, this bodge uses a dummy call to work around this. + static bool hasBeenCalled = false; + if (! hasBeenCalled) + { + hasBeenCalled = true; + TextLayout dummy; + DirectWriteTypeLayout::createLayout (dummy, text, factories->directWriteFactory, + factories->d2dFactory, factories->systemFonts); + } + #endif + + DirectWriteTypeLayout::createLayout (*this, text, factories->directWriteFactory, + factories->d2dFactory, factories->systemFonts); + return true; + } + #else + (void) text; + #endif + + return false; +} diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp new file mode 100644 index 0000000..0673aa5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -0,0 +1,307 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_USE_DIRECTWRITE +namespace +{ + static String getLocalisedName (IDWriteLocalizedStrings* names) + { + jassert (names != nullptr); + + uint32 index = 0; + BOOL exists = false; + HRESULT hr = names->FindLocaleName (L"en-us", &index, &exists); + if (! exists) + index = 0; + + uint32 length = 0; + hr = names->GetStringLength (index, &length); + + HeapBlock name (length + 1); + hr = names->GetString (index, name, length + 1); + + return static_cast (name); + } + + static String getFontFamilyName (IDWriteFontFamily* family) + { + jassert (family != nullptr); + ComSmartPtr familyNames; + HRESULT hr = family->GetFamilyNames (familyNames.resetAndGetPointerAddress()); + jassert (SUCCEEDED (hr)); (void) hr; + return getLocalisedName (familyNames); + } + + static String getFontFaceName (IDWriteFont* font) + { + jassert (font != nullptr); + ComSmartPtr faceNames; + HRESULT hr = font->GetFaceNames (faceNames.resetAndGetPointerAddress()); + jassert (SUCCEEDED (hr)); (void) hr; + + return getLocalisedName (faceNames); + } +} + +class Direct2DFactories +{ +public: + Direct2DFactories() + { + if (direct2dDll.open ("d2d1.dll")) + { + JUCE_LOAD_WINAPI_FUNCTION (direct2dDll, D2D1CreateFactory, d2d1CreateFactory, + HRESULT, (D2D1_FACTORY_TYPE, REFIID, D2D1_FACTORY_OPTIONS*, void**)) + + if (d2d1CreateFactory != nullptr) + { + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + d2d1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof (ID2D1Factory), &options, + (void**) d2dFactory.resetAndGetPointerAddress()); + } + } + + if (directWriteDll.open ("DWrite.dll")) + { + JUCE_LOAD_WINAPI_FUNCTION (directWriteDll, DWriteCreateFactory, dWriteCreateFactory, + HRESULT, (DWRITE_FACTORY_TYPE, REFIID, IUnknown**)) + + if (dWriteCreateFactory != nullptr) + { + dWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory), + (IUnknown**) directWriteFactory.resetAndGetPointerAddress()); + + if (directWriteFactory != nullptr) + directWriteFactory->GetSystemFontCollection (systemFonts.resetAndGetPointerAddress()); + } + } + } + + ~Direct2DFactories() + { + d2dFactory = nullptr; // (need to make sure these are released before deleting the DynamicLibrary objects) + directWriteFactory = nullptr; + systemFonts = nullptr; + } + + ComSmartPtr d2dFactory; + ComSmartPtr directWriteFactory; + ComSmartPtr systemFonts; + +private: + DynamicLibrary direct2dDll, directWriteDll; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Direct2DFactories) +}; + +//================================================================================================== +class WindowsDirectWriteTypeface : public Typeface +{ +public: + WindowsDirectWriteTypeface (const Font& font, IDWriteFontCollection* fontCollection) + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), + unitsToHeightScaleFactor (1.0f), heightToPointsFactor (1.0f), ascent (0.0f) + { + jassert (fontCollection != nullptr); + + BOOL fontFound = false; + uint32 fontIndex = 0; + HRESULT hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound); + if (! fontFound) + fontIndex = 0; + + // Get the font family using the search results + // Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family + ComSmartPtr dwFontFamily; + hr = fontCollection->GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); + + // Get a specific font in the font family using typeface style + { + ComSmartPtr dwFont; + + for (int i = (int) dwFontFamily->GetFontCount(); --i >= 0;) + { + hr = dwFontFamily->GetFont (i, dwFont.resetAndGetPointerAddress()); + + if (i == 0) + break; + + ComSmartPtr faceNames; + hr = dwFont->GetFaceNames (faceNames.resetAndGetPointerAddress()); + + if (font.getTypefaceStyle() == getLocalisedName (faceNames)) + break; + } + + jassert (dwFont != nullptr); + hr = dwFont->CreateFontFace (dwFontFace.resetAndGetPointerAddress()); + } + + if (dwFontFace != nullptr) + { + DWRITE_FONT_METRICS dwFontMetrics; + dwFontFace->GetMetrics (&dwFontMetrics); + + // All Font Metrics are in design units so we need to get designUnitsPerEm value + // to get the metrics into Em/Design Independent Pixels + designUnitsPerEm = dwFontMetrics.designUnitsPerEm; + + ascent = std::abs ((float) dwFontMetrics.ascent); + const float totalSize = ascent + std::abs ((float) dwFontMetrics.descent); + ascent /= totalSize; + unitsToHeightScaleFactor = designUnitsPerEm / totalSize; + + HDC tempDC = GetDC (0); + float dpi = (GetDeviceCaps (tempDC, LOGPIXELSX) + GetDeviceCaps (tempDC, LOGPIXELSY)) / 2.0f; + heightToPointsFactor = (dpi / GetDeviceCaps (tempDC, LOGPIXELSY)) * unitsToHeightScaleFactor; + ReleaseDC (0, tempDC); + + const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm; + const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm; + const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent)); + pathTransform = AffineTransform::scale (pathScale); + } + } + + bool loadedOk() const noexcept { return dwFontFace != nullptr; } + + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + float getHeightToPointsFactor() const { return heightToPointsFactor; } + + float getStringWidth (const String& text) + { + const CharPointer_UTF32 textUTF32 (text.toUTF32()); + const size_t len = textUTF32.length(); + + HeapBlock glyphIndices (len); + dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices); + + HeapBlock dwGlyphMetrics (len); + dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false); + + float x = 0; + for (size_t i = 0; i < len; ++i) + x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm; + + return x * unitsToHeightScaleFactor; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + xOffsets.add (0); + + const CharPointer_UTF32 textUTF32 (text.toUTF32()); + const size_t len = textUTF32.length(); + + HeapBlock glyphIndices (len); + dwFontFace->GetGlyphIndices (textUTF32, (UINT32) len, glyphIndices); + HeapBlock dwGlyphMetrics (len); + dwFontFace->GetDesignGlyphMetrics (glyphIndices, (UINT32) len, dwGlyphMetrics, false); + + float x = 0; + for (size_t i = 0; i < len; ++i) + { + x += (float) dwGlyphMetrics[i].advanceWidth / designUnitsPerEm; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphIndices[i]); + } + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) + { + jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty + UINT16 glyphIndex = (UINT16) glyphNumber; + ComSmartPtr pathGeometrySink (new PathGeometrySink()); + + dwFontFace->GetGlyphRunOutline (1024.0f, &glyphIndex, nullptr, nullptr, 1, false, false, pathGeometrySink); + path = pathGeometrySink->path; + + if (! pathTransform.isIdentity()) + path.applyTransform (pathTransform); + + return true; + } + + IDWriteFontFace* getIDWriteFontFace() const noexcept { return dwFontFace; } + +private: + SharedResourcePointer factories; + ComSmartPtr dwFontFace; + float unitsToHeightScaleFactor, heightToPointsFactor, ascent; + int designUnitsPerEm; + AffineTransform pathTransform; + + class PathGeometrySink : public ComBaseClassHelper + { + public: + PathGeometrySink() : ComBaseClassHelper (0) {} + + void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT *beziers, UINT beziersCount) + { + for (UINT i = 0; i < beziersCount; ++i) + path.cubicTo ((float) beziers[i].point1.x, (float) beziers[i].point1.y, + (float) beziers[i].point2.x, (float) beziers[i].point2.y, + (float) beziers[i].point3.x, (float) beziers[i].point3.y); + } + + void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount) + { + for (UINT i = 0; i < pointsCount; ++i) + path.lineTo ((float) points[i].x, + (float) points[i].y); + } + + void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) + { + path.startNewSubPath ((float) startPoint.x, + (float) startPoint.y); + } + + void __stdcall EndFigure (D2D1_FIGURE_END figureEnd) + { + if (figureEnd == D2D1_FIGURE_END_CLOSED) + path.closeSubPath(); + } + + void __stdcall SetFillMode (D2D1_FILL_MODE fillMode) + { + path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING); + } + + void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) {} + JUCE_COMRESULT Close() { return S_OK; } + + Path path; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathGeometrySink) + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsDirectWriteTypeface) +}; + +#endif diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp new file mode 100644 index 0000000..bc5bb35 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp @@ -0,0 +1,644 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +/* This is some quick-and-dirty code to extract the typeface name from a lump of TTF file data. + It's needed because although win32 will happily load a TTF file from in-memory data, it won't + tell you the name of the damned font that it just loaded.. and in order to actually use the font, + you need to know its name!! Anyway, this awful hack seems to work for most fonts. +*/ +namespace TTFNameExtractor +{ + struct OffsetTable + { + uint32 version; + uint16 numTables, searchRange, entrySelector, rangeShift; + }; + + struct TableDirectory + { + char tag[4]; + uint32 checkSum, offset, length; + }; + + struct NamingTable + { + uint16 formatSelector; + uint16 numberOfNameRecords; + uint16 offsetStartOfStringStorage; + }; + + struct NameRecord + { + uint16 platformID, encodingID, languageID; + uint16 nameID, stringLength, offsetFromStorageArea; + }; + + static String parseNameRecord (MemoryInputStream& input, const NameRecord& nameRecord, + const int64 directoryOffset, const int64 offsetOfStringStorage) + { + String result; + const int64 oldPos = input.getPosition(); + input.setPosition (directoryOffset + offsetOfStringStorage + ByteOrder::swapIfLittleEndian (nameRecord.offsetFromStorageArea)); + const int stringLength = (int) ByteOrder::swapIfLittleEndian (nameRecord.stringLength); + const int platformID = ByteOrder::swapIfLittleEndian (nameRecord.platformID); + + if (platformID == 0 || platformID == 3) + { + const int numChars = stringLength / 2 + 1; + HeapBlock buffer; + buffer.calloc (numChars + 1); + input.read (buffer, stringLength); + + for (int i = 0; i < numChars; ++i) + buffer[i] = ByteOrder::swapIfLittleEndian (buffer[i]); + + static_jassert (sizeof (CharPointer_UTF16::CharType) == sizeof (uint16)); + result = CharPointer_UTF16 ((CharPointer_UTF16::CharType*) buffer.getData()); + } + else + { + HeapBlock buffer; + buffer.calloc (stringLength + 1); + input.read (buffer, stringLength); + result = CharPointer_UTF8 (buffer.getData()); + } + + input.setPosition (oldPos); + return result; + } + + static String parseNameTable (MemoryInputStream& input, int64 directoryOffset) + { + input.setPosition (directoryOffset); + + NamingTable namingTable = { 0 }; + input.read (&namingTable, sizeof (namingTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (namingTable.numberOfNameRecords); ++i) + { + NameRecord nameRecord = { 0 }; + input.read (&nameRecord, sizeof (nameRecord)); + + if (ByteOrder::swapIfLittleEndian (nameRecord.nameID) == 4) + { + const String result (parseNameRecord (input, nameRecord, directoryOffset, + ByteOrder::swapIfLittleEndian (namingTable.offsetStartOfStringStorage))); + + if (result.isNotEmpty()) + return result; + } + } + + return String(); + } + + static String getTypefaceNameFromFile (MemoryInputStream& input) + { + OffsetTable offsetTable = { 0 }; + input.read (&offsetTable, sizeof (offsetTable)); + + for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i) + { + TableDirectory tableDirectory; + zerostruct (tableDirectory); + input.read (&tableDirectory, sizeof (tableDirectory)); + + if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name")) + return parseNameTable (input, ByteOrder::swapIfLittleEndian (tableDirectory.offset)); + } + + return String(); + } +} + +namespace FontEnumerators +{ + static int CALLBACK fontEnum2 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) + { + if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + { + const String fontName (lpelfe->elfLogFont.lfFaceName); + ((StringArray*) lParam)->addIfNotAlreadyThere (fontName.removeCharacters ("@")); + } + + return 1; + } + + static int CALLBACK fontEnum1 (ENUMLOGFONTEXW* lpelfe, NEWTEXTMETRICEXW*, int type, LPARAM lParam) + { + if (lpelfe != nullptr && (type & RASTER_FONTTYPE) == 0) + { + LOGFONTW lf = { 0 }; + lf.lfWeight = FW_DONTCARE; + lf.lfOutPrecision = OUT_OUTLINE_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfPitchAndFamily = FF_DONTCARE; + + const String fontName (lpelfe->elfLogFont.lfFaceName); + fontName.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); + + HDC dc = CreateCompatibleDC (0); + EnumFontFamiliesEx (dc, &lf, + (FONTENUMPROCW) &fontEnum2, + lParam, 0); + DeleteDC (dc); + } + + return 1; + } +} + +StringArray Font::findAllTypefaceNames() +{ + StringArray results; + + #if JUCE_USE_DIRECTWRITE + SharedResourcePointer factories; + + if (factories->systemFonts != nullptr) + { + ComSmartPtr fontFamily; + uint32 fontFamilyCount = 0; + fontFamilyCount = factories->systemFonts->GetFontFamilyCount(); + + for (uint32 i = 0; i < fontFamilyCount; ++i) + { + HRESULT hr = factories->systemFonts->GetFontFamily (i, fontFamily.resetAndGetPointerAddress()); + + if (SUCCEEDED (hr)) + results.addIfNotAlreadyThere (getFontFamilyName (fontFamily)); + } + } + else + #endif + { + HDC dc = CreateCompatibleDC (0); + + { + LOGFONTW lf = { 0 }; + lf.lfWeight = FW_DONTCARE; + lf.lfOutPrecision = OUT_OUTLINE_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfPitchAndFamily = FF_DONTCARE; + + EnumFontFamiliesEx (dc, &lf, + (FONTENUMPROCW) &FontEnumerators::fontEnum1, + (LPARAM) &results, 0); + } + + DeleteDC (dc); + } + + results.sort (true); + return results; +} + +StringArray Font::findAllTypefaceStyles (const String& family) +{ + if (FontStyleHelpers::isPlaceholderFamilyName (family)) + return findAllTypefaceStyles (FontStyleHelpers::getConcreteFamilyNameFromPlaceholder (family)); + + StringArray results; + + #if JUCE_USE_DIRECTWRITE + SharedResourcePointer factories; + + if (factories->systemFonts != nullptr) + { + BOOL fontFound = false; + uint32 fontIndex = 0; + HRESULT hr = factories->systemFonts->FindFamilyName (family.toWideCharPointer(), &fontIndex, &fontFound); + if (! fontFound) + fontIndex = 0; + + // Get the font family using the search results + // Fonts like: Times New Roman, Times New Roman Bold, Times New Roman Italic are all in the same font family + ComSmartPtr fontFamily; + hr = factories->systemFonts->GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress()); + + // Get the font faces + ComSmartPtr dwFont; + uint32 fontFacesCount = 0; + fontFacesCount = fontFamily->GetFontCount(); + + for (uint32 i = 0; i < fontFacesCount; ++i) + { + hr = fontFamily->GetFont (i, dwFont.resetAndGetPointerAddress()); + + // Ignore any algorithmically generated bold and oblique styles.. + if (dwFont->GetSimulations() == DWRITE_FONT_SIMULATIONS_NONE) + results.addIfNotAlreadyThere (getFontFaceName (dwFont)); + } + } + else + #endif + { + results.add ("Regular"); + results.add ("Italic"); + results.add ("Bold"); + results.add ("Bold Italic"); + } + + return results; +} + +extern bool juce_isRunningInWine(); + +struct DefaultFontNames +{ + DefaultFontNames() + { + if (juce_isRunningInWine()) + { + // If we're running in Wine, then use fonts that might be available on Linux.. + defaultSans = "Bitstream Vera Sans"; + defaultSerif = "Bitstream Vera Serif"; + defaultFixed = "Bitstream Vera Sans Mono"; + } + else + { + defaultSans = "Verdana"; + defaultSerif = "Times New Roman"; + defaultFixed = "Lucida Console"; + defaultFallback = "Tahoma"; // (contains plenty of unicode characters) + } + } + + String defaultSans, defaultSerif, defaultFixed, defaultFallback; +}; + +Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) +{ + static DefaultFontNames defaultNames; + + Font newFont (font); + const String& faceName = font.getTypefaceName(); + + if (faceName == getDefaultSansSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSans); + else if (faceName == getDefaultSerifFontName()) newFont.setTypefaceName (defaultNames.defaultSerif); + else if (faceName == getDefaultMonospacedFontName()) newFont.setTypefaceName (defaultNames.defaultFixed); + + if (font.getTypefaceStyle() == getDefaultStyle()) + newFont.setTypefaceStyle ("Regular"); + + return Typeface::createSystemTypefaceFor (newFont); +} + +//============================================================================== +class WindowsTypeface : public Typeface +{ +public: + WindowsTypeface (const Font& font) + : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), + ascent (1.0f), heightToPointsFactor (1.0f), + defaultGlyph (-1) + { + loadFont(); + } + + WindowsTypeface (const void* data, size_t dataSize) + : Typeface (String(), String()), + fontH (0), previousFontH (0), + dc (CreateCompatibleDC (0)), memoryFont (0), + ascent (1.0f), heightToPointsFactor (1.0f), + defaultGlyph (-1) + { + DWORD numInstalled = 0; + memoryFont = AddFontMemResourceEx (const_cast (data), (DWORD) dataSize, + nullptr, &numInstalled); + + MemoryInputStream m (data, dataSize, false); + name = TTFNameExtractor::getTypefaceNameFromFile (m); + loadFont(); + } + + ~WindowsTypeface() + { + SelectObject (dc, previousFontH); // Replacing the previous font before deleting the DC avoids a warning in BoundsChecker + DeleteDC (dc); + + if (fontH != 0) + DeleteObject (fontH); + + if (memoryFont != 0) + RemoveFontMemResourceEx (memoryFont); + } + + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + float getHeightToPointsFactor() const { return heightToPointsFactor; } + + float getStringWidth (const String& text) + { + const CharPointer_UTF16 utf16 (text.toUTF16()); + const size_t numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; + + if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) + { + for (size_t i = 0; i < numChars; ++i) + x += getKerning (dc, results[i], results[i + 1]); + } + + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + const CharPointer_UTF16 utf16 (text.toUTF16()); + const size_t numChars = utf16.length(); + HeapBlock results (numChars + 1); + results[numChars] = -1; + float x = 0; + + if (GetGlyphIndices (dc, utf16, (int) numChars, reinterpret_cast (results.getData()), + GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR) + { + resultGlyphs.ensureStorageAllocated ((int) numChars); + xOffsets.ensureStorageAllocated ((int) numChars + 1); + + for (size_t i = 0; i < numChars; ++i) + { + resultGlyphs.add (results[i]); + xOffsets.add (x); + x += getKerning (dc, results[i], results[i + 1]); + } + } + + xOffsets.add (x); + } + + bool getOutlineForGlyph (int glyphNumber, Path& glyphPath) + { + if (glyphNumber < 0) + glyphNumber = defaultGlyph; + + GLYPHMETRICS gm; + // (although GetGlyphOutline returns a DWORD, it may be -1 on failure, so treat it as signed int..) + const int bufSize = (int) GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, + &gm, 0, 0, &identityMatrix); + + if (bufSize > 0) + { + HeapBlock data (bufSize); + GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, + bufSize, data, &identityMatrix); + + const TTPOLYGONHEADER* pheader = reinterpret_cast (data.getData()); + + const float scaleX = 1.0f / tm.tmHeight; + const float scaleY = -scaleX; + + while ((char*) pheader < data + bufSize) + { + glyphPath.startNewSubPath (scaleX * pheader->pfxStart.x.value, + scaleY * pheader->pfxStart.y.value); + + const TTPOLYCURVE* curve = (const TTPOLYCURVE*) ((const char*) pheader + sizeof (TTPOLYGONHEADER)); + const char* const curveEnd = ((const char*) pheader) + pheader->cb; + + while ((const char*) curve < curveEnd) + { + if (curve->wType == TT_PRIM_LINE) + { + for (int i = 0; i < curve->cpfx; ++i) + glyphPath.lineTo (scaleX * curve->apfx[i].x.value, + scaleY * curve->apfx[i].y.value); + } + else if (curve->wType == TT_PRIM_QSPLINE) + { + for (int i = 0; i < curve->cpfx - 1; ++i) + { + const float x2 = scaleX * curve->apfx[i].x.value; + const float y2 = scaleY * curve->apfx[i].y.value; + float x3 = scaleX * curve->apfx[i + 1].x.value; + float y3 = scaleY * curve->apfx[i + 1].y.value; + + if (i < curve->cpfx - 2) + { + x3 = 0.5f * (x2 + x3); + y3 = 0.5f * (y2 + y3); + } + + glyphPath.quadraticTo (x2, y2, x3, y3); + } + } + + curve = (const TTPOLYCURVE*) &(curve->apfx [curve->cpfx]); + } + + pheader = (const TTPOLYGONHEADER*) curve; + + glyphPath.closeSubPath(); + } + } + + return true; + } + +private: + static const MAT2 identityMatrix; + HFONT fontH; + HGDIOBJ previousFontH; + HDC dc; + TEXTMETRIC tm; + HANDLE memoryFont; + float ascent, heightToPointsFactor; + int defaultGlyph, heightInPoints; + + struct KerningPair + { + int glyph1, glyph2; + float kerning; + + bool operator== (const KerningPair& other) const noexcept + { + return glyph1 == other.glyph1 && glyph2 == other.glyph2; + } + + bool operator< (const KerningPair& other) const noexcept + { + return glyph1 < other.glyph1 + || (glyph1 == other.glyph1 && glyph2 < other.glyph2); + } + }; + + SortedSet kerningPairs; + + void loadFont() + { + SetMapperFlags (dc, 0); + SetMapMode (dc, MM_TEXT); + + LOGFONTW lf = { 0 }; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfOutPrecision = OUT_OUTLINE_PRECIS; + lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + lf.lfQuality = PROOF_QUALITY; + lf.lfItalic = (BYTE) (style.contains ("Italic") ? TRUE : FALSE); + lf.lfWeight = style.contains ("Bold") ? FW_BOLD : FW_NORMAL; + lf.lfHeight = -256; + name.copyToUTF16 (lf.lfFaceName, sizeof (lf.lfFaceName)); + + HFONT standardSizedFont = CreateFontIndirect (&lf); + + if (standardSizedFont != 0) + { + if ((previousFontH = SelectObject (dc, standardSizedFont)) != 0) + { + fontH = standardSizedFont; + + OUTLINETEXTMETRIC otm; + if (GetOutlineTextMetrics (dc, sizeof (otm), &otm) != 0) + { + heightInPoints = otm.otmEMSquare; + lf.lfHeight = -(int) heightInPoints; + fontH = CreateFontIndirect (&lf); + + SelectObject (dc, fontH); + DeleteObject (standardSizedFont); + } + } + } + + if (GetTextMetrics (dc, &tm)) + { + float dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0f; + heightToPointsFactor = (dpi / GetDeviceCaps (dc, LOGPIXELSY)) * heightInPoints / (float) tm.tmHeight; + ascent = tm.tmAscent / (float) tm.tmHeight; + defaultGlyph = getGlyphForChar (dc, tm.tmDefaultChar); + createKerningPairs (dc, (float) tm.tmHeight); + } + } + + void createKerningPairs (HDC dc, const float height) + { + HeapBlock rawKerning; + const DWORD numKPs = GetKerningPairs (dc, 0, 0); + rawKerning.calloc (numKPs); + GetKerningPairs (dc, numKPs, rawKerning); + + kerningPairs.ensureStorageAllocated ((int) numKPs); + + for (DWORD i = 0; i < numKPs; ++i) + { + KerningPair kp; + kp.glyph1 = getGlyphForChar (dc, rawKerning[i].wFirst); + kp.glyph2 = getGlyphForChar (dc, rawKerning[i].wSecond); + + const int standardWidth = getGlyphWidth (dc, kp.glyph1); + kp.kerning = (standardWidth + rawKerning[i].iKernAmount) / height; + kerningPairs.add (kp); + + kp.glyph2 = -1; // add another entry for the standard width version.. + kp.kerning = standardWidth / height; + kerningPairs.add (kp); + } + } + + static int getGlyphForChar (HDC dc, juce_wchar character) + { + const WCHAR charToTest[] = { (WCHAR) character, 0 }; + WORD index = 0; + + if (GetGlyphIndices (dc, charToTest, 1, &index, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR + || index == 0xffff) + return -1; + + return index; + } + + static int getGlyphWidth (HDC dc, int glyphNumber) + { + GLYPHMETRICS gm; + gm.gmCellIncX = 0; + GetGlyphOutline (dc, (UINT) glyphNumber, GGO_NATIVE | GGO_GLYPH_INDEX, &gm, 0, 0, &identityMatrix); + return gm.gmCellIncX; + } + + float getKerning (HDC dc, const int glyph1, const int glyph2) + { + KerningPair kp; + kp.glyph1 = glyph1; + kp.glyph2 = glyph2; + int index = kerningPairs.indexOf (kp); + + if (index < 0) + { + kp.glyph2 = -1; + index = kerningPairs.indexOf (kp); + + if (index < 0) + { + kp.glyph2 = -1; + kp.kerning = getGlyphWidth (dc, kp.glyph1) / (float) tm.tmHeight; + kerningPairs.add (kp); + return kp.kerning; + } + } + + return kerningPairs.getReference (index).kerning; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTypeface) +}; + +const MAT2 WindowsTypeface::identityMatrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; + +Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) +{ + #if JUCE_USE_DIRECTWRITE + SharedResourcePointer factories; + + if (factories->systemFonts != nullptr) + { + ScopedPointer wtf (new WindowsDirectWriteTypeface (font, factories->systemFonts)); + + if (wtf->loadedOk()) + return wtf.release(); + } + #endif + + return new WindowsTypeface (font); +} + +Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) +{ + return new WindowsTypeface (data, dataSize); +} + +void Typeface::scanFolderForFonts (const File&) +{ + jassertfalse; // not implemented on this platform +} diff --git a/JuceLibraryCode/modules/juce_graphics/placement/juce_Justification.h b/JuceLibraryCode/modules/juce_graphics/placement/juce_Justification.h new file mode 100644 index 0000000..f80fa21 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/placement/juce_Justification.h @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_JUSTIFICATION_H_INCLUDED +#define JUCE_JUSTIFICATION_H_INCLUDED + + +//============================================================================== +/** + Represents a type of justification to be used when positioning graphical items. + + e.g. it indicates whether something should be placed top-left, top-right, + centred, etc. + + It is used in various places wherever this kind of information is needed. +*/ +class Justification +{ +public: + //============================================================================== + /** Creates a Justification object using a combination of flags from the Flags enum. */ + Justification (int justificationFlags) noexcept : flags (justificationFlags) {} + + /** Creates a copy of another Justification object. */ + Justification (const Justification& other) noexcept : flags (other.flags) {} + + /** Copies another Justification object. */ + Justification& operator= (const Justification& other) noexcept + { + flags = other.flags; + return *this; + } + + bool operator== (const Justification& other) const noexcept { return flags == other.flags; } + bool operator!= (const Justification& other) const noexcept { return flags != other.flags; } + + //============================================================================== + /** Returns the raw flags that are set for this Justification object. */ + inline int getFlags() const noexcept { return flags; } + + /** Tests a set of flags for this object. + @returns true if any of the flags passed in are set on this object. + */ + inline bool testFlags (int flagsToTest) const noexcept { return (flags & flagsToTest) != 0; } + + /** Returns just the flags from this object that deal with vertical layout. */ + int getOnlyVerticalFlags() const noexcept { return flags & (top | bottom | verticallyCentred); } + + /** Returns just the flags from this object that deal with horizontal layout. */ + int getOnlyHorizontalFlags() const noexcept { return flags & (left | right | horizontallyCentred | horizontallyJustified); } + + //============================================================================== + /** Adjusts the position of a rectangle to fit it into a space. + + The (x, y) position of the rectangle will be updated to position it inside the + given space according to the justification flags. + */ + template + void applyToRectangle (ValueType& x, ValueType& y, ValueType w, ValueType h, + ValueType spaceX, ValueType spaceY, ValueType spaceW, ValueType spaceH) const noexcept + { + x = spaceX; + if ((flags & horizontallyCentred) != 0) x += (spaceW - w) / (ValueType) 2; + else if ((flags & right) != 0) x += spaceW - w; + + y = spaceY; + if ((flags & verticallyCentred) != 0) y += (spaceH - h) / (ValueType) 2; + else if ((flags & bottom) != 0) y += spaceH - h; + } + + /** Returns the new position of a rectangle that has been justified to fit within a given space. + */ + template + const Rectangle appliedToRectangle (const Rectangle& areaToAdjust, + const Rectangle& targetSpace) const noexcept + { + ValueType x = areaToAdjust.getX(), y = areaToAdjust.getY(); + applyToRectangle (x, y, areaToAdjust.getWidth(), areaToAdjust.getHeight(), + targetSpace.getX(), targetSpace.getY(), targetSpace.getWidth(), targetSpace.getHeight()); + return areaToAdjust.withPosition (x, y); + } + + //============================================================================== + /** Flag values that can be combined and used in the constructor. */ + enum Flags + { + //============================================================================== + /** Indicates that the item should be aligned against the left edge of the available space. */ + left = 1, + + /** Indicates that the item should be aligned against the right edge of the available space. */ + right = 2, + + /** Indicates that the item should be placed in the centre between the left and right + sides of the available space. */ + horizontallyCentred = 4, + + //============================================================================== + /** Indicates that the item should be aligned against the top edge of the available space. */ + top = 8, + + /** Indicates that the item should be aligned against the bottom edge of the available space. */ + bottom = 16, + + /** Indicates that the item should be placed in the centre between the top and bottom + sides of the available space. */ + verticallyCentred = 32, + + //============================================================================== + /** Indicates that lines of text should be spread out to fill the maximum width + available, so that both margins are aligned vertically. + */ + horizontallyJustified = 64, + + //============================================================================== + /** Indicates that the item should be centred vertically and horizontally. + This is equivalent to (horizontallyCentred | verticallyCentred) + */ + centred = 36, + + /** Indicates that the item should be centred vertically but placed on the left hand side. + This is equivalent to (left | verticallyCentred) + */ + centredLeft = 33, + + /** Indicates that the item should be centred vertically but placed on the right hand side. + This is equivalent to (right | verticallyCentred) + */ + centredRight = 34, + + /** Indicates that the item should be centred horizontally and placed at the top. + This is equivalent to (horizontallyCentred | top) + */ + centredTop = 12, + + /** Indicates that the item should be centred horizontally and placed at the bottom. + This is equivalent to (horizontallyCentred | bottom) + */ + centredBottom = 20, + + /** Indicates that the item should be placed in the top-left corner. + This is equivalent to (left | top) + */ + topLeft = 9, + + /** Indicates that the item should be placed in the top-right corner. + This is equivalent to (right | top) + */ + topRight = 10, + + /** Indicates that the item should be placed in the bottom-left corner. + This is equivalent to (left | bottom) + */ + bottomLeft = 17, + + /** Indicates that the item should be placed in the bottom-left corner. + This is equivalent to (right | bottom) + */ + bottomRight = 18 + }; + + +private: + //============================================================================== + int flags; +}; + +#endif // JUCE_JUSTIFICATION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp new file mode 100644 index 0000000..031048e --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp @@ -0,0 +1,127 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +RectanglePlacement::RectanglePlacement (const RectanglePlacement& other) noexcept + : flags (other.flags) +{ +} + +RectanglePlacement& RectanglePlacement::operator= (const RectanglePlacement& other) noexcept +{ + flags = other.flags; + return *this; +} + +bool RectanglePlacement::operator== (const RectanglePlacement& other) const noexcept +{ + return flags == other.flags; +} + +bool RectanglePlacement::operator!= (const RectanglePlacement& other) const noexcept +{ + return flags != other.flags; +} + +void RectanglePlacement::applyTo (double& x, double& y, double& w, double& h, + const double dx, const double dy, const double dw, const double dh) const noexcept +{ + if (w == 0 || h == 0) + return; + + if ((flags & stretchToFit) != 0) + { + x = dx; + y = dy; + w = dw; + h = dh; + } + else + { + double scale = (flags & fillDestination) != 0 ? jmax (dw / w, dh / h) + : jmin (dw / w, dh / h); + + if ((flags & onlyReduceInSize) != 0) + scale = jmin (scale, 1.0); + + if ((flags & onlyIncreaseInSize) != 0) + scale = jmax (scale, 1.0); + + w *= scale; + h *= scale; + + if ((flags & xLeft) != 0) + x = dx; + else if ((flags & xRight) != 0) + x = dx + dw - w; + else + x = dx + (dw - w) * 0.5; + + if ((flags & yTop) != 0) + y = dy; + else if ((flags & yBottom) != 0) + y = dy + dh - h; + else + y = dy + (dh - h) * 0.5; + } +} + +AffineTransform RectanglePlacement::getTransformToFit (const Rectangle& source, const Rectangle& destination) const noexcept +{ + if (source.isEmpty()) + return AffineTransform::identity; + + float newX = destination.getX(); + float newY = destination.getY(); + + float scaleX = destination.getWidth() / source.getWidth(); + float scaleY = destination.getHeight() / source.getHeight(); + + if ((flags & stretchToFit) == 0) + { + scaleX = (flags & fillDestination) != 0 ? jmax (scaleX, scaleY) + : jmin (scaleX, scaleY); + + if ((flags & onlyReduceInSize) != 0) + scaleX = jmin (scaleX, 1.0f); + + if ((flags & onlyIncreaseInSize) != 0) + scaleX = jmax (scaleX, 1.0f); + + scaleY = scaleX; + + if ((flags & xRight) != 0) + newX += destination.getWidth() - source.getWidth() * scaleX; // right + else if ((flags & xLeft) == 0) + newX += (destination.getWidth() - source.getWidth() * scaleX) / 2.0f; // centre + + if ((flags & yBottom) != 0) + newY += destination.getHeight() - source.getHeight() * scaleX; // bottom + else if ((flags & yTop) == 0) + newY += (destination.getHeight() - source.getHeight() * scaleX) / 2.0f; // centre + } + + return AffineTransform::translation (-source.getX(), -source.getY()) + .scaled (scaleX, scaleY) + .translated (newX, newY); +} diff --git a/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.h b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.h new file mode 100644 index 0000000..9341e67 --- /dev/null +++ b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.h @@ -0,0 +1,172 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_RECTANGLEPLACEMENT_H_INCLUDED +#define JUCE_RECTANGLEPLACEMENT_H_INCLUDED + + +//============================================================================== +/** + Defines the method used to postion some kind of rectangular object within + a rectangular viewport. + + Although similar to Justification, this is more specific, and has some extra + options. +*/ +class JUCE_API RectanglePlacement +{ +public: + //============================================================================== + /** Creates a RectanglePlacement object using a combination of flags from the Flags enum. */ + inline RectanglePlacement (int placementFlags) noexcept : flags (placementFlags) {} + + /** Creates a default RectanglePlacement object, which is equivalent to using the 'centred' flag. */ + inline RectanglePlacement() noexcept : flags (centred) {} + + /** Creates a copy of another RectanglePlacement object. */ + RectanglePlacement (const RectanglePlacement&) noexcept; + + /** Copies another RectanglePlacement object. */ + RectanglePlacement& operator= (const RectanglePlacement&) noexcept; + + bool operator== (const RectanglePlacement&) const noexcept; + bool operator!= (const RectanglePlacement&) const noexcept; + + //============================================================================== + /** Flag values that can be combined and used in the constructor. */ + enum Flags + { + //============================================================================== + /** Indicates that the source rectangle's left edge should be aligned with the left edge of the target rectangle. */ + xLeft = 1, + + /** Indicates that the source rectangle's right edge should be aligned with the right edge of the target rectangle. */ + xRight = 2, + + /** Indicates that the source should be placed in the centre between the left and right + sides of the available space. */ + xMid = 4, + + //============================================================================== + /** Indicates that the source's top edge should be aligned with the top edge of the + destination rectangle. */ + yTop = 8, + + /** Indicates that the source's bottom edge should be aligned with the bottom edge of the + destination rectangle. */ + yBottom = 16, + + /** Indicates that the source should be placed in the centre between the top and bottom + sides of the available space. */ + yMid = 32, + + //============================================================================== + /** If this flag is set, then the source rectangle will be resized to completely fill + the destination rectangle, and all other flags are ignored. + */ + stretchToFit = 64, + + //============================================================================== + /** If this flag is set, then the source rectangle will be resized so that it is the + minimum size to completely fill the destination rectangle, without changing its + aspect ratio. This means that some of the source rectangle may fall outside + the destination. + + If this flag is not set, the source will be given the maximum size at which none + of it falls outside the destination rectangle. + */ + fillDestination = 128, + + /** Indicates that the source rectangle can be reduced in size if required, but should + never be made larger than its original size. + */ + onlyReduceInSize = 256, + + /** Indicates that the source rectangle can be enlarged if required, but should + never be made smaller than its original size. + */ + onlyIncreaseInSize = 512, + + /** Indicates that the source rectangle's size should be left unchanged. + */ + doNotResize = (onlyIncreaseInSize | onlyReduceInSize), + + //============================================================================== + /** A shorthand value that is equivalent to (xMid | yMid). */ + centred = 4 + 32 + }; + + //============================================================================== + /** Returns the raw flags that are set for this object. */ + inline int getFlags() const noexcept { return flags; } + + /** Tests a set of flags for this object. + + @returns true if any of the flags passed in are set on this object. + */ + inline bool testFlags (int flagsToTest) const noexcept { return (flags & flagsToTest) != 0; } + + + //============================================================================== + /** Adjusts the position and size of a rectangle to fit it into a space. + + The source rectangle coordinates will be adjusted so that they fit into + the destination rectangle based on this object's flags. + */ + void applyTo (double& sourceX, + double& sourceY, + double& sourceW, + double& sourceH, + double destinationX, + double destinationY, + double destinationW, + double destinationH) const noexcept; + + /** Returns the rectangle that should be used to fit the given source rectangle + into the destination rectangle using the current flags. + */ + template + Rectangle appliedTo (const Rectangle& source, + const Rectangle& destination) const noexcept + { + double x = source.getX(), y = source.getY(), w = source.getWidth(), h = source.getHeight(); + applyTo (x, y, w, h, static_cast (destination.getX()), static_cast (destination.getY()), + static_cast (destination.getWidth()), static_cast (destination.getHeight())); + return Rectangle (static_cast (x), static_cast (y), + static_cast (w), static_cast (h)); + } + + /** Returns the transform that should be applied to these source coordinates to fit them + into the destination rectangle using the current flags. + */ + AffineTransform getTransformToFit (const Rectangle& source, + const Rectangle& destination) const noexcept; + + +private: + //============================================================================== + int flags; +}; + +#endif // JUCE_RECTANGLEPLACEMENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp new file mode 100644 index 0000000..4e21873 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp @@ -0,0 +1,98 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +JUCEApplication::JUCEApplication() {} +JUCEApplication::~JUCEApplication() {} + +//============================================================================== +JUCEApplication* JUCE_CALLTYPE JUCEApplication::getInstance() noexcept +{ + return dynamic_cast (JUCEApplicationBase::getInstance()); +} + +bool JUCEApplication::moreThanOneInstanceAllowed() { return true; } +void JUCEApplication::anotherInstanceStarted (const String&) {} + +void JUCEApplication::suspended() {} +void JUCEApplication::resumed() {} + +void JUCEApplication::systemRequestedQuit() { quit(); } + +void JUCEApplication::unhandledException (const std::exception*, const String&, int) +{ + jassertfalse; +} + +//============================================================================== +ApplicationCommandTarget* JUCEApplication::getNextCommandTarget() +{ + return nullptr; +} + +void JUCEApplication::getAllCommands (Array& commands) +{ + commands.add (StandardApplicationCommandIDs::quit); +} + +void JUCEApplication::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) +{ + if (commandID == StandardApplicationCommandIDs::quit) + { + result.setInfo (TRANS("Quit"), + TRANS("Quits the application"), + "Application", 0); + + result.defaultKeypresses.add (KeyPress ('q', ModifierKeys::commandModifier, 0)); + } +} + +bool JUCEApplication::perform (const InvocationInfo& info) +{ + if (info.commandID == StandardApplicationCommandIDs::quit) + { + systemRequestedQuit(); + return true; + } + + return false; +} + +//============================================================================== +#if JUCE_MAC + extern void juce_initialiseMacMainMenu(); +#endif + +bool JUCEApplication::initialiseApp() +{ + if (JUCEApplicationBase::initialiseApp()) + { + #if JUCE_MAC + juce_initialiseMacMainMenu(); // (needs to get the app's name) + #endif + + return true; + } + + return false; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.h b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.h new file mode 100644 index 0000000..140cf9d --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.h @@ -0,0 +1,190 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATION_H_INCLUDED +#define JUCE_APPLICATION_H_INCLUDED + + +//============================================================================== +/** + An instance of this class is used to specify initialisation and shutdown + code for the application. + + Any application that wants to run an event loop must declare a subclass of + JUCEApplicationBase or JUCEApplication, and implement its various pure virtual + methods. + + It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file + to declare an instance of this class and generate suitable platform-specific + boilerplate code to launch the app. + + Note that this class is derived from JUCEApplicationBase, which contains most + of the useful methods and functionality. This derived class is here simply as + a convenient way to also inherit from an ApplicationCommandTarget, and to implement + default versions of some of the pure virtual base class methods. But you can derive + your app object directly from JUCEApplicationBase if you want to, and by doing so + can avoid having a dependency on the juce_gui_basics module. + + e.g. @code + class MyJUCEApp : public JUCEApplication + { + public: + MyJUCEApp() {} + ~MyJUCEApp() {} + + void initialise (const String& commandLine) override + { + myMainWindow = new MyApplicationWindow(); + myMainWindow->setBounds (100, 100, 400, 500); + myMainWindow->setVisible (true); + } + + void shutdown() override + { + myMainWindow = nullptr; + } + + const String getApplicationName() override + { + return "Super JUCE-o-matic"; + } + + const String getApplicationVersion() override + { + return "1.0"; + } + + private: + ScopedPointer myMainWindow; + }; + + // this generates boilerplate code to launch our app class: + START_JUCE_APPLICATION (MyJUCEApp) + @endcode + + @see JUCEApplicationBase, START_JUCE_APPLICATION +*/ +class JUCE_API JUCEApplication : public JUCEApplicationBase, + public ApplicationCommandTarget +{ +public: + //============================================================================== + /** Constructs a JUCE app object. + + If subclasses implement a constructor or destructor, they shouldn't call any + JUCE code in there - put your startup/shutdown code in initialise() and + shutdown() instead. + */ + JUCEApplication(); + + /** Destructor. + + If subclasses implement a constructor or destructor, they shouldn't call any + JUCE code in there - put your startup/shutdown code in initialise() and + shutdown() instead. + */ + ~JUCEApplication(); + + //============================================================================== + /** Returns the global instance of the application object being run. */ + static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept; + + //============================================================================== + /** Returns the application's name. */ + virtual const String getApplicationName() = 0; + + /** Returns the application's version number. */ + virtual const String getApplicationVersion() = 0; + + /** Checks whether multiple instances of the app are allowed. + + If you application class returns true for this, more than one instance is + permitted to run (except on OSX where the OS automatically stops you launching + a second instance of an app without explicitly starting it from the command-line). + + If it's false, the second instance won't start, but it you will still get a + callback to anotherInstanceStarted() to tell you about this - which + gives you a chance to react to what the user was trying to do. + */ + bool moreThanOneInstanceAllowed() override; + + /** Indicates that the user has tried to start up another instance of the app. + This will get called even if moreThanOneInstanceAllowed() is false. + */ + void anotherInstanceStarted (const String& commandLine) override; + + /** Called when the operating system is trying to close the application. + + The default implementation of this method is to call quit(), but it may + be overloaded to ignore the request or do some other special behaviour + instead. For example, you might want to offer the user the chance to save + their changes before quitting, and give them the chance to cancel. + + If you want to send a quit signal to your app, this is the correct method + to call, because it means that requests that come from the system get handled + in the same way as those from your own application code. So e.g. you'd + call this method from a "quit" item on a menu bar. + */ + void systemRequestedQuit() override; + + /** This method is called when the application is being put into background mode + by the operating system. + */ + void suspended() override; + + /** This method is called when the application is being woken from background mode + by the operating system. + */ + void resumed() override; + + /** If any unhandled exceptions make it through to the message dispatch loop, this + callback will be triggered, in case you want to log them or do some other + type of error-handling. + + If the type of exception is derived from the std::exception class, the pointer + passed-in will be valid. If the exception is of unknown type, this pointer + will be null. + */ + void unhandledException (const std::exception* e, + const String& sourceFilename, + int lineNumber) override; + + //============================================================================== + /** @internal */ + ApplicationCommandTarget* getNextCommandTarget(); + /** @internal */ + void getCommandInfo (CommandID, ApplicationCommandInfo&); + /** @internal */ + void getAllCommands (Array&); + /** @internal */ + bool perform (const InvocationInfo&); + +private: + bool initialiseApp() override; + + JUCE_DECLARE_NON_COPYABLE (JUCEApplication) +}; + + +#endif // JUCE_APPLICATION_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.cpp new file mode 100644 index 0000000..75f8590 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.cpp @@ -0,0 +1,45 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ArrowButton::ArrowButton (const String& name, float arrowDirectionInRadians, Colour arrowColour) + : Button (name), colour (arrowColour) +{ + path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); + path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * arrowDirectionInRadians, 0.5f, 0.5f)); +} + +ArrowButton::~ArrowButton() {} + +void ArrowButton::paintButton (Graphics& g, bool /*isMouseOverButton*/, bool isButtonDown) +{ + Path p (path); + + const float offset = isButtonDown ? 1.0f : 0.0f; + p.applyTransform (path.getTransformToScaleToFit (offset, offset, getWidth() - 3.0f, getHeight() - 3.0f, false)); + + DropShadow (Colours::black.withAlpha (0.3f), isButtonDown ? 2 : 4, Point()).drawForPath (g, p); + + g.setColour (colour); + g.fillPath (p); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.h new file mode 100644 index 0000000..5d64123 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ArrowButton.h @@ -0,0 +1,64 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_ARROWBUTTON_H_INCLUDED +#define JUCE_ARROWBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button with an arrow in it. + + @see Button +*/ +class JUCE_API ArrowButton : public Button +{ +public: + //============================================================================== + /** Creates an ArrowButton. + + @param buttonName the name to give the button + @param arrowDirection the direction the arrow should point in, where 0.0 is + pointing right, 0.25 is down, 0.5 is left, 0.75 is up + @param arrowColour the colour to use for the arrow + */ + ArrowButton (const String& buttonName, + float arrowDirection, + Colour arrowColour); + + /** Destructor. */ + ~ArrowButton(); + + /** @internal */ + void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; + +private: + Colour colour; + Path path; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArrowButton) +}; + + +#endif // JUCE_ARROWBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp new file mode 100644 index 0000000..10d6e03 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp @@ -0,0 +1,674 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class Button::CallbackHelper : public Timer, + public ApplicationCommandManagerListener, + public ValueListener, + public KeyListener +{ +public: + CallbackHelper (Button& b) : button (b) {} + + void timerCallback() override + { + button.repeatTimerCallback(); + } + + bool keyStateChanged (bool, Component*) override + { + return button.keyStateChangedCallback(); + } + + void valueChanged (Value& value) override + { + if (value.refersToSameSourceAs (button.isOn)) + button.setToggleState (button.isOn.getValue(), sendNotification); + } + + bool keyPressed (const KeyPress&, Component*) override + { + // returning true will avoid forwarding events for keys that we're using as shortcuts + return button.isShortcutPressed(); + } + + void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) override + { + if (info.commandID == button.commandID + && (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0) + button.flashButtonState(); + } + + void applicationCommandListChanged() override + { + button.applicationCommandListChangeCallback(); + } + +private: + Button& button; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHelper) +}; + +//============================================================================== +Button::Button (const String& name) + : Component (name), + text (name), + buttonPressTime (0), + lastRepeatTime (0), + commandManagerToUse (nullptr), + autoRepeatDelay (-1), + autoRepeatSpeed (0), + autoRepeatMinimumDelay (-1), + radioGroupId (0), + connectedEdgeFlags (0), + commandID(), + buttonState (buttonNormal), + lastToggleState (false), + clickTogglesState (false), + needsToRelease (false), + needsRepainting (false), + isKeyDown (false), + triggerOnMouseDown (false), + generateTooltip (false) +{ + callbackHelper = new CallbackHelper (*this); + + setWantsKeyboardFocus (true); + isOn.addListener (callbackHelper); +} + +Button::~Button() +{ + clearShortcuts(); + + if (commandManagerToUse != nullptr) + commandManagerToUse->removeListener (callbackHelper); + + isOn.removeListener (callbackHelper); + callbackHelper = nullptr; +} + +//============================================================================== +void Button::setButtonText (const String& newText) +{ + if (text != newText) + { + text = newText; + repaint(); + } +} + +void Button::setTooltip (const String& newTooltip) +{ + SettableTooltipClient::setTooltip (newTooltip); + generateTooltip = false; +} + +void Button::updateAutomaticTooltip (const ApplicationCommandInfo& info) +{ + if (generateTooltip && commandManagerToUse != nullptr) + { + String tt (info.description.isNotEmpty() ? info.description + : info.shortName); + + Array keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID)); + + for (int i = 0; i < keyPresses.size(); ++i) + { + const String key (keyPresses.getReference(i).getTextDescription()); + + tt << " ["; + + if (key.length() == 1) + tt << TRANS("shortcut") << ": '" << key << "']"; + else + tt << key << ']'; + } + + SettableTooltipClient::setTooltip (tt); + } +} + +void Button::setConnectedEdges (const int newFlags) +{ + if (connectedEdgeFlags != newFlags) + { + connectedEdgeFlags = newFlags; + repaint(); + } +} + +//============================================================================== +void Button::setToggleState (const bool shouldBeOn, const NotificationType notification) +{ + if (shouldBeOn != lastToggleState) + { + WeakReference deletionWatcher (this); + + if (shouldBeOn) + { + turnOffOtherButtonsInGroup (notification); + + if (deletionWatcher == nullptr) + return; + } + + if (getToggleState() != shouldBeOn) // this test means that if the value is void rather than explicitly set to + isOn = shouldBeOn; // false, it won't be changed unless the required value is true. + + lastToggleState = shouldBeOn; + repaint(); + + if (notification != dontSendNotification) + { + // async callbacks aren't possible here + jassert (notification != sendNotificationAsync); + + sendClickMessage (ModifierKeys::getCurrentModifiers()); + + if (deletionWatcher == nullptr) + return; + } + + if (notification != dontSendNotification) + sendStateMessage(); + else + buttonStateChanged(); + } +} + +void Button::setToggleState (const bool shouldBeOn, bool sendChange) +{ + setToggleState (shouldBeOn, sendChange ? sendNotification : dontSendNotification); +} + +void Button::setClickingTogglesState (const bool shouldToggle) noexcept +{ + clickTogglesState = shouldToggle; + + // if you've got clickTogglesState turned on, you shouldn't also connect the button + // up to be a command invoker. Instead, your command handler must flip the state of whatever + // it is that this button represents, and the button will update its state to reflect this + // in the applicationCommandListChanged() method. + jassert (commandManagerToUse == nullptr || ! clickTogglesState); +} + +bool Button::getClickingTogglesState() const noexcept +{ + return clickTogglesState; +} + +void Button::setRadioGroupId (const int newGroupId, NotificationType notification) +{ + if (radioGroupId != newGroupId) + { + radioGroupId = newGroupId; + + if (lastToggleState) + turnOffOtherButtonsInGroup (notification); + } +} + +void Button::turnOffOtherButtonsInGroup (const NotificationType notification) +{ + if (Component* const p = getParentComponent()) + { + if (radioGroupId != 0) + { + WeakReference deletionWatcher (this); + + for (int i = p->getNumChildComponents(); --i >= 0;) + { + Component* const c = p->getChildComponent (i); + + if (c != this) + { + if (Button* const b = dynamic_cast (c)) + { + if (b->getRadioGroupId() == radioGroupId) + { + b->setToggleState (false, notification); + + if (deletionWatcher == nullptr) + return; + } + } + } + } + } + } +} + +//============================================================================== +void Button::enablementChanged() +{ + updateState(); + repaint(); +} + +Button::ButtonState Button::updateState() +{ + return updateState (isMouseOver (true), isMouseButtonDown()); +} + +Button::ButtonState Button::updateState (const bool over, const bool down) +{ + ButtonState newState = buttonNormal; + + if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent()) + { + if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown) + newState = buttonDown; + else if (over) + newState = buttonOver; + } + + setState (newState); + return newState; +} + +void Button::setState (const ButtonState newState) +{ + if (buttonState != newState) + { + buttonState = newState; + repaint(); + + if (buttonState == buttonDown) + { + buttonPressTime = Time::getApproximateMillisecondCounter(); + lastRepeatTime = 0; + } + + sendStateMessage(); + } +} + +bool Button::isDown() const noexcept { return buttonState == buttonDown; } +bool Button::isOver() const noexcept { return buttonState != buttonNormal; } + +void Button::buttonStateChanged() {} + +uint32 Button::getMillisecondsSinceButtonDown() const noexcept +{ + const uint32 now = Time::getApproximateMillisecondCounter(); + return now > buttonPressTime ? now - buttonPressTime : 0; +} + +void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) noexcept +{ + triggerOnMouseDown = isTriggeredOnMouseDown; +} + +//============================================================================== +void Button::clicked() +{ +} + +void Button::clicked (const ModifierKeys&) +{ + clicked(); +} + +enum { clickMessageId = 0x2f3f4f99 }; + +void Button::triggerClick() +{ + postCommandMessage (clickMessageId); +} + +void Button::internalClickCallback (const ModifierKeys& modifiers) +{ + if (clickTogglesState) + { + const bool shouldBeOn = (radioGroupId != 0 || ! lastToggleState); + + if (shouldBeOn != getToggleState()) + { + setToggleState (shouldBeOn, sendNotification); + return; + } + } + + sendClickMessage (modifiers); +} + +void Button::flashButtonState() +{ + if (isEnabled()) + { + needsToRelease = true; + setState (buttonDown); + callbackHelper->startTimer (100); + } +} + +void Button::handleCommandMessage (int commandId) +{ + if (commandId == clickMessageId) + { + if (isEnabled()) + { + flashButtonState(); + internalClickCallback (ModifierKeys::getCurrentModifiers()); + } + } + else + { + Component::handleCommandMessage (commandId); + } +} + +//============================================================================== +void Button::addListener (ButtonListener* const newListener) +{ + buttonListeners.add (newListener); +} + +void Button::removeListener (ButtonListener* const listener) +{ + buttonListeners.remove (listener); +} + +void Button::sendClickMessage (const ModifierKeys& modifiers) +{ + Component::BailOutChecker checker (this); + + if (commandManagerToUse != nullptr && commandID != 0) + { + ApplicationCommandTarget::InvocationInfo info (commandID); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton; + info.originatingComponent = this; + + commandManagerToUse->invoke (info, true); + } + + clicked (modifiers); + + if (! checker.shouldBailOut()) + buttonListeners.callChecked (checker, &ButtonListener::buttonClicked, this); // (can't use Button::Listener due to idiotic VC2005 bug) +} + +void Button::sendStateMessage() +{ + Component::BailOutChecker checker (this); + + buttonStateChanged(); + + if (! checker.shouldBailOut()) + buttonListeners.callChecked (checker, &ButtonListener::buttonStateChanged, this); +} + +//============================================================================== +void Button::paint (Graphics& g) +{ + if (needsToRelease && isEnabled()) + { + needsToRelease = false; + needsRepainting = true; + } + + paintButton (g, isOver(), isDown()); +} + +//============================================================================== +void Button::mouseEnter (const MouseEvent&) { updateState (true, false); } +void Button::mouseExit (const MouseEvent&) { updateState (false, false); } + +void Button::mouseDown (const MouseEvent& e) +{ + updateState (true, true); + + if (isDown()) + { + if (autoRepeatDelay >= 0) + callbackHelper->startTimer (autoRepeatDelay); + + if (triggerOnMouseDown) + internalClickCallback (e.mods); + } +} + +void Button::mouseUp (const MouseEvent& e) +{ + const bool wasDown = isDown(); + const bool wasOver = isOver(); + updateState (isMouseOver(), false); + + if (wasDown && wasOver && ! triggerOnMouseDown) + internalClickCallback (e.mods); +} + +void Button::mouseDrag (const MouseEvent&) +{ + const ButtonState oldState = buttonState; + updateState (isMouseOver(), true); + + if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) + callbackHelper->startTimer (autoRepeatSpeed); +} + +void Button::focusGained (FocusChangeType) +{ + updateState(); + repaint(); +} + +void Button::focusLost (FocusChangeType) +{ + updateState(); + repaint(); +} + +void Button::visibilityChanged() +{ + needsToRelease = false; + updateState(); +} + +void Button::parentHierarchyChanged() +{ + Component* const newKeySource = (shortcuts.size() == 0) ? nullptr : getTopLevelComponent(); + + if (newKeySource != keySource.get()) + { + if (keySource != nullptr) + keySource->removeKeyListener (callbackHelper); + + keySource = newKeySource; + + if (keySource != nullptr) + keySource->addKeyListener (callbackHelper); + } +} + +//============================================================================== +void Button::setCommandToTrigger (ApplicationCommandManager* const newCommandManager, + const CommandID newCommandID, const bool generateTip) +{ + commandID = newCommandID; + generateTooltip = generateTip; + + if (commandManagerToUse != newCommandManager) + { + if (commandManagerToUse != nullptr) + commandManagerToUse->removeListener (callbackHelper); + + commandManagerToUse = newCommandManager; + + if (commandManagerToUse != nullptr) + commandManagerToUse->addListener (callbackHelper); + + // if you've got clickTogglesState turned on, you shouldn't also connect the button + // up to be a command invoker. Instead, your command handler must flip the state of whatever + // it is that this button represents, and the button will update its state to reflect this + // in the applicationCommandListChanged() method. + jassert (commandManagerToUse == nullptr || ! clickTogglesState); + } + + if (commandManagerToUse != nullptr) + applicationCommandListChangeCallback(); + else + setEnabled (true); +} + +void Button::applicationCommandListChangeCallback() +{ + if (commandManagerToUse != nullptr) + { + ApplicationCommandInfo info (0); + + if (commandManagerToUse->getTargetForCommand (commandID, info) != nullptr) + { + updateAutomaticTooltip (info); + setEnabled ((info.flags & ApplicationCommandInfo::isDisabled) == 0); + setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, dontSendNotification); + } + else + { + setEnabled (false); + } + } +} + +//============================================================================== +void Button::addShortcut (const KeyPress& key) +{ + if (key.isValid()) + { + jassert (! isRegisteredForShortcut (key)); // already registered! + + shortcuts.add (key); + parentHierarchyChanged(); + } +} + +void Button::clearShortcuts() +{ + shortcuts.clear(); + parentHierarchyChanged(); +} + +bool Button::isShortcutPressed() const +{ + if (isShowing() && ! isCurrentlyBlockedByAnotherModalComponent()) + for (int i = shortcuts.size(); --i >= 0;) + if (shortcuts.getReference(i).isCurrentlyDown()) + return true; + + return false; +} + +bool Button::isRegisteredForShortcut (const KeyPress& key) const +{ + for (int i = shortcuts.size(); --i >= 0;) + if (key == shortcuts.getReference(i)) + return true; + + return false; +} + +bool Button::keyStateChangedCallback() +{ + if (! isEnabled()) + return false; + + const bool wasDown = isKeyDown; + isKeyDown = isShortcutPressed(); + + if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown)) + callbackHelper->startTimer (autoRepeatDelay); + + updateState(); + + if (isEnabled() && wasDown && ! isKeyDown) + { + internalClickCallback (ModifierKeys::getCurrentModifiers()); + + // (return immediately - this button may now have been deleted) + return true; + } + + return wasDown || isKeyDown; +} + +bool Button::keyPressed (const KeyPress& key) +{ + if (isEnabled() && key.isKeyCode (KeyPress::returnKey)) + { + triggerClick(); + return true; + } + + return false; +} + +//============================================================================== +void Button::setRepeatSpeed (const int initialDelayMillisecs, + const int repeatMillisecs, + const int minimumDelayInMillisecs) noexcept +{ + autoRepeatDelay = initialDelayMillisecs; + autoRepeatSpeed = repeatMillisecs; + autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs); +} + +void Button::repeatTimerCallback() +{ + if (needsRepainting) + { + callbackHelper->stopTimer(); + updateState(); + needsRepainting = false; + } + else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown))) + { + int repeatSpeed = autoRepeatSpeed; + + if (autoRepeatMinimumDelay >= 0) + { + double timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0); + timeHeldDown *= timeHeldDown; + + repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed)); + } + + repeatSpeed = jmax (1, repeatSpeed); + + const uint32 now = Time::getMillisecondCounter(); + + // if we've been blocked from repeating often enough, speed up the repeat timer to compensate.. + if (lastRepeatTime != 0 && (int) (now - lastRepeatTime) > repeatSpeed * 2) + repeatSpeed = jmax (1, repeatSpeed / 2); + + lastRepeatTime = now; + callbackHelper->startTimer (repeatSpeed); + + internalClickCallback (ModifierKeys::getCurrentModifiers()); + } + else if (! needsToRelease) + { + callbackHelper->stopTimer(); + } +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h new file mode 100644 index 0000000..a58342d --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h @@ -0,0 +1,514 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_BUTTON_H_INCLUDED +#define JUCE_BUTTON_H_INCLUDED + + +//============================================================================== +/** + A base class for buttons. + + This contains all the logic for button behaviours such as enabling/disabling, + responding to shortcut keystrokes, auto-repeating when held down, toggle-buttons + and radio groups, etc. + + @see TextButton, DrawableButton, ToggleButton +*/ +class JUCE_API Button : public Component, + public SettableTooltipClient +{ +protected: + //============================================================================== + /** Creates a button. + + @param buttonName the text to put in the button (the component's name is also + initially set to this string, but these can be changed later + using the setName() and setButtonText() methods) + */ + explicit Button (const String& buttonName); + +public: + /** Destructor. */ + virtual ~Button(); + + //============================================================================== + /** Changes the button's text. + @see getButtonText + */ + void setButtonText (const String& newText); + + /** Returns the text displayed in the button. + @see setButtonText + */ + const String& getButtonText() const { return text; } + + //============================================================================== + /** Returns true if the button is currently being held down. + @see isOver + */ + bool isDown() const noexcept; + + /** Returns true if the mouse is currently over the button. + This will be also be true if the button is being held down. + @see isDown + */ + bool isOver() const noexcept; + + //============================================================================== + /** A button has an on/off state associated with it, and this changes that. + + By default buttons are 'off' and for simple buttons that you click to perform + an action you won't change this. Toggle buttons, however will want to + change their state when turned on or off. + + @param shouldBeOn whether to set the button's toggle state to be on or + off. If it's a member of a button group, this will + always try to turn it on, and to turn off any other + buttons in the group + @param notification determines the behaviour if the value changes - this + can invoke a synchronous call to clicked(), but + sendNotificationAsync is not supported + @see getToggleState, setRadioGroupId + */ + void setToggleState (bool shouldBeOn, NotificationType notification); + + /** Returns true if the button is 'on'. + + By default buttons are 'off' and for simple buttons that you click to perform + an action you won't change this. Toggle buttons, however will want to + change their state when turned on or off. + + @see setToggleState + */ + bool getToggleState() const noexcept { return isOn.getValue(); } + + /** Returns the Value object that represents the botton's toggle state. + You can use this Value object to connect the button's state to external values or setters, + either by taking a copy of the Value, or by using Value::referTo() to make it point to + your own Value object. + @see getToggleState, Value + */ + Value& getToggleStateValue() noexcept { return isOn; } + + /** This tells the button to automatically flip the toggle state when + the button is clicked. + + If set to true, then before the clicked() callback occurs, the toggle-state + of the button is flipped. + */ + void setClickingTogglesState (bool shouldAutoToggleOnClick) noexcept; + + /** Returns true if this button is set to be an automatic toggle-button. + This returns the last value that was passed to setClickingTogglesState(). + */ + bool getClickingTogglesState() const noexcept; + + //============================================================================== + /** Enables the button to act as a member of a mutually-exclusive group + of 'radio buttons'. + + If the group ID is set to a non-zero number, then this button will + act as part of a group of buttons with the same ID, only one of + which can be 'on' at the same time. Note that when it's part of + a group, clicking a toggle-button that's 'on' won't turn it off. + + To find other buttons with the same ID, this button will search through + its sibling components for ToggleButtons, so all the buttons for a + particular group must be placed inside the same parent component. + + Set the group ID back to zero if you want it to act as a normal toggle + button again. + + The notification argument lets you specify how other buttons should react + to being turned on or off in response to this call. + + @see getRadioGroupId + */ + void setRadioGroupId (int newGroupId, NotificationType notification = sendNotification); + + /** Returns the ID of the group to which this button belongs. + (See setRadioGroupId() for an explanation of this). + */ + int getRadioGroupId() const noexcept { return radioGroupId; } + + //============================================================================== + /** + Used to receive callbacks when a button is clicked. + + @see Button::addListener, Button::removeListener + */ + class JUCE_API Listener + { + public: + /** Destructor. */ + virtual ~Listener() {} + + /** Called when the button is clicked. */ + virtual void buttonClicked (Button*) = 0; + + /** Called when the button's state changes. */ + virtual void buttonStateChanged (Button*) {} + }; + + /** Registers a listener to receive events when this button's state changes. + If the listener is already registered, this will not register it again. + @see removeListener + */ + void addListener (Listener* newListener); + + /** Removes a previously-registered button listener + @see addListener + */ + void removeListener (Listener* listener); + + //============================================================================== + /** Causes the button to act as if it's been clicked. + + This will asynchronously make the button draw itself going down and up, and + will then call back the clicked() method as if mouse was clicked on it. + + @see clicked + */ + virtual void triggerClick(); + + //============================================================================== + /** Sets a command ID for this button to automatically invoke when it's clicked. + + When the button is pressed, it will use the given manager to trigger the + command ID. + + Obviously be careful that the ApplicationCommandManager doesn't get deleted + before this button is. To disable the command triggering, call this method and + pass nullptr as the command manager. + + If generateTooltip is true, then the button's tooltip will be automatically + generated based on the name of this command and its current shortcut key. + + @see addShortcut, getCommandID + */ + void setCommandToTrigger (ApplicationCommandManager* commandManagerToUse, + CommandID commandID, + bool generateTooltip); + + /** Returns the command ID that was set by setCommandToTrigger(). */ + CommandID getCommandID() const noexcept { return commandID; } + + //============================================================================== + /** Assigns a shortcut key to trigger the button. + + The button registers itself with its top-level parent component for keypresses. + + Note that a different way of linking buttons to keypresses is by using the + setCommandToTrigger() method to invoke a command. + + @see clearShortcuts + */ + void addShortcut (const KeyPress&); + + /** Removes all key shortcuts that had been set for this button. + @see addShortcut + */ + void clearShortcuts(); + + /** Returns true if the given keypress is a shortcut for this button. + @see addShortcut + */ + bool isRegisteredForShortcut (const KeyPress&) const; + + //============================================================================== + /** Sets an auto-repeat speed for the button when it is held down. + + (Auto-repeat is disabled by default). + + @param initialDelayInMillisecs how long to wait after the mouse is pressed before + triggering the next click. If this is zero, auto-repeat + is disabled + @param repeatDelayInMillisecs the frequently subsequent repeated clicks should be + triggered + @param minimumDelayInMillisecs if this is greater than 0, the auto-repeat speed will + get faster, the longer the button is held down, up to the + minimum interval specified here + */ + void setRepeatSpeed (int initialDelayInMillisecs, + int repeatDelayInMillisecs, + int minimumDelayInMillisecs = -1) noexcept; + + /** Sets whether the button click should happen when the mouse is pressed or released. + + By default the button is only considered to have been clicked when the mouse is + released, but setting this to true will make it call the clicked() method as soon + as the button is pressed. + + This is useful if the button is being used to show a pop-up menu, as it allows + the click to be used as a drag onto the menu. + */ + void setTriggeredOnMouseDown (bool isTriggeredOnMouseDown) noexcept; + + /** Returns the number of milliseconds since the last time the button + went into the 'down' state. + */ + uint32 getMillisecondsSinceButtonDown() const noexcept; + + //============================================================================== + /** Sets the tooltip for this button. + @see TooltipClient, TooltipWindow + */ + void setTooltip (const String& newTooltip) override; + + //============================================================================== + /** A combination of these flags are used by setConnectedEdges(). */ + enum ConnectedEdgeFlags + { + ConnectedOnLeft = 1, + ConnectedOnRight = 2, + ConnectedOnTop = 4, + ConnectedOnBottom = 8 + }; + + /** Hints about which edges of the button might be connected to adjoining buttons. + + The value passed in is a bitwise combination of any of the values in the + ConnectedEdgeFlags enum. + + E.g. if you are placing two buttons adjacent to each other, you could use this to + indicate which edges are touching, and the LookAndFeel might choose to drawn them + without rounded corners on the edges that connect. It's only a hint, so the + LookAndFeel can choose to ignore it if it's not relevent for this type of + button. + */ + void setConnectedEdges (int connectedEdgeFlags); + + /** Returns the set of flags passed into setConnectedEdges(). */ + int getConnectedEdgeFlags() const noexcept { return connectedEdgeFlags; } + + /** Indicates whether the button adjoins another one on its left edge. + @see setConnectedEdges + */ + bool isConnectedOnLeft() const noexcept { return (connectedEdgeFlags & ConnectedOnLeft) != 0; } + + /** Indicates whether the button adjoins another one on its right edge. + @see setConnectedEdges + */ + bool isConnectedOnRight() const noexcept { return (connectedEdgeFlags & ConnectedOnRight) != 0; } + + /** Indicates whether the button adjoins another one on its top edge. + @see setConnectedEdges + */ + bool isConnectedOnTop() const noexcept { return (connectedEdgeFlags & ConnectedOnTop) != 0; } + + /** Indicates whether the button adjoins another one on its bottom edge. + @see setConnectedEdges + */ + bool isConnectedOnBottom() const noexcept { return (connectedEdgeFlags & ConnectedOnBottom) != 0; } + + + //============================================================================== + /** Used by setState(). */ + enum ButtonState + { + buttonNormal, + buttonOver, + buttonDown + }; + + /** Can be used to force the button into a particular state. + + This only changes the button's appearance, it won't trigger a click, or stop any mouse-clicks + from happening. + + The state that you set here will only last until it is automatically changed when the mouse + enters or exits the button, or the mouse-button is pressed or released. + */ + void setState (ButtonState newState); + + /** Returns the button's current over/down/up state. */ + ButtonState getState() const noexcept { return buttonState; } + + // This method's parameters have changed - see the new version. + JUCE_DEPRECATED (void setToggleState (bool, bool)); + + //============================================================================== + /** This abstract base class is implemented by LookAndFeel classes to provide + button-drawing functionality. + */ + struct JUCE_API LookAndFeelMethods + { + virtual ~LookAndFeelMethods() {} + + virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour, + bool isMouseOverButton, bool isButtonDown) = 0; + + virtual Font getTextButtonFont (TextButton&, int buttonHeight) = 0; + virtual int getTextButtonWidthToFitText (TextButton&, int buttonHeight) = 0; + + /** Draws the text for a TextButton. */ + virtual void drawButtonText (Graphics&, TextButton&, bool isMouseOverButton, bool isButtonDown) = 0; + + /** Draws the contents of a standard ToggleButton. */ + virtual void drawToggleButton (Graphics&, ToggleButton&, bool isMouseOverButton, bool isButtonDown) = 0; + + virtual void changeToggleButtonWidthToFitText (ToggleButton&) = 0; + + virtual void drawTickBox (Graphics&, Component&, float x, float y, float w, float h, + bool ticked, bool isEnabled, bool isMouseOverButton, bool isButtonDown) = 0; + + virtual void drawDrawableButton (Graphics&, DrawableButton&, bool isMouseOverButton, bool isButtonDown) = 0; + + private: + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // These method have been deprecated: see their replacements above. + virtual int getTextButtonFont (TextButton&) { return 0; } + virtual int changeTextButtonWidthToFitText (TextButton&, int) { return 0; } + #endif + }; + +protected: + //============================================================================== + /** This method is called when the button has been clicked. + + Subclasses can override this to perform whatever they actions they need + to do. + + Alternatively, a ButtonListener can be added to the button, and these listeners + will be called when the click occurs. + + @see triggerClick + */ + virtual void clicked(); + + /** This method is called when the button has been clicked. + + By default it just calls clicked(), but you might want to override it to handle + things like clicking when a modifier key is pressed, etc. + + @see ModifierKeys + */ + virtual void clicked (const ModifierKeys& modifiers); + + /** Subclasses should override this to actually paint the button's contents. + + It's better to use this than the paint method, because it gives you information + about the over/down state of the button. + + @param g the graphics context to use + @param isMouseOverButton true if the button is either in the 'over' or + 'down' state + @param isButtonDown true if the button should be drawn in the 'down' position + */ + virtual void paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) = 0; + + /** Called when the button's up/down/over state changes. + + Subclasses can override this if they need to do something special when the button + goes up or down. + + @see isDown, isOver + */ + virtual void buttonStateChanged(); + + //============================================================================== + /** @internal */ + virtual void internalClickCallback (const ModifierKeys&); + /** @internal */ + void handleCommandMessage (int commandId) override; + /** @internal */ + void mouseEnter (const MouseEvent&) override; + /** @internal */ + void mouseExit (const MouseEvent&) override; + /** @internal */ + void mouseDown (const MouseEvent&) override; + /** @internal */ + void mouseDrag (const MouseEvent&) override; + /** @internal */ + void mouseUp (const MouseEvent&) override; + /** @internal */ + bool keyPressed (const KeyPress&) override; + /** @internal */ + using Component::keyStateChanged; + /** @internal */ + void paint (Graphics&) override; + /** @internal */ + void parentHierarchyChanged() override; + /** @internal */ + void visibilityChanged() override; + /** @internal */ + void focusGained (FocusChangeType) override; + /** @internal */ + void focusLost (FocusChangeType) override; + /** @internal */ + void enablementChanged() override; + +private: + //============================================================================== + Array shortcuts; + WeakReference keySource; + String text; + ListenerList buttonListeners; + + class CallbackHelper; + friend class CallbackHelper; + friend struct ContainerDeletePolicy; + ScopedPointer callbackHelper; + uint32 buttonPressTime, lastRepeatTime; + ApplicationCommandManager* commandManagerToUse; + int autoRepeatDelay, autoRepeatSpeed, autoRepeatMinimumDelay; + int radioGroupId, connectedEdgeFlags; + CommandID commandID; + ButtonState buttonState; + + Value isOn; + bool lastToggleState; + bool clickTogglesState; + bool needsToRelease; + bool needsRepainting; + bool isKeyDown; + bool triggerOnMouseDown; + bool generateTooltip; + + void repeatTimerCallback(); + bool keyStateChangedCallback(); + void applicationCommandListChangeCallback(); + void updateAutomaticTooltip (const ApplicationCommandInfo&); + + ButtonState updateState(); + ButtonState updateState (bool isOver, bool isDown); + bool isShortcutPressed() const; + void turnOffOtherButtonsInGroup (NotificationType); + + void flashButtonState(); + void sendClickMessage (const ModifierKeys&); + void sendStateMessage(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Button) +}; + +#ifndef DOXYGEN + /** This typedef is just for compatibility with old code and VC6 - newer code should use Button::Listener instead. */ + typedef Button::Listener ButtonListener; +#endif + +#endif // JUCE_BUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.cpp new file mode 100644 index 0000000..83e9c89 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.cpp @@ -0,0 +1,221 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle) + : Button (name), + style (buttonStyle), + currentImage (nullptr), + edgeIndent (3) +{ +} + +DrawableButton::~DrawableButton() +{ +} + +//============================================================================== +static Drawable* copyDrawableIfNotNull (const Drawable* const d) +{ + return d != nullptr ? d->createCopy() : nullptr; +} + +void DrawableButton::setImages (const Drawable* normal, + const Drawable* over, + const Drawable* down, + const Drawable* disabled, + const Drawable* normalOn, + const Drawable* overOn, + const Drawable* downOn, + const Drawable* disabledOn) +{ + jassert (normal != nullptr); // you really need to give it at least a normal image.. + + normalImage = copyDrawableIfNotNull (normal); + overImage = copyDrawableIfNotNull (over); + downImage = copyDrawableIfNotNull (down); + disabledImage = copyDrawableIfNotNull (disabled); + normalImageOn = copyDrawableIfNotNull (normalOn); + overImageOn = copyDrawableIfNotNull (overOn); + downImageOn = copyDrawableIfNotNull (downOn); + disabledImageOn = copyDrawableIfNotNull (disabledOn); + + buttonStateChanged(); +} + +//============================================================================== +void DrawableButton::setButtonStyle (const DrawableButton::ButtonStyle newStyle) +{ + if (style != newStyle) + { + style = newStyle; + buttonStateChanged(); + } +} + +void DrawableButton::setEdgeIndent (const int numPixelsIndent) +{ + edgeIndent = numPixelsIndent; + repaint(); + resized(); +} + +Rectangle DrawableButton::getImageBounds() const +{ + Rectangle r (getLocalBounds()); + + if (style != ImageStretched) + { + int indentX = jmin (edgeIndent, proportionOfWidth (0.3f)); + int indentY = jmin (edgeIndent, proportionOfHeight (0.3f)); + + if (style == ImageOnButtonBackground) + { + indentX = jmax (getWidth() / 4, indentX); + indentY = jmax (getHeight() / 4, indentY); + } + else if (style == ImageAboveTextLabel) + { + r = r.withTrimmedBottom (jmin (16, proportionOfHeight (0.25f))); + } + + r = r.reduced (indentX, indentY); + } + + return r.toFloat(); +} + +void DrawableButton::resized() +{ + Button::resized(); + + if (currentImage != nullptr) + { + if (style == ImageRaw) + currentImage->setOriginWithOriginalSize (Point()); + else + currentImage->setTransformToFit (getImageBounds(), + style == ImageStretched ? RectanglePlacement::stretchToFit + : RectanglePlacement::centred); + } +} + +void DrawableButton::buttonStateChanged() +{ + repaint(); + + Drawable* imageToDraw = nullptr; + float opacity = 1.0f; + + if (isEnabled()) + { + imageToDraw = getCurrentImage(); + } + else + { + imageToDraw = getToggleState() ? disabledImageOn + : disabledImage; + + if (imageToDraw == nullptr) + { + opacity = 0.4f; + imageToDraw = getNormalImage(); + } + } + + if (imageToDraw != currentImage) + { + removeChildComponent (currentImage); + currentImage = imageToDraw; + + if (currentImage != nullptr) + { + currentImage->setInterceptsMouseClicks (false, false); + addAndMakeVisible (currentImage); + resized(); + } + } + + if (currentImage != nullptr) + currentImage->setAlpha (opacity); +} + +void DrawableButton::enablementChanged() +{ + Button::enablementChanged(); + buttonStateChanged(); +} + +void DrawableButton::colourChanged() +{ + repaint(); +} + +void DrawableButton::paintButton (Graphics& g, + const bool isMouseOverButton, + const bool isButtonDown) +{ + LookAndFeel& lf = getLookAndFeel(); + + if (style == ImageOnButtonBackground) + lf.drawButtonBackground (g, *this, + findColour (getToggleState() ? TextButton::buttonOnColourId + : TextButton::buttonColourId), + isMouseOverButton, isButtonDown); + else + lf.drawDrawableButton (g, *this, isMouseOverButton, isButtonDown); +} + +//============================================================================== +Drawable* DrawableButton::getCurrentImage() const noexcept +{ + if (isDown()) return getDownImage(); + if (isOver()) return getOverImage(); + + return getNormalImage(); +} + +Drawable* DrawableButton::getNormalImage() const noexcept +{ + return (getToggleState() && normalImageOn != nullptr) ? normalImageOn + : normalImage; +} + +Drawable* DrawableButton::getOverImage() const noexcept +{ + if (getToggleState()) + { + if (overImageOn != nullptr) return overImageOn; + if (normalImageOn != nullptr) return normalImageOn; + } + + return overImage != nullptr ? overImage : normalImage; +} + +Drawable* DrawableButton::getDownImage() const noexcept +{ + if (Drawable* const d = getToggleState() ? downImageOn : downImage) + return d; + + return getOverImage(); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.h new file mode 100644 index 0000000..f07616f --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_DrawableButton.h @@ -0,0 +1,188 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLEBUTTON_H_INCLUDED +#define JUCE_DRAWABLEBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button that displays a Drawable. + + Up to three Drawable objects can be given to this button, to represent the + 'normal', 'over' and 'down' states. + + @see Button +*/ +class JUCE_API DrawableButton : public Button +{ +public: + //============================================================================== + enum ButtonStyle + { + ImageFitted, /**< The button will just display the images, but will resize and centre them to fit inside it. */ + ImageRaw, /**< The button will just display the images in their normal size and position. + This leaves it up to the caller to make sure the images are the correct size and position for the button. */ + ImageAboveTextLabel, /**< Draws the button as a text label across the bottom with the image resized and scaled to fit above it. */ + ImageOnButtonBackground, /**< Draws the button as a standard rounded-rectangle button with the image on top. + Note that if you use this style, the colour IDs that control the button colour are + TextButton::buttonColourId and TextButton::buttonOnColourId. */ + ImageStretched /**< Fills the button with a stretched version of the image. */ + }; + + //============================================================================== + /** Creates a DrawableButton. + + After creating one of these, use setImages() to specify the drawables to use. + + @param buttonName the name to give the component + @param buttonStyle the layout to use + + @see ButtonStyle, setButtonStyle, setImages + */ + DrawableButton (const String& buttonName, + ButtonStyle buttonStyle); + + /** Destructor. */ + ~DrawableButton(); + + //============================================================================== + /** Sets up the images to draw for the various button states. + + The button will keep its own internal copies of these drawables. + + @param normalImage the thing to draw for the button's 'normal' state. An internal copy + will be made of the object passed-in if it is non-zero. + @param overImage the thing to draw for the button's 'over' state - if this is + zero, the button's normal image will be used when the mouse is + over it. An internal copy will be made of the object passed-in + if it is non-zero. + @param downImage the thing to draw for the button's 'down' state - if this is + zero, the 'over' image will be used instead (or the normal image + as a last resort). An internal copy will be made of the object + passed-in if it is non-zero. + @param disabledImage an image to draw when the button is disabled. If this is zero, + the normal image will be drawn with a reduced opacity instead. + An internal copy will be made of the object passed-in if it is + non-zero. + @param normalImageOn same as the normalImage, but this is used when the button's toggle + state is 'on'. If this is nullptr, the normal image is used instead + @param overImageOn same as the overImage, but this is used when the button's toggle + state is 'on'. If this is nullptr, the normalImageOn is drawn instead + @param downImageOn same as the downImage, but this is used when the button's toggle + state is 'on'. If this is nullptr, the overImageOn is drawn instead + @param disabledImageOn same as the disabledImage, but this is used when the button's toggle + state is 'on'. If this is nullptr, the normal image will be drawn instead + with a reduced opacity + */ + void setImages (const Drawable* normalImage, + const Drawable* overImage = nullptr, + const Drawable* downImage = nullptr, + const Drawable* disabledImage = nullptr, + const Drawable* normalImageOn = nullptr, + const Drawable* overImageOn = nullptr, + const Drawable* downImageOn = nullptr, + const Drawable* disabledImageOn = nullptr); + + + //============================================================================== + /** Changes the button's style. + @see ButtonStyle + */ + void setButtonStyle (ButtonStyle newStyle); + + /** Returns the current style. */ + ButtonStyle getStyle() const noexcept { return style; } + + //============================================================================== + /** Gives the button an optional amount of space around the edge of the drawable. + By default there's a gap of about 3 pixels. + */ + void setEdgeIndent (int numPixelsIndent); + + //============================================================================== + /** Returns the image that the button is currently displaying. */ + Drawable* getCurrentImage() const noexcept; + + /** Returns the image that the button will use for its normal state. */ + Drawable* getNormalImage() const noexcept; + /** Returns the image that the button will use when the mouse is over it. */ + Drawable* getOverImage() const noexcept; + /** Returns the image that the button will use when the mouse is held down on it. */ + Drawable* getDownImage() const noexcept; + + /** Can be overridden to specify a custom position for the image within the button. */ + virtual Rectangle getImageBounds() const; + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the link. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + Note that when the ImageOnButtonBackground style is used, the colour IDs that control + the button colour are TextButton::buttonColourId and TextButton::buttonOnColourId. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + textColourId = 0x1004010, /**< The colour to use for the button's text label. */ + textColourOnId = 0x1004013, /**< The colour to use for the button's text.when the button's toggle state is "on". */ + + backgroundColourId = 0x1004011, /**< The colour used to fill the button's background (when + the button is toggled 'off'). Note that if you use the + ImageOnButtonBackground style, you should use TextButton::buttonColourId + to change the button's colour. */ + backgroundOnColourId = 0x1004012, /**< The colour used to fill the button's background (when + the button is toggled 'on'). Note that if you use the + ImageOnButtonBackground style, you should use TextButton::buttonOnColourId + to change the button's colour. */ + }; + + //============================================================================== + /** @internal */ + void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; + /** @internal */ + void buttonStateChanged() override; + /** @internal */ + void resized() override; + /** @internal */ + void enablementChanged() override; + /** @internal */ + void colourChanged() override; + +private: + //============================================================================== + ButtonStyle style; + ScopedPointer normalImage, overImage, downImage, disabledImage, + normalImageOn, overImageOn, downImageOn, disabledImageOn; + Drawable* currentImage; + int edgeIndent; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton) +}; + + +#endif // JUCE_DRAWABLEBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp new file mode 100644 index 0000000..8274f4c --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp @@ -0,0 +1,109 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +HyperlinkButton::HyperlinkButton (const String& linkText, + const URL& linkURL) + : Button (linkText), + url (linkURL), + font (14.0f, Font::underlined), + resizeFont (true), + justification (Justification::centred) +{ + setMouseCursor (MouseCursor::PointingHandCursor); + setTooltip (linkURL.toString (false)); +} + +HyperlinkButton::HyperlinkButton () + : Button (String::empty), + font (14.0f, Font::underlined), + resizeFont (true), + justification (Justification::centred) +{ + setMouseCursor (MouseCursor::PointingHandCursor); +} + +HyperlinkButton::~HyperlinkButton() +{ +} + +//============================================================================== +void HyperlinkButton::setFont (const Font& newFont, + const bool resizeToMatchComponentHeight, + Justification justificationType) +{ + font = newFont; + resizeFont = resizeToMatchComponentHeight; + justification = justificationType; + repaint(); +} + +void HyperlinkButton::setURL (const URL& newURL) noexcept +{ + url = newURL; + setTooltip (newURL.toString (false)); +} + +Font HyperlinkButton::getFontToUse() const +{ + if (resizeFont) + return font.withHeight (getHeight() * 0.7f); + + return font; +} + +void HyperlinkButton::changeWidthToFitText() +{ + setSize (getFontToUse().getStringWidth (getButtonText()) + 6, getHeight()); +} + +void HyperlinkButton::colourChanged() +{ + repaint(); +} + +//============================================================================== +void HyperlinkButton::clicked() +{ + if (url.isWellFormed()) + url.launchInDefaultBrowser(); +} + +void HyperlinkButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + const Colour textColour (findColour (textColourId)); + + if (isEnabled()) + g.setColour ((isMouseOverButton) ? textColour.darker ((isButtonDown) ? 1.3f : 0.4f) + : textColour); + else + g.setColour (textColour.withMultipliedAlpha (0.4f)); + + g.setFont (getFontToUse()); + + g.drawText (getButtonText(), getLocalBounds().reduced (1, 0), + justification.getOnlyHorizontalFlags() | Justification::verticallyCentred, + true); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h new file mode 100644 index 0000000..4cd3f52 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h @@ -0,0 +1,114 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_HYPERLINKBUTTON_H_INCLUDED +#define JUCE_HYPERLINKBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button showing an underlined weblink, that will launch the link + when it's clicked. + + @see Button +*/ +class JUCE_API HyperlinkButton : public Button +{ +public: + //============================================================================== + /** Creates a HyperlinkButton. + + @param linkText the text that will be displayed in the button - this is + also set as the Component's name, but the text can be + changed later with the Button::getButtonText() method + @param linkURL the URL to launch when the user clicks the button + */ + HyperlinkButton (const String& linkText, + const URL& linkURL); + + /** Creates a HyperlinkButton. */ + HyperlinkButton(); + + /** Destructor. */ + ~HyperlinkButton(); + + //============================================================================== + /** Changes the font to use for the text. + + If resizeToMatchComponentHeight is true, the font's height will be adjusted + to match the size of the component. + */ + void setFont (const Font& newFont, + bool resizeToMatchComponentHeight, + Justification justificationType = Justification::horizontallyCentred); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the link. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + textColourId = 0x1001f00, /**< The colour to use for the URL text. */ + }; + + //============================================================================== + /** Changes the URL that the button will trigger. */ + void setURL (const URL& newURL) noexcept; + + /** Returns the URL that the button will trigger. */ + const URL& getURL() const noexcept { return url; } + + //============================================================================== + /** Resizes the button horizontally to fit snugly around the text. + + This won't affect the button's height. + */ + void changeWidthToFitText(); + +protected: + //============================================================================== + /** @internal */ + void clicked() override; + /** @internal */ + void colourChanged() override; + /** @internal */ + void paintButton (Graphics&, bool isMouseOver, bool isButtonDown) override; + +private: + //============================================================================== + URL url; + Font font; + bool resizeFont; + Justification justification; + + Font getFontToUse() const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HyperlinkButton) +}; + +#endif // JUCE_HYPERLINKBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.cpp new file mode 100644 index 0000000..3170d05 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.cpp @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ImageButton::ImageButton (const String& text_) + : Button (text_), + scaleImageToFit (true), + preserveProportions (true), + alphaThreshold (0) +{ +} + +ImageButton::~ImageButton() +{ +} + +void ImageButton::setImages (const bool resizeButtonNowToFitThisImage, + const bool rescaleImagesWhenButtonSizeChanges, + const bool preserveImageProportions, + const Image& normalImage_, + const float imageOpacityWhenNormal, + Colour overlayColourWhenNormal, + const Image& overImage_, + const float imageOpacityWhenOver, + Colour overlayColourWhenOver, + const Image& downImage_, + const float imageOpacityWhenDown, + Colour overlayColourWhenDown, + const float hitTestAlphaThreshold) +{ + normalImage = normalImage_; + overImage = overImage_; + downImage = downImage_; + + if (resizeButtonNowToFitThisImage && normalImage.isValid()) + { + imageBounds.setSize (normalImage.getWidth(), + normalImage.getHeight()); + + setSize (imageBounds.getWidth(), imageBounds.getHeight()); + } + + scaleImageToFit = rescaleImagesWhenButtonSizeChanges; + preserveProportions = preserveImageProportions; + + normalOpacity = imageOpacityWhenNormal; + normalOverlay = overlayColourWhenNormal; + overOpacity = imageOpacityWhenOver; + overOverlay = overlayColourWhenOver; + downOpacity = imageOpacityWhenDown; + downOverlay = overlayColourWhenDown; + + alphaThreshold = (uint8) jlimit (0, 0xff, roundToInt (255.0f * hitTestAlphaThreshold)); + + repaint(); +} + +Image ImageButton::getCurrentImage() const +{ + if (isDown() || getToggleState()) + return getDownImage(); + + if (isOver()) + return getOverImage(); + + return getNormalImage(); +} + +Image ImageButton::getNormalImage() const +{ + return normalImage; +} + +Image ImageButton::getOverImage() const +{ + return overImage.isValid() ? overImage + : normalImage; +} + +Image ImageButton::getDownImage() const +{ + return downImage.isValid() ? downImage + : getOverImage(); +} + +void ImageButton::paintButton (Graphics& g, + bool isMouseOverButton, + bool isButtonDown) +{ + if (! isEnabled()) + { + isMouseOverButton = false; + isButtonDown = false; + } + + Image im (getCurrentImage()); + + if (im.isValid()) + { + const int iw = im.getWidth(); + const int ih = im.getHeight(); + int w = getWidth(); + int h = getHeight(); + int x = (w - iw) / 2; + int y = (h - ih) / 2; + + if (scaleImageToFit) + { + if (preserveProportions) + { + int newW, newH; + const float imRatio = ih / (float) iw; + const float destRatio = h / (float) w; + + if (imRatio > destRatio) + { + newW = roundToInt (h / imRatio); + newH = h; + } + else + { + newW = w; + newH = roundToInt (w * imRatio); + } + + x = (w - newW) / 2; + y = (h - newH) / 2; + w = newW; + h = newH; + } + else + { + x = 0; + y = 0; + } + } + + if (! scaleImageToFit) + { + w = iw; + h = ih; + } + + imageBounds.setBounds (x, y, w, h); + + const bool useDownImage = isButtonDown || getToggleState(); + + getLookAndFeel().drawImageButton (g, &im, x, y, w, h, + useDownImage ? downOverlay + : (isMouseOverButton ? overOverlay + : normalOverlay), + useDownImage ? downOpacity + : (isMouseOverButton ? overOpacity + : normalOpacity), + *this); + } +} + +bool ImageButton::hitTest (int x, int y) +{ + if (alphaThreshold == 0) + return true; + + Image im (getCurrentImage()); + + return im.isNull() || ((! imageBounds.isEmpty()) + && alphaThreshold < im.getPixelAt (((x - imageBounds.getX()) * im.getWidth()) / imageBounds.getWidth(), + ((y - imageBounds.getY()) * im.getHeight()) / imageBounds.getHeight()).getAlpha()); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.h new file mode 100644 index 0000000..64db59d --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ImageButton.h @@ -0,0 +1,160 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_IMAGEBUTTON_H_INCLUDED +#define JUCE_IMAGEBUTTON_H_INCLUDED + + +//============================================================================== +/** + As the title suggests, this is a button containing an image. + + The colour and transparency of the image can be set to vary when the + button state changes. + + @see Button, ShapeButton, TextButton +*/ +class JUCE_API ImageButton : public Button +{ +public: + //============================================================================== + /** Creates an ImageButton. + + Use setImage() to specify the image to use. The colours and opacities that + are specified here can be changed later using setImages(). + + @param name the name to give the component + */ + explicit ImageButton (const String& name = String::empty); + + /** Destructor. */ + ~ImageButton(); + + //============================================================================== + /** Sets up the images to draw in various states. + + @param resizeButtonNowToFitThisImage if true, the button will be immediately + resized to the same dimensions as the normal image + @param rescaleImagesWhenButtonSizeChanges if true, the image will be rescaled to fit the + button when the button's size changes + @param preserveImageProportions if true then any rescaling of the image to fit + the button will keep the image's x and y proportions + correct - i.e. it won't distort its shape, although + this might create gaps around the edges + @param normalImage the image to use when the button is in its normal state. + button no longer needs it. + @param imageOpacityWhenNormal the opacity to use when drawing the normal image. + @param overlayColourWhenNormal an overlay colour to use to fill the alpha channel of the + normal image - if this colour is transparent, no overlay + will be drawn. The overlay will be drawn over the top of the + image, so you can basically add a solid or semi-transparent + colour to the image to brighten or darken it + @param overImage the image to use when the mouse is over the button. If + you want to use the same image as was set in the normalImage + parameter, this value can be a null image. + @param imageOpacityWhenOver the opacity to use when drawing the image when the mouse + is over the button + @param overlayColourWhenOver an overlay colour to use to fill the alpha channel of the + image when the mouse is over - if this colour is transparent, + no overlay will be drawn + @param downImage an image to use when the button is pressed down. If set + to a null image, the 'over' image will be drawn instead (or the + normal image if there isn't an 'over' image either). + @param imageOpacityWhenDown the opacity to use when drawing the image when the button + is pressed + @param overlayColourWhenDown an overlay colour to use to fill the alpha channel of the + image when the button is pressed down - if this colour is + transparent, no overlay will be drawn + @param hitTestAlphaThreshold if set to zero, the mouse is considered to be over the button + whenever it's inside the button's bounding rectangle. If + set to values higher than 0, the mouse will only be + considered to be over the image when the value of the + image's alpha channel at that position is greater than + this level. + */ + void setImages (bool resizeButtonNowToFitThisImage, + bool rescaleImagesWhenButtonSizeChanges, + bool preserveImageProportions, + const Image& normalImage, + float imageOpacityWhenNormal, + Colour overlayColourWhenNormal, + const Image& overImage, + float imageOpacityWhenOver, + Colour overlayColourWhenOver, + const Image& downImage, + float imageOpacityWhenDown, + Colour overlayColourWhenDown, + float hitTestAlphaThreshold = 0.0f); + + /** Returns the currently set 'normal' image. */ + Image getNormalImage() const; + + /** Returns the image that's drawn when the mouse is over the button. + + If a valid 'over' image has been set, this will return it; otherwise it'll + just return the normal image. + */ + Image getOverImage() const; + + /** Returns the image that's drawn when the button is held down. + + If a valid 'down' image has been set, this will return it; otherwise it'll + return the 'over' image or normal image, depending on what's available. + */ + Image getDownImage() const; + + //============================================================================== + /** This abstract base class is implemented by LookAndFeel classes. */ + struct JUCE_API LookAndFeelMethods + { + virtual ~LookAndFeelMethods() {} + + virtual void drawImageButton (Graphics&, Image*, + int imageX, int imageY, int imageW, int imageH, + const Colour& overlayColour, float imageOpacity, ImageButton&) = 0; + }; + +protected: + //============================================================================== + /** @internal */ + bool hitTest (int x, int y) override; + /** @internal */ + void paintButton (Graphics&, bool isMouseOver, bool isButtonDown) override; + +private: + //============================================================================== + bool scaleImageToFit, preserveProportions; + uint8 alphaThreshold; + Rectangle imageBounds; + Image normalImage, overImage, downImage; + float normalOpacity, overOpacity, downOpacity; + Colour normalOverlay, overOverlay, downOverlay; + + Image getCurrentImage() const; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageButton) +}; + + +#endif // JUCE_IMAGEBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp new file mode 100644 index 0000000..28cd8de --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.cpp @@ -0,0 +1,114 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ShapeButton::ShapeButton (const String& t, Colour n, Colour o, Colour d) + : Button (t), + normalColour (n), overColour (o), downColour (d), + maintainShapeProportions (false), + outlineWidth (0.0f) +{ +} + +ShapeButton::~ShapeButton() {} + +void ShapeButton::setColours (Colour newNormalColour, Colour newOverColour, Colour newDownColour) +{ + normalColour = newNormalColour; + overColour = newOverColour; + downColour = newDownColour; +} + +void ShapeButton::setOutline (Colour newOutlineColour, const float newOutlineWidth) +{ + outlineColour = newOutlineColour; + outlineWidth = newOutlineWidth; +} + +void ShapeButton::setBorderSize (BorderSize newBorder) +{ + border = newBorder; +} + +void ShapeButton::setShape (const Path& newShape, + const bool resizeNowToFitThisShape, + const bool maintainShapeProportions_, + const bool hasShadow) +{ + shape = newShape; + maintainShapeProportions = maintainShapeProportions_; + + shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point())); + setComponentEffect (hasShadow ? &shadow : nullptr); + + if (resizeNowToFitThisShape) + { + Rectangle newBounds (shape.getBounds()); + + if (hasShadow) + newBounds = newBounds.expanded (4.0f); + + shape.applyTransform (AffineTransform::translation (-newBounds.getX(), + -newBounds.getY())); + + setSize (1 + (int) (newBounds.getWidth() + outlineWidth) + border.getLeftAndRight(), + 1 + (int) (newBounds.getHeight() + outlineWidth) + border.getTopAndBottom()); + } + + repaint(); +} + +void ShapeButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) +{ + if (! isEnabled()) + { + isMouseOverButton = false; + isButtonDown = false; + } + + Rectangle r (border.subtractedFrom (getLocalBounds()).toFloat().reduced (outlineWidth * 0.5f)); + + if (getComponentEffect() != nullptr) + r = r.reduced (2.0f); + + if (isButtonDown) + { + const float sizeReductionWhenPressed = 0.04f; + + r = r.reduced (sizeReductionWhenPressed * r.getWidth(), + sizeReductionWhenPressed * r.getHeight()); + } + + const AffineTransform trans (shape.getTransformToScaleToFit (r, maintainShapeProportions)); + + g.setColour (isButtonDown ? downColour + : isMouseOverButton ? overColour + : normalColour); + g.fillPath (shape, trans); + + if (outlineWidth > 0.0f) + { + g.setColour (outlineColour); + g.strokePath (shape, PathStrokeType (outlineWidth), trans); + } +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.h new file mode 100644 index 0000000..ddc4625 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ShapeButton.h @@ -0,0 +1,106 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_SHAPEBUTTON_H_INCLUDED +#define JUCE_SHAPEBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button that contains a filled shape. + + @see Button, ImageButton, TextButton, ArrowButton +*/ +class JUCE_API ShapeButton : public Button +{ +public: + //============================================================================== + /** Creates a ShapeButton. + + @param name a name to give the component - see Component::setName() + @param normalColour the colour to fill the shape with when the mouse isn't over + @param overColour the colour to use when the mouse is over the shape + @param downColour the colour to use when the button is in the pressed-down state + */ + ShapeButton (const String& name, + Colour normalColour, + Colour overColour, + Colour downColour); + + /** Destructor. */ + ~ShapeButton(); + + //============================================================================== + /** Sets the shape to use. + + @param newShape the shape to use + @param resizeNowToFitThisShape if true, the button will be resized to fit the shape's bounds + @param maintainShapeProportions if true, the shape's proportions will be kept fixed when + the button is resized + @param hasDropShadow if true, the button will be given a drop-shadow effect + */ + void setShape (const Path& newShape, + bool resizeNowToFitThisShape, + bool maintainShapeProportions, + bool hasDropShadow); + + /** Set the colours to use for drawing the shape. + + @param normalColour the colour to fill the shape with when the mouse isn't over + @param overColour the colour to use when the mouse is over the shape + @param downColour the colour to use when the button is in the pressed-down state + */ + void setColours (Colour normalColour, + Colour overColour, + Colour downColour); + + /** Sets up an outline to draw around the shape. + + @param outlineColour the colour to use + @param outlineStrokeWidth the thickness of line to draw + */ + void setOutline (Colour outlineColour, float outlineStrokeWidth); + + /** This lets you specify a border to be left around the edge of the button when + drawing the shape. + */ + void setBorderSize (BorderSize border); + + /** @internal */ + void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; + +private: + //============================================================================== + Colour normalColour, overColour, downColour, outlineColour; + DropShadowEffect shadow; + Path shape; + BorderSize border; + bool maintainShapeProportions; + float outlineWidth; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ShapeButton) +}; + + +#endif // JUCE_SHAPEBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.cpp new file mode 100644 index 0000000..b51ea65 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.cpp @@ -0,0 +1,70 @@ +/* + ============================================================================== + file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +TextButton::TextButton() : Button (String()) +{ +} + +TextButton::TextButton (const String& name) : Button (name) +{ +} + +TextButton::TextButton (const String& name, const String& toolTip) : Button (name) +{ + setTooltip (toolTip); +} + +TextButton::~TextButton() +{ +} + +void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) +{ + LookAndFeel& lf = getLookAndFeel(); + + lf.drawButtonBackground (g, *this, + findColour (getToggleState() ? buttonOnColourId : buttonColourId), + isMouseOverButton, isButtonDown); + + lf.drawButtonText (g, *this, isMouseOverButton, isButtonDown); +} + +void TextButton::colourChanged() +{ + repaint(); +} + +void TextButton::changeWidthToFitText() +{ + changeWidthToFitText (getHeight()); +} + +void TextButton::changeWidthToFitText (const int newHeight) +{ + setSize (getBestWidthForHeight (newHeight), newHeight); +} + +int TextButton::getBestWidthForHeight (int buttonHeight) +{ + return getLookAndFeel().getTextButtonWidthToFitText (*this, buttonHeight); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.h new file mode 100644 index 0000000..31c477b --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_TextButton.h @@ -0,0 +1,113 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TEXTBUTTON_H_INCLUDED +#define JUCE_TEXTBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button that uses the standard lozenge-shaped background with a line of + text on it. + + @see Button, DrawableButton +*/ +class JUCE_API TextButton : public Button +{ +public: + //============================================================================== + /** Creates a TextButton. */ + TextButton(); + + /** Creates a TextButton. + @param buttonName the text to put in the button (the component's name is also + initially set to this string, but these can be changed later + using the setName() and setButtonText() methods) + */ + explicit TextButton (const String& buttonName); + + /** Creates a TextButton. + @param buttonName the text to put in the button (the component's name is also + initially set to this string, but these can be changed later + using the setName() and setButtonText() methods) + @param toolTip an optional string to use as a toolip + */ + TextButton (const String& buttonName, const String& toolTip); + + /** Destructor. */ + ~TextButton(); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the button. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + buttonColourId = 0x1000100, /**< The colour used to fill the button shape (when the button is toggled + 'off'). The look-and-feel class might re-interpret this to add + effects, etc. */ + buttonOnColourId = 0x1000101, /**< The colour used to fill the button shape (when the button is toggled + 'on'). The look-and-feel class might re-interpret this to add + effects, etc. */ + textColourOffId = 0x1000102, /**< The colour to use for the button's text when the button's toggle state is "off". */ + textColourOnId = 0x1000103 /**< The colour to use for the button's text.when the button's toggle state is "on". */ + }; + + //============================================================================== + /** Changes this button's width to fit neatly around its current text, without + changing its height. + */ + void changeWidthToFitText(); + + /** Resizes the button's width to fit neatly around its current text, and gives it + the specified height. + */ + void changeWidthToFitText (int newHeight); + + /** Returns the width that the LookAndFeel suggests would be best for this button if it + had the given height. + */ + int getBestWidthForHeight (int buttonHeight); + + //============================================================================== + /** @internal */ + void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; + /** @internal */ + void colourChanged() override; + +private: + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // Note that this method has been removed - instead, see LookAndFeel::getTextButtonFont() + virtual int getFont() { return 0; } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextButton) +}; + + +#endif // JUCE_TEXTBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp new file mode 100644 index 0000000..edaf7ab --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp @@ -0,0 +1,54 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ToggleButton::ToggleButton() + : Button (String::empty) +{ + setClickingTogglesState (true); +} + +ToggleButton::ToggleButton (const String& buttonText) + : Button (buttonText) +{ + setClickingTogglesState (true); +} + +ToggleButton::~ToggleButton() +{ +} + +void ToggleButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) +{ + getLookAndFeel().drawToggleButton (g, *this, isMouseOverButton, isButtonDown); +} + +void ToggleButton::changeWidthToFitText() +{ + getLookAndFeel().changeToggleButtonWidthToFitText (*this); +} + +void ToggleButton::colourChanged() +{ + repaint(); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h new file mode 100644 index 0000000..911bc59 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h @@ -0,0 +1,87 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TOGGLEBUTTON_H_INCLUDED +#define JUCE_TOGGLEBUTTON_H_INCLUDED + + +//============================================================================== +/** + A button that can be toggled on/off. + + All buttons can be toggle buttons, but this lets you create one of the + standard ones which has a tick-box and a text label next to it. + + @see Button, DrawableButton, TextButton +*/ +class JUCE_API ToggleButton : public Button +{ +public: + //============================================================================== + /** Creates a ToggleButton. */ + ToggleButton(); + + /** Creates a ToggleButton. + + @param buttonText the text to put in the button (the component's name is also + initially set to this string, but these can be changed later + using the setName() and setButtonText() methods) + */ + explicit ToggleButton (const String& buttonText); + + /** Destructor. */ + ~ToggleButton(); + + //============================================================================== + /** Resizes the button to fit neatly around its current text. + The button's height won't be affected, only its width. + */ + void changeWidthToFitText(); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the button. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + textColourId = 0x1006501 /**< The colour to use for the button's text. */ + }; + +protected: + //============================================================================== + /** @internal */ + void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; + /** @internal */ + void colourChanged() override; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton) +}; + + +#endif // JUCE_TOGGLEBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.cpp new file mode 100644 index 0000000..f7ef1b0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.cpp @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ToolbarButton::ToolbarButton (const int iid, const String& buttonText, + Drawable* const normalIm, Drawable* const toggledOnIm) + : ToolbarItemComponent (iid, buttonText, true), + normalImage (normalIm), + toggledOnImage (toggledOnIm), + currentImage (nullptr) +{ + jassert (normalImage != nullptr); +} + +ToolbarButton::~ToolbarButton() +{ +} + +//============================================================================== +bool ToolbarButton::getToolbarItemSizes (int toolbarDepth, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize) +{ + preferredSize = minSize = maxSize = toolbarDepth; + return true; +} + +void ToolbarButton::paintButtonArea (Graphics&, int /*width*/, int /*height*/, bool /*isMouseOver*/, bool /*isMouseDown*/) +{ +} + +void ToolbarButton::contentAreaChanged (const Rectangle&) +{ + buttonStateChanged(); +} + +void ToolbarButton::setCurrentImage (Drawable* const newImage) +{ + if (newImage != currentImage) + { + removeChildComponent (currentImage); + currentImage = newImage; + + if (currentImage != nullptr) + { + enablementChanged(); + addAndMakeVisible (currentImage); + updateDrawable(); + } + } +} + +void ToolbarButton::updateDrawable() +{ + if (currentImage != nullptr) + { + currentImage->setInterceptsMouseClicks (false, false); + currentImage->setTransformToFit (getContentArea().toFloat(), RectanglePlacement::centred); + currentImage->setAlpha (isEnabled() ? 1.0f : 0.5f); + } +} + +void ToolbarButton::resized() +{ + ToolbarItemComponent::resized(); + updateDrawable(); +} + +void ToolbarButton::enablementChanged() +{ + ToolbarItemComponent::enablementChanged(); + updateDrawable(); +} + +Drawable* ToolbarButton::getImageToUse() const +{ + if (getStyle() == Toolbar::textOnly) + return nullptr; + + if (getToggleState() && toggledOnImage != nullptr) + return toggledOnImage; + + return normalImage; +} + +void ToolbarButton::buttonStateChanged() +{ + setCurrentImage (getImageToUse()); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.h new file mode 100644 index 0000000..1f2548f --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToolbarButton.h @@ -0,0 +1,97 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_TOOLBARBUTTON_H_INCLUDED +#define JUCE_TOOLBARBUTTON_H_INCLUDED + + +//============================================================================== +/** + A type of button designed to go on a toolbar. + + This simple button can have two Drawable objects specified - one for normal + use and another one (optionally) for the button's "on" state if it's a + toggle button. + + @see Toolbar, ToolbarItemFactory, ToolbarItemComponent, Drawable, Button +*/ +class JUCE_API ToolbarButton : public ToolbarItemComponent +{ +public: + //============================================================================== + /** Creates a ToolbarButton. + + @param itemId the ID for this toolbar item type. This is passed through to the + ToolbarItemComponent constructor + @param labelText the text to display on the button (if the toolbar is using a style + that shows text labels). This is passed through to the + ToolbarItemComponent constructor + @param normalImage a drawable object that the button should use as its icon. The object + that is passed-in here will be kept by this object and will be + deleted when no longer needed or when this button is deleted. + @param toggledOnImage a drawable object that the button can use as its icon if the button + is in a toggled-on state (see the Button::getToggleState() method). If + nullptr is passed-in here, then the normal image will be used instead, + regardless of the toggle state. The object that is passed-in here will be + owned by this object and will be deleted when no longer needed or when + this button is deleted. + */ + ToolbarButton (int itemId, + const String& labelText, + Drawable* normalImage, + Drawable* toggledOnImage); + + /** Destructor. */ + ~ToolbarButton(); + + + //============================================================================== + /** @internal */ + bool getToolbarItemSizes (int toolbarDepth, bool isToolbarVertical, int& preferredSize, + int& minSize, int& maxSize) override; + /** @internal */ + void paintButtonArea (Graphics&, int width, int height, bool isMouseOver, bool isMouseDown) override; + /** @internal */ + void contentAreaChanged (const Rectangle&) override; + /** @internal */ + void buttonStateChanged() override; + /** @internal */ + void resized() override; + /** @internal */ + void enablementChanged() override; + +private: + //============================================================================== + ScopedPointer normalImage, toggledOnImage; + Drawable* currentImage; + + void updateDrawable(); + Drawable* getImageToUse() const; + void setCurrentImage (Drawable*); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarButton) +}; + + +#endif // JUCE_TOOLBARBUTTON_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h new file mode 100644 index 0000000..9a00cbc --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h @@ -0,0 +1,91 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONCOMMANDID_H_INCLUDED +#define JUCE_APPLICATIONCOMMANDID_H_INCLUDED + + +//============================================================================== +/** A type used to hold the unique ID for an application command. + + This is a numeric type, so it can be stored as an integer. + + @see ApplicationCommandInfo, ApplicationCommandManager, + ApplicationCommandTarget, KeyPressMappingSet +*/ +typedef int CommandID; + + +//============================================================================== +/** A set of general-purpose application command IDs. + + Because these commands are likely to be used in most apps, they're defined + here to help different apps to use the same numeric values for them. + + Of course you don't have to use these, but some of them are used internally by + Juce - e.g. the quit ID is recognised as a command by the JUCEApplication class. + + @see ApplicationCommandInfo, ApplicationCommandManager, + ApplicationCommandTarget, KeyPressMappingSet +*/ +namespace StandardApplicationCommandIDs +{ + enum + { + /** This command ID should be used to send a "Quit the App" command. + + This command is recognised by the JUCEApplication class, so if it is invoked + and no other ApplicationCommandTarget handles the event first, the JUCEApplication + object will catch it and call JUCEApplicationBase::systemRequestedQuit(). + */ + quit = 0x1001, + + /** The command ID that should be used to send a "Delete" command. */ + del = 0x1002, + + /** The command ID that should be used to send a "Cut" command. */ + cut = 0x1003, + + /** The command ID that should be used to send a "Copy to clipboard" command. */ + copy = 0x1004, + + /** The command ID that should be used to send a "Paste from clipboard" command. */ + paste = 0x1005, + + /** The command ID that should be used to send a "Select all" command. */ + selectAll = 0x1006, + + /** The command ID that should be used to send a "Deselect all" command. */ + deselectAll = 0x1007, + + /** The command ID that should be used to send a "undo" command. */ + undo = 0x1008, + + /** The command ID that should be used to send a "redo" command. */ + redo = 0x1009 + }; +} + + +#endif // JUCE_APPLICATIONCOMMANDID_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.cpp new file mode 100644 index 0000000..174ec80 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.cpp @@ -0,0 +1,60 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ApplicationCommandInfo::ApplicationCommandInfo (const CommandID cid) noexcept + : commandID (cid), flags (0) +{ +} + +void ApplicationCommandInfo::setInfo (const String& shortName_, + const String& description_, + const String& categoryName_, + const int flags_) noexcept +{ + shortName = shortName_; + description = description_; + categoryName = categoryName_; + flags = flags_; +} + +void ApplicationCommandInfo::setActive (const bool b) noexcept +{ + if (b) + flags &= ~isDisabled; + else + flags |= isDisabled; +} + +void ApplicationCommandInfo::setTicked (const bool b) noexcept +{ + if (b) + flags |= isTicked; + else + flags &= ~isTicked; +} + +void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, ModifierKeys modifiers) noexcept +{ + defaultKeypresses.add (KeyPress (keyCode, modifiers, 0)); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h new file mode 100644 index 0000000..f906f43 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandInfo.h @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED +#define JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED + + +//============================================================================== +/** + Holds information describing an application command. + + This object is used to pass information about a particular command, such as its + name, description and other usage flags. + + When an ApplicationCommandTarget is asked to provide information about the commands + it can perform, this is the structure gets filled-in to describe each one. + + @see ApplicationCommandTarget, ApplicationCommandTarget::getCommandInfo(), + ApplicationCommandManager +*/ +struct JUCE_API ApplicationCommandInfo +{ + //============================================================================== + explicit ApplicationCommandInfo (CommandID commandID) noexcept; + + //============================================================================== + /** Sets a number of the structures values at once. + + The meanings of each of the parameters is described below, in the appropriate + member variable's description. + */ + void setInfo (const String& shortName, + const String& description, + const String& categoryName, + int flags) noexcept; + + /** An easy way to set or remove the isDisabled bit in the structure's flags field. + + If isActive is true, the flags member has the isDisabled bit cleared; if isActive + is false, the bit is set. + */ + void setActive (bool isActive) noexcept; + + /** An easy way to set or remove the isTicked bit in the structure's flags field. + */ + void setTicked (bool isTicked) noexcept; + + /** Handy method for adding a keypress to the defaultKeypresses array. + + This is just so you can write things like: + @code + myinfo.addDefaultKeypress ('s', ModifierKeys::commandModifier); + @endcode + instead of + @code + myinfo.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier)); + @endcode + */ + void addDefaultKeypress (int keyCode, ModifierKeys modifiers) noexcept; + + //============================================================================== + /** The command's unique ID number. + */ + CommandID commandID; + + /** A short name to describe the command. + + This should be suitable for use in menus, on buttons that trigger the command, etc. + + You can use the setInfo() method to quickly set this and some of the command's + other properties. + */ + String shortName; + + /** A longer description of the command. + + This should be suitable for use in contexts such as a KeyMappingEditorComponent or + pop-up tooltip describing what the command does. + + You can use the setInfo() method to quickly set this and some of the command's + other properties. + */ + String description; + + /** A named category that the command fits into. + + You can give your commands any category you like, and these will be displayed in + contexts such as the KeyMappingEditorComponent, where the category is used to group + commands together. + + You can use the setInfo() method to quickly set this and some of the command's + other properties. + */ + String categoryName; + + /** A list of zero or more keypresses that should be used as the default keys for + this command. + + Methods such as KeyPressMappingSet::resetToDefaultMappings() will use the keypresses in + this list to initialise the default set of key-to-command mappings. + + @see addDefaultKeypress + */ + Array defaultKeypresses; + + //============================================================================== + /** Flags describing the ways in which this command should be used. + + A bitwise-OR of these values is stored in the ApplicationCommandInfo::flags + variable. + */ + enum CommandFlags + { + /** Indicates that the command can't currently be performed. + + The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's + not currently permissable to perform the command. If the flag is set, then + components that trigger the command, e.g. PopupMenu, may choose to grey-out the + command or show themselves as not being enabled. + + @see ApplicationCommandInfo::setActive + */ + isDisabled = 1 << 0, + + /** Indicates that the command should have a tick next to it on a menu. + + If your command is shown on a menu and this is set, it'll show a tick next to + it. Other components such as buttons may also use this flag to indicate that it + is a value that can be toggled, and is currently in the 'on' state. + + @see ApplicationCommandInfo::setTicked + */ + isTicked = 1 << 1, + + /** If this flag is present, then when a KeyPressMappingSet invokes the command, + it will call the command twice, once on key-down and again on key-up. + + @see ApplicationCommandTarget::InvocationInfo + */ + wantsKeyUpDownCallbacks = 1 << 2, + + /** If this flag is present, then a KeyMappingEditorComponent will not display the + command in its list. + */ + hiddenFromKeyEditor = 1 << 3, + + /** If this flag is present, then a KeyMappingEditorComponent will display the + command in its list, but won't allow the assigned keypress to be changed. + */ + readOnlyInKeyEditor = 1 << 4, + + /** If this flag is present and the command is invoked from a keypress, then any + buttons or menus that are also connected to the command will not flash to + indicate that they've been triggered. + */ + dontTriggerVisualFeedback = 1 << 5 + }; + + /** A bitwise-OR of the values specified in the CommandFlags enum. + + You can use the setInfo() method to quickly set this and some of the command's + other properties. + */ + int flags; +}; + + +#endif // JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp new file mode 100644 index 0000000..82ec3a2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp @@ -0,0 +1,312 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +ApplicationCommandManager::ApplicationCommandManager() + : firstTarget (nullptr) +{ + keyMappings = new KeyPressMappingSet (*this); + Desktop::getInstance().addFocusChangeListener (this); +} + +ApplicationCommandManager::~ApplicationCommandManager() +{ + Desktop::getInstance().removeFocusChangeListener (this); + keyMappings = nullptr; +} + +//============================================================================== +void ApplicationCommandManager::clearCommands() +{ + commands.clear(); + keyMappings->clearAllKeyPresses(); + triggerAsyncUpdate(); +} + +void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand) +{ + // zero isn't a valid command ID! + jassert (newCommand.commandID != 0); + + // the name isn't optional! + jassert (newCommand.shortName.isNotEmpty()); + + if (ApplicationCommandInfo* command = getMutableCommandForID (newCommand.commandID)) + { + // Trying to re-register the same command ID with different parameters can often indicate a typo. + // This assertion is here because I've found it useful catching some mistakes, but it may also cause + // false alarms if you're deliberately updating some flags for a command. + jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName + && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName + && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses + && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)) + == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))); + + *command = newCommand; + } + else + { + ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand); + newInfo->flags &= ~ApplicationCommandInfo::isTicked; + commands.add (newInfo); + + keyMappings->resetToDefaultMapping (newCommand.commandID); + + triggerAsyncUpdate(); + } +} + +void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target) +{ + if (target != nullptr) + { + Array commandIDs; + target->getAllCommands (commandIDs); + + for (int i = 0; i < commandIDs.size(); ++i) + { + ApplicationCommandInfo info (commandIDs.getUnchecked(i)); + target->getCommandInfo (info.commandID, info); + + registerCommand (info); + } + } +} + +void ApplicationCommandManager::removeCommand (const CommandID commandID) +{ + for (int i = commands.size(); --i >= 0;) + { + if (commands.getUnchecked (i)->commandID == commandID) + { + commands.remove (i); + triggerAsyncUpdate(); + + const Array keys (keyMappings->getKeyPressesAssignedToCommand (commandID)); + + for (int j = keys.size(); --j >= 0;) + keyMappings->removeKeyPress (keys.getReference (j)); + } + } +} + +void ApplicationCommandManager::commandStatusChanged() +{ + triggerAsyncUpdate(); +} + +//============================================================================== +ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept +{ + for (int i = commands.size(); --i >= 0;) + if (commands.getUnchecked(i)->commandID == commandID) + return commands.getUnchecked(i); + + return nullptr; +} + +const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const noexcept +{ + return getMutableCommandForID (commandID); +} + +String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const noexcept +{ + if (const ApplicationCommandInfo* const ci = getCommandForID (commandID)) + return ci->shortName; + + return String(); +} + +String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const noexcept +{ + if (const ApplicationCommandInfo* const ci = getCommandForID (commandID)) + return ci->description.isNotEmpty() ? ci->description + : ci->shortName; + + return String(); +} + +StringArray ApplicationCommandManager::getCommandCategories() const +{ + StringArray s; + + for (int i = 0; i < commands.size(); ++i) + s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false); + + return s; +} + +Array ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const +{ + Array results; + + for (int i = 0; i < commands.size(); ++i) + if (commands.getUnchecked(i)->categoryName == categoryName) + results.add (commands.getUnchecked(i)->commandID); + + return results; +} + +//============================================================================== +bool ApplicationCommandManager::invokeDirectly (const CommandID commandID, const bool asynchronously) +{ + ApplicationCommandTarget::InvocationInfo info (commandID); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; + + return invoke (info, asynchronously); +} + +bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, const bool asynchronously) +{ + // This call isn't thread-safe for use from a non-UI thread without locking the message + // manager first.. + jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + bool ok = false; + ApplicationCommandInfo commandInfo (0); + + if (ApplicationCommandTarget* const target = getTargetForCommand (inf.commandID, commandInfo)) + { + ApplicationCommandTarget::InvocationInfo info (inf); + info.commandFlags = commandInfo.flags; + + sendListenerInvokeCallback (info); + ok = target->invoke (info, asynchronously); + commandStatusChanged(); + } + + return ok; +} + +//============================================================================== +ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (const CommandID) +{ + return firstTarget != nullptr ? firstTarget + : findDefaultComponentTarget(); +} + +void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) noexcept +{ + firstTarget = newTarget; +} + +ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID, + ApplicationCommandInfo& upToDateInfo) +{ + ApplicationCommandTarget* target = getFirstCommandTarget (commandID); + + if (target == nullptr) + target = JUCEApplication::getInstance(); + + if (target != nullptr) + target = target->getTargetForCommand (commandID); + + if (target != nullptr) + { + upToDateInfo.commandID = commandID; + target->getCommandInfo (commandID, upToDateInfo); + } + + return target; +} + +//============================================================================== +ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c) +{ + ApplicationCommandTarget* target = dynamic_cast (c); + + if (target == nullptr && c != nullptr) + target = c->findParentComponentOfClass(); + + return target; +} + +ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget() +{ + Component* c = Component::getCurrentlyFocusedComponent(); + + if (c == nullptr) + { + if (TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow()) + { + c = activeWindow->getPeer()->getLastFocusedSubcomponent(); + + if (c == nullptr) + c = activeWindow; + } + } + + if (c == nullptr && Process::isForegroundProcess()) + { + Desktop& desktop = Desktop::getInstance(); + + // getting a bit desperate now: try all desktop comps.. + for (int i = desktop.getNumComponents(); --i >= 0;) + if (ComponentPeer* const peer = desktop.getComponent(i)->getPeer()) + if (ApplicationCommandTarget* const target = findTargetForComponent (peer->getLastFocusedSubcomponent())) + return target; + } + + if (c != nullptr) + { + // if we're focused on a ResizableWindow, chances are that it's the content + // component that really should get the event. And if not, the event will + // still be passed up to the top level window anyway, so let's send it to the + // content comp. + if (ResizableWindow* const resizableWindow = dynamic_cast (c)) + if (Component* const content = resizableWindow->getContentComponent()) + c = content; + + if (ApplicationCommandTarget* const target = findTargetForComponent (c)) + return target; + } + + return JUCEApplication::getInstance(); +} + +//============================================================================== +void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* const listener) +{ + listeners.add (listener); +} + +void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* const listener) +{ + listeners.remove (listener); +} + +void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info) +{ + listeners.call (&ApplicationCommandManagerListener::applicationCommandInvoked, info); +} + +void ApplicationCommandManager::handleAsyncUpdate() +{ + listeners.call (&ApplicationCommandManagerListener::applicationCommandListChanged); +} + +void ApplicationCommandManager::globalFocusChanged (Component*) +{ + commandStatusChanged(); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.h b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.h new file mode 100644 index 0000000..5be135a --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.h @@ -0,0 +1,353 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED +#define JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED + + +//============================================================================== +/** + One of these objects holds a list of all the commands your app can perform, + and despatches these commands when needed. + + Application commands are a good way to trigger actions in your app, e.g. "Quit", + "Copy", "Paste", etc. Menus, buttons and keypresses can all be given commands + to invoke automatically, which means you don't have to handle the result of a menu + or button click manually. Commands are despatched to ApplicationCommandTarget objects + which can choose which events they want to handle. + + This architecture also allows for nested ApplicationCommandTargets, so that for example + you could have two different objects, one inside the other, both of which can respond to + a "delete" command. Depending on which one has focus, the command will be sent to the + appropriate place, regardless of whether it was triggered by a menu, keypress or some other + method. + + To set up your app to use commands, you'll need to do the following: + + - Create a global ApplicationCommandManager to hold the list of all possible + commands. (This will also manage a set of key-mappings for them). + + - Make some of your UI components (or other objects) inherit from ApplicationCommandTarget. + This allows the object to provide a list of commands that it can perform, and + to handle them. + + - Register each type of command using ApplicationCommandManager::registerAllCommandsForTarget(), + or ApplicationCommandManager::registerCommand(). + + - If you want key-presses to trigger your commands, use the ApplicationCommandManager::getKeyMappings() + method to access the key-mapper object, which you will need to register as a key-listener + in whatever top-level component you're using. See the KeyPressMappingSet class for more help + about setting this up. + + - Use methods such as PopupMenu::addCommandItem() or Button::setCommandToTrigger() to + cause these commands to be invoked automatically. + + - Commands can be invoked directly by your code using ApplicationCommandManager::invokeDirectly(). + + When a command is invoked, the ApplicationCommandManager will try to choose the best + ApplicationCommandTarget to receive the specified command. To do this it will use the + current keyboard focus to see which component might be interested, and will search the + component hierarchy for those that also implement the ApplicationCommandTarget interface. + If an ApplicationCommandTarget isn't interested in the command that is being invoked, then + the next one in line will be tried (see the ApplicationCommandTarget::getNextCommandTarget() + method), and so on until ApplicationCommandTarget::getNextCommandTarget() returns nullptr. + At this point if the command still hasn't been performed, it will be passed to the current + JUCEApplication object (which is itself an ApplicationCommandTarget). + + To exert some custom control over which ApplicationCommandTarget is chosen to invoke a command, + you can override the ApplicationCommandManager::getFirstCommandTarget() method and choose + the object yourself. + + @see ApplicationCommandTarget, ApplicationCommandInfo +*/ +class JUCE_API ApplicationCommandManager : private AsyncUpdater, + private FocusChangeListener +{ +public: + //============================================================================== + /** Creates an ApplicationCommandManager. + + Once created, you'll need to register all your app's commands with it, using + ApplicationCommandManager::registerAllCommandsForTarget() or + ApplicationCommandManager::registerCommand(). + */ + ApplicationCommandManager(); + + /** Destructor. + + Make sure that you don't delete this if pointers to it are still being used by + objects such as PopupMenus or Buttons. + */ + virtual ~ApplicationCommandManager(); + + //============================================================================== + /** Clears the current list of all commands. + Note that this will also clear the contents of the KeyPressMappingSet. + */ + void clearCommands(); + + /** Adds a command to the list of registered commands. + @see registerAllCommandsForTarget + */ + void registerCommand (const ApplicationCommandInfo& newCommand); + + /** Adds all the commands that this target publishes to the manager's list. + + This will use ApplicationCommandTarget::getAllCommands() and ApplicationCommandTarget::getCommandInfo() + to get details about all the commands that this target can do, and will call + registerCommand() to add each one to the manger's list. + + @see registerCommand + */ + void registerAllCommandsForTarget (ApplicationCommandTarget* target); + + /** Removes the command with a specified ID. + Note that this will also remove any key mappings that are mapped to the command. + */ + void removeCommand (CommandID commandID); + + /** This should be called to tell the manager that one of its registered commands may have changed + its active status. + + Because the command manager only finds out whether a command is active or inactive by querying + the current ApplicationCommandTarget, this is used to tell it that things may have changed. It + allows things like buttons to update their enablement, etc. + + This method will cause an asynchronous call to ApplicationCommandManagerListener::applicationCommandListChanged() + for any registered listeners. + */ + void commandStatusChanged(); + + //============================================================================== + /** Returns the number of commands that have been registered. + @see registerCommand + */ + int getNumCommands() const noexcept { return commands.size(); } + + /** Returns the details about one of the registered commands. + The index is between 0 and (getNumCommands() - 1). + */ + const ApplicationCommandInfo* getCommandForIndex (int index) const noexcept { return commands [index]; } + + /** Returns the details about a given command ID. + + This will search the list of registered commands for one with the given command + ID number, and return its associated info. If no matching command is found, this + will return 0. + */ + const ApplicationCommandInfo* getCommandForID (CommandID commandID) const noexcept; + + /** Returns the name field for a command. + + An empty string is returned if no command with this ID has been registered. + @see getDescriptionOfCommand + */ + String getNameOfCommand (CommandID commandID) const noexcept; + + /** Returns the description field for a command. + + An empty string is returned if no command with this ID has been registered. If the + command has no description, this will return its short name field instead. + + @see getNameOfCommand + */ + String getDescriptionOfCommand (CommandID commandID) const noexcept; + + /** Returns the list of categories. + + This will go through all registered commands, and return a list of all the distinct + categoryName values from their ApplicationCommandInfo structure. + + @see getCommandsInCategory() + */ + StringArray getCommandCategories() const; + + /** Returns a list of all the command UIDs in a particular category. + @see getCommandCategories() + */ + Array getCommandsInCategory (const String& categoryName) const; + + //============================================================================== + /** Returns the manager's internal set of key mappings. + + This object can be used to edit the keypresses. To actually link this object up + to invoke commands when a key is pressed, see the comments for the KeyPressMappingSet + class. + + @see KeyPressMappingSet + */ + KeyPressMappingSet* getKeyMappings() const noexcept { return keyMappings; } + + + //============================================================================== + /** Invokes the given command directly, sending it to the default target. + This is just an easy way to call invoke() without having to fill out the InvocationInfo + structure. + */ + bool invokeDirectly (CommandID commandID, bool asynchronously); + + /** Sends a command to the default target. + + This will choose a target using getFirstCommandTarget(), and send the specified command + to it using the ApplicationCommandTarget::invoke() method. This means that if the + first target can't handle the command, it will be passed on to targets further down the + chain (see ApplicationCommandTarget::invoke() for more info). + + @param invocationInfo this must be correctly filled-in, describing the context for + the invocation. + @param asynchronously if false, the command will be performed before this method returns. + If true, a message will be posted so that the command will be performed + later on the message thread, and this method will return immediately. + + @see ApplicationCommandTarget::invoke + */ + bool invoke (const ApplicationCommandTarget::InvocationInfo& invocationInfo, + bool asynchronously); + + + //============================================================================== + /** Chooses the ApplicationCommandTarget to which a command should be sent. + + Whenever the manager needs to know which target a command should be sent to, it calls + this method to determine the first one to try. + + By default, this method will return the target that was set by calling setFirstCommandTarget(). + If no target is set, it will return the result of findDefaultComponentTarget(). + + If you need to make sure all commands go via your own custom target, then you can + either use setFirstCommandTarget() to specify a single target, or override this method + if you need more complex logic to choose one. + + It may return nullptr if no targets are available. + + @see getTargetForCommand, invoke, invokeDirectly + */ + virtual ApplicationCommandTarget* getFirstCommandTarget (CommandID commandID); + + /** Sets a target to be returned by getFirstCommandTarget(). + + If this is set to nullptr, then getFirstCommandTarget() will by default return the + result of findDefaultComponentTarget(). + + If you use this to set a target, make sure you call setFirstCommandTarget(nullptr) + before deleting the target object. + */ + void setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept; + + /** Tries to find the best target to use to perform a given command. + + This will call getFirstCommandTarget() to find the preferred target, and will + check whether that target can handle the given command. If it can't, then it'll use + ApplicationCommandTarget::getNextCommandTarget() to find the next one to try, and + so on until no more are available. + + If no targets are found that can perform the command, this method will return nullptr. + + If a target is found, then it will get the target to fill-in the upToDateInfo + structure with the latest info about that command, so that the caller can see + whether the command is disabled, ticked, etc. + */ + ApplicationCommandTarget* getTargetForCommand (CommandID commandID, + ApplicationCommandInfo& upToDateInfo); + + //============================================================================== + /** Registers a listener that will be called when various events occur. */ + void addListener (ApplicationCommandManagerListener* listener); + + /** Deregisters a previously-added listener. */ + void removeListener (ApplicationCommandManagerListener* listener); + + //============================================================================== + /** Looks for a suitable command target based on which Components have the keyboard focus. + + This is used by the default implementation of ApplicationCommandTarget::getFirstCommandTarget(), + but is exposed here in case it's useful. + + It tries to pick the best ApplicationCommandTarget by looking at focused components, top level + windows, etc., and using the findTargetForComponent() method. + */ + static ApplicationCommandTarget* findDefaultComponentTarget(); + + /** Examines this component and all its parents in turn, looking for the first one + which is an ApplicationCommandTarget. + + Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them + implement that class. + */ + static ApplicationCommandTarget* findTargetForComponent (Component*); + + +private: + //============================================================================== + OwnedArray commands; + ListenerList listeners; + ScopedPointer keyMappings; + ApplicationCommandTarget* firstTarget; + + void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&); + void handleAsyncUpdate() override; + void globalFocusChanged (Component*) override; + ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept; + + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // This is just here to cause a compile error in old code that hasn't been changed to use the new + // version of this method. + virtual short getFirstCommandTarget() { return 0; } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandManager) +}; + + +//============================================================================== +/** + A listener that receives callbacks from an ApplicationCommandManager when + commands are invoked or the command list is changed. + + @see ApplicationCommandManager::addListener, ApplicationCommandManager::removeListener + +*/ +class JUCE_API ApplicationCommandManagerListener +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~ApplicationCommandManagerListener() {} + + /** Called when an app command is about to be invoked. */ + virtual void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) = 0; + + /** Called when commands are registered or deregistered from the + command manager, or when commands are made active or inactive. + + Note that if you're using this to watch for changes to whether a command is disabled, + you'll need to make sure that ApplicationCommandManager::commandStatusChanged() is called + whenever the status of your command might have changed. + */ + virtual void applicationCommandListChanged() = 0; +}; + + + +#endif // JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp new file mode 100644 index 0000000..c31d187 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp @@ -0,0 +1,186 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class ApplicationCommandTarget::CommandMessage : public MessageManager::MessageBase +{ +public: + CommandMessage (ApplicationCommandTarget* const target, const InvocationInfo& inf) + : owner (target), info (inf) + { + } + + void messageCallback() override + { + if (ApplicationCommandTarget* const target = owner) + target->tryToInvoke (info, false); + } + +private: + WeakReference owner; + const InvocationInfo info; + + JUCE_DECLARE_NON_COPYABLE (CommandMessage) +}; + +//============================================================================== +ApplicationCommandTarget::ApplicationCommandTarget() +{ +} + +ApplicationCommandTarget::~ApplicationCommandTarget() +{ + masterReference.clear(); +} + +//============================================================================== +bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async) +{ + if (isCommandActive (info.commandID)) + { + if (async) + { + (new CommandMessage (this, info))->post(); + return true; + } + + if (perform (info)) + return true; + + // Hmm.. your target claimed that it could perform this command, but failed to do so. + // If it can't do it at the moment for some reason, it should clear the 'isActive' flag + // when it returns the command's info. + jassertfalse; + } + + return false; +} + +ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent() +{ + if (Component* const c = dynamic_cast (this)) + return c->findParentComponentOfClass(); + + return nullptr; +} + +ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID) +{ + ApplicationCommandTarget* target = this; + int depth = 0; + + while (target != nullptr) + { + Array commandIDs; + target->getAllCommands (commandIDs); + + if (commandIDs.contains (commandID)) + return target; + + target = target->getNextCommandTarget(); + + ++depth; + jassert (depth < 100); // could be a recursive command chain?? + jassert (target != this); // definitely a recursive command chain! + + if (depth > 100 || target == this) + break; + } + + if (target == nullptr) + { + target = JUCEApplication::getInstance(); + + if (target != nullptr) + { + Array commandIDs; + target->getAllCommands (commandIDs); + + if (commandIDs.contains (commandID)) + return target; + } + } + + return nullptr; +} + +bool ApplicationCommandTarget::isCommandActive (const CommandID commandID) +{ + ApplicationCommandInfo info (commandID); + info.flags = ApplicationCommandInfo::isDisabled; + + getCommandInfo (commandID, info); + + return (info.flags & ApplicationCommandInfo::isDisabled) == 0; +} + +//============================================================================== +bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async) +{ + ApplicationCommandTarget* target = this; + int depth = 0; + + while (target != nullptr) + { + if (target->tryToInvoke (info, async)) + return true; + + target = target->getNextCommandTarget(); + + ++depth; + jassert (depth < 100); // could be a recursive command chain?? + jassert (target != this); // definitely a recursive command chain! + + if (depth > 100 || target == this) + break; + } + + if (target == nullptr) + { + target = JUCEApplication::getInstance(); + + if (target != nullptr) + return target->tryToInvoke (info, async); + } + + return false; +} + +bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously) +{ + ApplicationCommandTarget::InvocationInfo info (commandID); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct; + + return invoke (info, asynchronously); +} + +//============================================================================== +ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID command) + : commandID (command), + commandFlags (0), + invocationMethod (direct), + originatingComponent (nullptr), + isKeyDown (false), + millisecsSinceKeyPressed (0) +{ +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h new file mode 100644 index 0000000..c14faa8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h @@ -0,0 +1,245 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED +#define JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED + + +//============================================================================== +/** + A command target publishes a list of command IDs that it can perform. + + An ApplicationCommandManager despatches commands to targets, which must be + able to provide information about what commands they can handle. + + To create a target, you'll need to inherit from this class, implementing all of + its pure virtual methods. + + For info about how a target is chosen to receive a command, see + ApplicationCommandManager::getFirstCommandTarget(). + + @see ApplicationCommandManager, ApplicationCommandInfo +*/ +class JUCE_API ApplicationCommandTarget +{ +public: + //============================================================================== + /** Creates a command target. */ + ApplicationCommandTarget(); + + /** Destructor. */ + virtual ~ApplicationCommandTarget(); + + //============================================================================== + /** + Contains contextual details about the invocation of a command. + */ + struct JUCE_API InvocationInfo + { + //============================================================================== + InvocationInfo (const CommandID commandID); + + //============================================================================== + /** The UID of the command that should be performed. */ + CommandID commandID; + + /** The command's flags. + See ApplicationCommandInfo for a description of these flag values. + */ + int commandFlags; + + //============================================================================== + /** The types of context in which the command might be called. */ + enum InvocationMethod + { + direct = 0, /**< The command is being invoked directly by a piece of code. */ + fromKeyPress, /**< The command is being invoked by a key-press. */ + fromMenu, /**< The command is being invoked by a menu selection. */ + fromButton /**< The command is being invoked by a button click. */ + }; + + /** The type of event that triggered this command. */ + InvocationMethod invocationMethod; + + //============================================================================== + /** If triggered by a keypress or menu, this will be the component that had the + keyboard focus at the time. + + If triggered by a button, it may be set to that component, or it may be null. + */ + Component* originatingComponent; + + //============================================================================== + /** The keypress that was used to invoke it. + + Note that this will be an invalid keypress if the command was invoked + by some other means than a keyboard shortcut. + */ + KeyPress keyPress; + + /** True if the callback is being invoked when the key is pressed, + false if the key is being released. + + @see KeyPressMappingSet::addCommand() + */ + bool isKeyDown; + + /** If the key is being released, this indicates how long it had been held + down for. + + (Only relevant if isKeyDown is false.) + */ + int millisecsSinceKeyPressed; + }; + + //============================================================================== + /** This must return the next target to try after this one. + + When a command is being sent, and the first target can't handle + that command, this method is used to determine the next target that should + be tried. + + It may return nullptr if it doesn't know of another target. + + If your target is a Component, you would usually use the findFirstTargetParentComponent() + method to return a parent component that might want to handle it. + + @see invoke + */ + virtual ApplicationCommandTarget* getNextCommandTarget() = 0; + + /** This must return a complete list of commands that this target can handle. + + Your target should add all the command IDs that it handles to the array that is + passed-in. + */ + virtual void getAllCommands (Array& commands) = 0; + + /** This must provide details about one of the commands that this target can perform. + + This will be called with one of the command IDs that the target provided in its + getAllCommands() methods. + + It should fill-in all appropriate fields of the ApplicationCommandInfo structure with + suitable information about the command. (The commandID field will already have been filled-in + by the caller). + + The easiest way to set the info is using the ApplicationCommandInfo::setInfo() method to + set all the fields at once. + + If the command is currently inactive for some reason, this method must use + ApplicationCommandInfo::setActive() to make that clear, (or it should set the isDisabled + bit of the ApplicationCommandInfo::flags field). + + Any default key-presses for the command should be appended to the + ApplicationCommandInfo::defaultKeypresses field. + + Note that if you change something that affects the status of the commands + that would be returned by this method (e.g. something that makes some commands + active or inactive), you should call ApplicationCommandManager::commandStatusChanged() + to cause the manager to refresh its status. + */ + virtual void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) = 0; + + /** This must actually perform the specified command. + + If this target is able to perform the command specified by the commandID field of the + InvocationInfo structure, then it should do so, and must return true. + + If it can't handle this command, it should return false, which tells the caller to pass + the command on to the next target in line. + + @see invoke, ApplicationCommandManager::invoke + */ + virtual bool perform (const InvocationInfo& info) = 0; + + //============================================================================== + /** Makes this target invoke a command. + + Your code can call this method to invoke a command on this target, but normally + you'd call it indirectly via ApplicationCommandManager::invoke() or + ApplicationCommandManager::invokeDirectly(). + + If this target can perform the given command, it will call its perform() method to + do so. If not, then getNextCommandTarget() will be used to determine the next target + to try, and the command will be passed along to it. + + @param invocationInfo this must be correctly filled-in, describing the context for + the invocation. + @param asynchronously if false, the command will be performed before this method returns. + If true, a message will be posted so that the command will be performed + later on the message thread, and this method will return immediately. + @see perform, ApplicationCommandManager::invoke + */ + bool invoke (const InvocationInfo& invocationInfo, + const bool asynchronously); + + /** Invokes a given command directly on this target. + + This is just an easy way to call invoke() without having to fill out the InvocationInfo + structure. + */ + bool invokeDirectly (const CommandID commandID, + const bool asynchronously); + + //============================================================================== + /** Searches this target and all subsequent ones for the first one that can handle + the specified command. + + This will use getNextCommandTarget() to determine the chain of targets to try + after this one. + */ + ApplicationCommandTarget* getTargetForCommand (const CommandID commandID); + + /** Checks whether this command can currently be performed by this target. + + This will return true only if a call to getCommandInfo() doesn't set the + isDisabled flag to indicate that the command is inactive. + */ + bool isCommandActive (const CommandID commandID); + + /** If this object is a Component, this method will seach upwards in its current + UI hierarchy for the next parent component that implements the + ApplicationCommandTarget class. + + If your target is a Component, this is a very handy method to use in your + getNextCommandTarget() implementation. + */ + ApplicationCommandTarget* findFirstTargetParentComponent(); + +private: + //============================================================================== + WeakReference::Master masterReference; + friend class WeakReference; + + class CommandMessage; + friend class CommandMessage; + + bool tryToInvoke (const InvocationInfo&, bool async); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget) +}; + + +#endif // JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp new file mode 100644 index 0000000..bcfac4e --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp @@ -0,0 +1,412 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm) + : commandManager (cm) +{ + Desktop::getInstance().addFocusChangeListener (this); +} + +KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other) + : KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager) +{ + Desktop::getInstance().addFocusChangeListener (this); +} + +KeyPressMappingSet::~KeyPressMappingSet() +{ + Desktop::getInstance().removeFocusChangeListener (this); +} + +//============================================================================== +Array KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const +{ + for (int i = 0; i < mappings.size(); ++i) + if (mappings.getUnchecked(i)->commandID == commandID) + return mappings.getUnchecked (i)->keypresses; + + return Array(); +} + +void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex) +{ + // If you specify an upper-case letter but no shift key, how is the user supposed to press it!? + // Stick to lower-case letters when defining a keypress, to avoid ambiguity. + jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter()) + && ! newKeyPress.getModifiers().isShiftDown())); + + if (findCommandForKeyPress (newKeyPress) != commandID) + { + if (newKeyPress.isValid()) + { + for (int i = mappings.size(); --i >= 0;) + { + if (mappings.getUnchecked(i)->commandID == commandID) + { + mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress); + + sendChangeMessage(); + return; + } + } + + if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID)) + { + CommandMapping* const cm = new CommandMapping(); + cm->commandID = commandID; + cm->keypresses.add (newKeyPress); + cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0; + + mappings.add (cm); + sendChangeMessage(); + } + else + { + // If you hit this, you're trying to attach a keypress to a command ID that + // doesn't exist, so the key is not being attached. + jassertfalse; + } + } + } +} + +static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci) +{ + for (int j = 0; j < ci->defaultKeypresses.size(); ++j) + set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j)); +} + +void KeyPressMappingSet::resetToDefaultMappings() +{ + mappings.clear(); + + for (int i = 0; i < commandManager.getNumCommands(); ++i) + addKeyPresses (*this, commandManager.getCommandForIndex (i)); + + sendChangeMessage(); +} + +void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID) +{ + clearAllKeyPresses (commandID); + + if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID)) + addKeyPresses (*this, ci); +} + +void KeyPressMappingSet::clearAllKeyPresses() +{ + if (mappings.size() > 0) + { + sendChangeMessage(); + mappings.clear(); + } +} + +void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID) +{ + for (int i = mappings.size(); --i >= 0;) + { + if (mappings.getUnchecked(i)->commandID == commandID) + { + mappings.remove (i); + sendChangeMessage(); + } + } +} + +void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress) +{ + if (keypress.isValid()) + { + for (int i = mappings.size(); --i >= 0;) + { + CommandMapping& cm = *mappings.getUnchecked(i); + + for (int j = cm.keypresses.size(); --j >= 0;) + { + if (keypress == cm.keypresses [j]) + { + cm.keypresses.remove (j); + sendChangeMessage(); + } + } + } + } +} + +void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex) +{ + for (int i = mappings.size(); --i >= 0;) + { + if (mappings.getUnchecked(i)->commandID == commandID) + { + mappings.getUnchecked(i)->keypresses.remove (keyPressIndex); + sendChangeMessage(); + break; + } + } +} + +//============================================================================== +CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept +{ + for (int i = 0; i < mappings.size(); ++i) + if (mappings.getUnchecked(i)->keypresses.contains (keyPress)) + return mappings.getUnchecked(i)->commandID; + + return 0; +} + +bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept +{ + for (int i = mappings.size(); --i >= 0;) + if (mappings.getUnchecked(i)->commandID == commandID) + return mappings.getUnchecked(i)->keypresses.contains (keyPress); + + return false; +} + +void KeyPressMappingSet::invokeCommand (const CommandID commandID, + const KeyPress& key, + const bool isKeyDown, + const int millisecsSinceKeyPressed, + Component* const originatingComponent) const +{ + ApplicationCommandTarget::InvocationInfo info (commandID); + + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress; + info.isKeyDown = isKeyDown; + info.keyPress = key; + info.millisecsSinceKeyPressed = millisecsSinceKeyPressed; + info.originatingComponent = originatingComponent; + + commandManager.invoke (info, false); +} + +//============================================================================== +bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion) +{ + if (xmlVersion.hasTagName ("KEYMAPPINGS")) + { + if (xmlVersion.getBoolAttribute ("basedOnDefaults", true)) + { + // if the XML was created as a set of differences from the default mappings, + // (i.e. by calling createXml (true)), then we need to first restore the defaults. + resetToDefaultMappings(); + } + else + { + // if the XML was created calling createXml (false), then we need to clear all + // the keys and treat the xml as describing the entire set of mappings. + clearAllKeyPresses(); + } + + forEachXmlChildElement (xmlVersion, map) + { + const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32(); + + if (commandId != 0) + { + const KeyPress key (KeyPress::createFromDescription (map->getStringAttribute ("key"))); + + if (map->hasTagName ("MAPPING")) + { + addKeyPress (commandId, key); + } + else if (map->hasTagName ("UNMAPPING")) + { + for (int i = mappings.size(); --i >= 0;) + if (mappings.getUnchecked(i)->commandID == commandId) + mappings.getUnchecked(i)->keypresses.removeAllInstancesOf (key); + } + } + } + + return true; + } + + return false; +} + +XmlElement* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const +{ + ScopedPointer defaultSet; + + if (saveDifferencesFromDefaultSet) + { + defaultSet = new KeyPressMappingSet (commandManager); + defaultSet->resetToDefaultMappings(); + } + + XmlElement* const doc = new XmlElement ("KEYMAPPINGS"); + + doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet); + + for (int i = 0; i < mappings.size(); ++i) + { + const CommandMapping& cm = *mappings.getUnchecked(i); + + for (int j = 0; j < cm.keypresses.size(); ++j) + { + if (defaultSet == nullptr + || ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j))) + { + XmlElement* const map = doc->createNewChildElement ("MAPPING"); + + map->setAttribute ("commandId", String::toHexString ((int) cm.commandID)); + map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID)); + map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription()); + } + } + } + + if (defaultSet != nullptr) + { + for (int i = 0; i < defaultSet->mappings.size(); ++i) + { + const CommandMapping& cm = *defaultSet->mappings.getUnchecked(i); + + for (int j = 0; j < cm.keypresses.size(); ++j) + { + if (! containsMapping (cm.commandID, cm.keypresses.getReference (j))) + { + XmlElement* const map = doc->createNewChildElement ("UNMAPPING"); + + map->setAttribute ("commandId", String::toHexString ((int) cm.commandID)); + map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID)); + map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription()); + } + } + } + } + + return doc; +} + +//============================================================================== +bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent) +{ + bool commandWasDisabled = false; + + for (int i = 0; i < mappings.size(); ++i) + { + CommandMapping& cm = *mappings.getUnchecked(i); + + if (cm.keypresses.contains (key)) + { + if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID)) + { + if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0) + { + ApplicationCommandInfo info (0); + + if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr) + { + if ((info.flags & ApplicationCommandInfo::isDisabled) == 0) + { + invokeCommand (cm.commandID, key, true, 0, originatingComponent); + return true; + } + + commandWasDisabled = true; + } + } + } + } + } + + if (originatingComponent != nullptr && commandWasDisabled) + originatingComponent->getLookAndFeel().playAlertSound(); + + return false; +} + +bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent) +{ + bool used = false; + const uint32 now = Time::getMillisecondCounter(); + + for (int i = mappings.size(); --i >= 0;) + { + CommandMapping& cm = *mappings.getUnchecked(i); + + if (cm.wantsKeyUpDownCallbacks) + { + for (int j = cm.keypresses.size(); --j >= 0;) + { + const KeyPress key (cm.keypresses.getReference (j)); + const bool isDown = key.isCurrentlyDown(); + + int keyPressEntryIndex = 0; + bool wasDown = false; + + for (int k = keysDown.size(); --k >= 0;) + { + if (key == keysDown.getUnchecked(k)->key) + { + keyPressEntryIndex = k; + wasDown = true; + used = true; + break; + } + } + + if (isDown != wasDown) + { + int millisecs = 0; + + if (isDown) + { + KeyPressTime* const k = new KeyPressTime(); + k->key = key; + k->timeWhenPressed = now; + + keysDown.add (k); + } + else + { + const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed; + + if (now > pressTime) + millisecs = (int) (now - pressTime); + + keysDown.remove (keyPressEntryIndex); + } + + invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent); + used = true; + } + } + } + } + + return used; +} + +void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent) +{ + if (focusedComponent != nullptr) + focusedComponent->keyStateChanged (false); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.h b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.h new file mode 100644 index 0000000..d3351ac --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.h @@ -0,0 +1,245 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_KEYPRESSMAPPINGSET_H_INCLUDED +#define JUCE_KEYPRESSMAPPINGSET_H_INCLUDED + + +//============================================================================== +/** + Manages and edits a list of keypresses, which it uses to invoke the appropriate + command in an ApplicationCommandManager. + + Normally, you won't actually create a KeyPressMappingSet directly, because + each ApplicationCommandManager contains its own KeyPressMappingSet, so typically + you'd create yourself an ApplicationCommandManager, and call its + ApplicationCommandManager::getKeyMappings() method to get a pointer to its + KeyPressMappingSet. + + For one of these to actually use keypresses, you'll need to add it as a KeyListener + to the top-level component for which you want to handle keystrokes. So for example: + + @code + class MyMainWindow : public Component + { + ApplicationCommandManager* myCommandManager; + + public: + MyMainWindow() + { + myCommandManager = new ApplicationCommandManager(); + + // first, make sure the command manager has registered all the commands that its + // targets can perform.. + myCommandManager->registerAllCommandsForTarget (myCommandTarget1); + myCommandManager->registerAllCommandsForTarget (myCommandTarget2); + + // this will use the command manager to initialise the KeyPressMappingSet with + // the default keypresses that were specified when the targets added their commands + // to the manager. + myCommandManager->getKeyMappings()->resetToDefaultMappings(); + + // having set up the default key-mappings, you might now want to load the last set + // of mappings that the user configured. + myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML); + + // Now tell our top-level window to send any keypresses that arrive to the + // KeyPressMappingSet, which will use them to invoke the appropriate commands. + addKeyListener (myCommandManager->getKeyMappings()); + } + + ... + } + @endcode + + KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can + register to be told when a command or mapping is added, removed, etc. + + There's also a UI component called KeyMappingEditorComponent that can be used + to easily edit the key mappings. + + @see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager +*/ +class JUCE_API KeyPressMappingSet : public KeyListener, + public ChangeBroadcaster, + private FocusChangeListener +{ +public: + //============================================================================== + /** Creates a KeyPressMappingSet for a given command manager. + + Normally, you won't actually create a KeyPressMappingSet directly, because + each ApplicationCommandManager contains its own KeyPressMappingSet, so the + best thing to do is to create your ApplicationCommandManager, and use the + ApplicationCommandManager::getKeyMappings() method to access its mappings. + + When a suitable keypress happens, the manager's invoke() method will be + used to invoke the appropriate command. + + @see ApplicationCommandManager + */ + explicit KeyPressMappingSet (ApplicationCommandManager&); + + /** Creates an copy of a KeyPressMappingSet. */ + KeyPressMappingSet (const KeyPressMappingSet&); + + /** Destructor. */ + ~KeyPressMappingSet(); + + //============================================================================== + ApplicationCommandManager& getCommandManager() const noexcept { return commandManager; } + + //============================================================================== + /** Returns a list of keypresses that are assigned to a particular command. + + @param commandID the command's ID + */ + Array getKeyPressesAssignedToCommand (CommandID commandID) const; + + /** Assigns a keypress to a command. + + If the keypress is already assigned to a different command, it will first be + removed from that command, to avoid it triggering multiple functions. + + @param commandID the ID of the command that you want to add a keypress to. If + this is 0, the keypress will be removed from anything that it + was previously assigned to, but not re-assigned + @param newKeyPress the new key-press + @param insertIndex if this is less than zero, the key will be appended to the + end of the list of keypresses; otherwise the new keypress will + be inserted into the existing list at this index + */ + void addKeyPress (CommandID commandID, + const KeyPress& newKeyPress, + int insertIndex = -1); + + /** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager. + @see resetToDefaultMapping + */ + void resetToDefaultMappings(); + + /** Resets all key-mappings to the defaults for a particular command. + @see resetToDefaultMappings + */ + void resetToDefaultMapping (CommandID commandID); + + /** Removes all keypresses that are assigned to any commands. */ + void clearAllKeyPresses(); + + /** Removes all keypresses that are assigned to a particular command. */ + void clearAllKeyPresses (CommandID commandID); + + /** Removes one of the keypresses that are assigned to a command. + See the getKeyPressesAssignedToCommand() for the list of keypresses to + which the keyPressIndex refers. + */ + void removeKeyPress (CommandID commandID, int keyPressIndex); + + /** Removes a keypress from any command that it may be assigned to. */ + void removeKeyPress (const KeyPress& keypress); + + /** Returns true if the given command is linked to this key. */ + bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept; + + //============================================================================== + /** Looks for a command that corresponds to a keypress. + @returns the UID of the command or 0 if none was found + */ + CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept; + + //============================================================================== + /** Tries to recreate the mappings from a previously stored state. + + The XML passed in must have been created by the createXml() method. + + If the stored state makes any reference to commands that aren't + currently available, these will be ignored. + + If the set of mappings being loaded was a set of differences (using createXml (true)), + then this will call resetToDefaultMappings() and then merge the saved mappings + on top. If the saved set was created with createXml (false), then this method + will first clear all existing mappings and load the saved ones as a complete set. + + @returns true if it manages to load the XML correctly + @see createXml + */ + bool restoreFromXml (const XmlElement& xmlVersion); + + /** Creates an XML representation of the current mappings. + + This will produce a lump of XML that can be later reloaded using + restoreFromXml() to recreate the current mapping state. + + The object that is returned must be deleted by the caller. + + @param saveDifferencesFromDefaultSet if this is false, then all keypresses + will be saved into the XML. If it's true, then the XML will + only store the differences between the current mappings and + the default mappings you'd get from calling resetToDefaultMappings(). + The advantage of saving a set of differences from the default is that + if you change the default mappings (in a new version of your app, for + example), then these will be merged into a user's saved preferences. + + @see restoreFromXml + */ + XmlElement* createXml (bool saveDifferencesFromDefaultSet) const; + + //============================================================================== + /** @internal */ + bool keyPressed (const KeyPress&, Component*) override; + /** @internal */ + bool keyStateChanged (bool isKeyDown, Component*) override; + /** @internal */ + void globalFocusChanged (Component*) override; + +private: + //============================================================================== + ApplicationCommandManager& commandManager; + + struct CommandMapping + { + CommandID commandID; + Array keypresses; + bool wantsKeyUpDownCallbacks; + }; + + OwnedArray mappings; + + struct KeyPressTime + { + KeyPress key; + uint32 timeWhenPressed; + }; + + OwnedArray keysDown; + + void invokeCommand (const CommandID, const KeyPress&, const bool isKeyDown, + const int millisecsSinceKeyPressed, Component* originator) const; + + KeyPressMappingSet& operator= (const KeyPressMappingSet&); + JUCE_LEAK_DETECTOR (KeyPressMappingSet) +}; + + +#endif // JUCE_KEYPRESSMAPPINGSET_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_CachedComponentImage.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_CachedComponentImage.h new file mode 100644 index 0000000..3f273cb --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_CachedComponentImage.h @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED +#define JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED + + +//============================================================================== +/** + Base class used internally for structures that can store cached images of + component state. + + Most people are unlikely to ever need to know about this class - it's really + only for power-users! + + @see Component::setCachedComponentImage +*/ +class JUCE_API CachedComponentImage +{ +public: + CachedComponentImage() noexcept {} + virtual ~CachedComponentImage() {} + + //============================================================================== + /** Called as part of the parent component's paint method, this must draw + the given component into the target graphics context, using the cached + version where possible. + */ + virtual void paint (Graphics&) = 0; + + /** Invalidates all cached image data. + @returns true if the peer should also be repainted, or false if this object + handles all repaint work internally. + */ + virtual bool invalidateAll() = 0; + + /** Invalidates a section of the cached image data. + @returns true if the peer should also be repainted, or false if this object + handles all repaint work internally. + */ + virtual bool invalidate (const Rectangle& area) = 0; + + /** Called to indicate that the component is no longer active, so + any cached data should be released if possible. + */ + virtual void releaseResources() = 0; +}; + +#endif // JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp new file mode 100644 index 0000000..00e8476 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp @@ -0,0 +1,3027 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +Component* Component::currentlyFocusedComponent = nullptr; + + +//============================================================================== +class Component::MouseListenerList +{ +public: + MouseListenerList() noexcept + : numDeepMouseListeners (0) + { + } + + void addListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) + { + if (! listeners.contains (newListener)) + { + if (wantsEventsForAllNestedChildComponents) + { + listeners.insert (0, newListener); + ++numDeepMouseListeners; + } + else + { + listeners.add (newListener); + } + } + } + + void removeListener (MouseListener* const listenerToRemove) + { + const int index = listeners.indexOf (listenerToRemove); + + if (index >= 0) + { + if (index < numDeepMouseListeners) + --numDeepMouseListeners; + + listeners.remove (index); + } + } + + static void sendMouseEvent (Component& comp, Component::BailOutChecker& checker, + void (MouseListener::*eventMethod) (const MouseEvent&), const MouseEvent& e) + { + if (checker.shouldBailOut()) + return; + + if (MouseListenerList* const list = comp.mouseListeners) + { + for (int i = list->listeners.size(); --i >= 0;) + { + (list->listeners.getUnchecked(i)->*eventMethod) (e); + + if (checker.shouldBailOut()) + return; + + i = jmin (i, list->listeners.size()); + } + } + + for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) + { + MouseListenerList* const list = p->mouseListeners; + + if (list != nullptr && list->numDeepMouseListeners > 0) + { + BailOutChecker2 checker2 (checker, p); + + for (int i = list->numDeepMouseListeners; --i >= 0;) + { + (list->listeners.getUnchecked(i)->*eventMethod) (e); + + if (checker2.shouldBailOut()) + return; + + i = jmin (i, list->numDeepMouseListeners); + } + } + } + } + + static void sendWheelEvent (Component& comp, Component::BailOutChecker& checker, + const MouseEvent& e, const MouseWheelDetails& wheel) + { + if (MouseListenerList* const list = comp.mouseListeners) + { + for (int i = list->listeners.size(); --i >= 0;) + { + list->listeners.getUnchecked(i)->mouseWheelMove (e, wheel); + + if (checker.shouldBailOut()) + return; + + i = jmin (i, list->listeners.size()); + } + } + + for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) + { + MouseListenerList* const list = p->mouseListeners; + + if (list != nullptr && list->numDeepMouseListeners > 0) + { + BailOutChecker2 checker2 (checker, p); + + for (int i = list->numDeepMouseListeners; --i >= 0;) + { + list->listeners.getUnchecked(i)->mouseWheelMove (e, wheel); + + if (checker2.shouldBailOut()) + return; + + i = jmin (i, list->numDeepMouseListeners); + } + } + } + } + +private: + Array listeners; + int numDeepMouseListeners; + + class BailOutChecker2 + { + public: + BailOutChecker2 (Component::BailOutChecker& boc, Component* const comp) + : checker (boc), safePointer (comp) + { + } + + bool shouldBailOut() const noexcept + { + return checker.shouldBailOut() || safePointer == 0; + } + + private: + Component::BailOutChecker& checker; + const WeakReference safePointer; + + JUCE_DECLARE_NON_COPYABLE (BailOutChecker2) + }; + + JUCE_DECLARE_NON_COPYABLE (MouseListenerList) +}; + +//============================================================================== +struct FocusRestorer +{ + FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {} + + ~FocusRestorer() + { + if (lastFocus != nullptr && ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) + lastFocus->grabKeyboardFocus(); + } + + WeakReference lastFocus; + + JUCE_DECLARE_NON_COPYABLE (FocusRestorer) +}; + +//============================================================================== +struct ScalingHelpers +{ + template + static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept + { + return scale != 1.0f ? pos / scale : pos; + } + + template + static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept + { + return scale != 1.0f ? pos * scale : pos; + } + + // For these, we need to avoid getSmallestIntegerContainer being used, which causes + // judder when moving windows + static Rectangle unscaledScreenPosToScaled (float scale, const Rectangle& pos) noexcept + { + return scale != 1.0f ? Rectangle (roundToInt (pos.getX() / scale), + roundToInt (pos.getY() / scale), + roundToInt (pos.getWidth() / scale), + roundToInt (pos.getHeight() / scale)) : pos; + } + + static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle& pos) noexcept + { + return scale != 1.0f ? Rectangle (roundToInt (pos.getX() * scale), + roundToInt (pos.getY() * scale), + roundToInt (pos.getWidth() * scale), + roundToInt (pos.getHeight() * scale)) : pos; + } + + template + static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos); + } + + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition(); } + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } +}; + +//============================================================================== +struct Component::ComponentHelpers +{ + #if JUCE_MODAL_LOOPS_PERMITTED + static void* runModalLoopCallback (void* userData) + { + return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); + } + #endif + + static Identifier getColourPropertyId (int colourId) + { + char reversedHex[32]; + char* t = reversedHex; + + for (unsigned int v = (unsigned int) colourId;;) + { + *t++ = "0123456789abcdef" [(int) (v & 15)]; + v >>= 4; + + if (v == 0) + break; + } + + char destBuffer[32]; + char* dest = destBuffer; + memcpy (dest, "jcclr_", 6); + dest += 6; + + while (t > reversedHex) + *dest++ = *--t; + + *dest++ = 0; + return destBuffer; + } + + //============================================================================== + static inline bool hitTest (Component& comp, Point localPoint) + { + return isPositiveAndBelow (localPoint.x, comp.getWidth()) + && isPositiveAndBelow (localPoint.y, comp.getHeight()) + && comp.hitTest (localPoint.x, localPoint.y); + } + + // converts an unscaled position within a peer to the local position within that peer's component + template + static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform().inverted()); + + return ScalingHelpers::unscaledScreenPosToScaled (comp, pos); + } + + // converts a position within a peer's component to the unscaled position within the peer + template + static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform()); + + return ScalingHelpers::scaledScreenPosToUnscaled (comp, pos); + } + + template + static PointOrRect convertFromParentSpace (const Component& comp, PointOrRect pointInParentSpace) + { + if (comp.affineTransform != nullptr) + pointInParentSpace = pointInParentSpace.transformedBy (comp.affineTransform->inverted()); + + if (comp.isOnDesktop()) + { + if (ComponentPeer* peer = comp.getPeer()) + pointInParentSpace = ScalingHelpers::unscaledScreenPosToScaled + (comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (pointInParentSpace))); + else + jassertfalse; + } + else + { + pointInParentSpace = ScalingHelpers::subtractPosition (pointInParentSpace, comp); + } + + return pointInParentSpace; + } + + template + static PointOrRect convertToParentSpace (const Component& comp, PointOrRect pointInLocalSpace) + { + if (comp.isOnDesktop()) + { + if (ComponentPeer* peer = comp.getPeer()) + pointInLocalSpace = ScalingHelpers::unscaledScreenPosToScaled + (peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); + else + jassertfalse; + } + else + { + pointInLocalSpace = ScalingHelpers::addPosition (pointInLocalSpace, comp); + } + + if (comp.affineTransform != nullptr) + pointInLocalSpace = pointInLocalSpace.transformedBy (*comp.affineTransform); + + return pointInLocalSpace; + } + + template + static PointOrRect convertFromDistantParentSpace (const Component* parent, const Component& target, const PointOrRect& coordInParent) + { + const Component* const directParent = target.getParentComponent(); + jassert (directParent != nullptr); + + if (directParent == parent) + return convertFromParentSpace (target, coordInParent); + + return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent)); + } + + template + static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) + { + while (source != nullptr) + { + if (source == target) + return p; + + if (source->isParentOf (target)) + return convertFromDistantParentSpace (source, *target, p); + + p = convertToParentSpace (*source, p); + source = source->getParentComponent(); + } + + jassert (source == nullptr); + if (target == nullptr) + return p; + + const Component* const topLevelComp = target->getTopLevelComponent(); + + p = convertFromParentSpace (*topLevelComp, p); + + if (topLevelComp == target) + return p; + + return convertFromDistantParentSpace (topLevelComp, *target, p); + } + + static bool clipObscuredRegions (const Component& comp, Graphics& g, const Rectangle& clipRect, Point delta) + { + bool nothingChanged = true; + + for (int i = comp.childComponentList.size(); --i >= 0;) + { + const Component& child = *comp.childComponentList.getUnchecked(i); + + if (child.isVisible() && ! child.isTransformed()) + { + const Rectangle newClip (clipRect.getIntersection (child.bounds)); + + if (! newClip.isEmpty()) + { + if (child.isOpaque() && child.componentTransparency == 0) + { + g.excludeClipRegion (newClip + delta); + nothingChanged = false; + } + else + { + const Point childPos (child.getPosition()); + if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta)) + nothingChanged = false; + } + } + } + } + + return nothingChanged; + } + + static Rectangle getParentOrMainMonitorBounds (const Component& comp) + { + if (Component* p = comp.getParentComponent()) + return p->getLocalBounds(); + + return Desktop::getInstance().getDisplays().getMainDisplay().userArea; + } +}; + +//============================================================================== +Component::Component() noexcept + : parentComponent (nullptr), + lookAndFeel (nullptr), + effect (nullptr), + componentFlags (0), + componentTransparency (0) +{ +} + +Component::Component (const String& name) noexcept + : componentName (name), + parentComponent (nullptr), + lookAndFeel (nullptr), + effect (nullptr), + componentFlags (0), + componentTransparency (0) +{ +} + +Component::~Component() +{ + static_jassert (sizeof (flags) <= sizeof (componentFlags)); + + componentListeners.call (&ComponentListener::componentBeingDeleted, *this); + + masterReference.clear(); + + while (childComponentList.size() > 0) + removeChildComponent (childComponentList.size() - 1, false, true); + + if (parentComponent != nullptr) + parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false); + else if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) + giveAwayFocus (currentlyFocusedComponent != this); + + if (flags.hasHeavyweightPeerFlag) + removeFromDesktop(); + + // Something has added some children to this component during its destructor! Not a smart idea! + jassert (childComponentList.size() == 0); +} + +//============================================================================== +void Component::setName (const String& name) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (componentName != name) + { + componentName = name; + + if (flags.hasHeavyweightPeerFlag) + if (ComponentPeer* const peer = getPeer()) + peer->setTitle (name); + + BailOutChecker checker (this); + componentListeners.callChecked (checker, &ComponentListener::componentNameChanged, *this); + } +} + +void Component::setComponentID (const String& newID) +{ + componentID = newID; +} + +void Component::setVisible (bool shouldBeVisible) +{ + if (flags.visibleFlag != shouldBeVisible) + { + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + const WeakReference safePointer (this); + flags.visibleFlag = shouldBeVisible; + + if (shouldBeVisible) + repaint(); + else + repaintParent(); + + sendFakeMouseMove(); + + if (! shouldBeVisible) + { + if (cachedImage != nullptr) + cachedImage->releaseResources(); + + if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) + { + if (parentComponent != nullptr) + parentComponent->grabKeyboardFocus(); + else + giveAwayFocus (true); + } + } + + if (safePointer != nullptr) + { + sendVisibilityChangeMessage(); + + if (safePointer != nullptr && flags.hasHeavyweightPeerFlag) + { + if (ComponentPeer* const peer = getPeer()) + { + peer->setVisible (shouldBeVisible); + internalHierarchyChanged(); + } + } + } + } +} + +void Component::visibilityChanged() {} + +void Component::sendVisibilityChangeMessage() +{ + BailOutChecker checker (this); + visibilityChanged(); + + if (! checker.shouldBailOut()) + componentListeners.callChecked (checker, &ComponentListener::componentVisibilityChanged, *this); +} + +bool Component::isShowing() const +{ + if (! flags.visibleFlag) + return false; + + if (parentComponent != nullptr) + return parentComponent->isShowing(); + + if (const ComponentPeer* const peer = getPeer()) + return ! peer->isMinimised(); + + return false; +} + +//============================================================================== +void* Component::getWindowHandle() const +{ + if (const ComponentPeer* const peer = getPeer()) + return peer->getNativeHandle(); + + return nullptr; +} + +//============================================================================== +void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (isOpaque()) + styleWanted &= ~ComponentPeer::windowIsSemiTransparent; + else + styleWanted |= ComponentPeer::windowIsSemiTransparent; + + // don't use getPeer(), so that we only get the peer that's specifically + // for this comp, and not for one of its parents. + ComponentPeer* peer = ComponentPeer::getPeerFor (this); + + if (peer == nullptr || styleWanted != peer->getStyleFlags()) + { + const WeakReference safePointer (this); + + #if JUCE_LINUX + // it's wise to give the component a non-zero size before + // putting it on the desktop, as X windows get confused by this, and + // a (1, 1) minimum size is enforced here. + setSize (jmax (1, getWidth()), + jmax (1, getHeight())); + #endif + + const Point topLeft (getScreenPosition()); + + bool wasFullscreen = false; + bool wasMinimised = false; + ComponentBoundsConstrainer* currentConstrainer = nullptr; + Rectangle oldNonFullScreenBounds; + int oldRenderingEngine = -1; + + if (peer != nullptr) + { + ScopedPointer oldPeerToDelete (peer); + + wasFullscreen = peer->isFullScreen(); + wasMinimised = peer->isMinimised(); + currentConstrainer = peer->getConstrainer(); + oldNonFullScreenBounds = peer->getNonFullScreenBounds(); + oldRenderingEngine = peer->getCurrentRenderingEngine(); + + flags.hasHeavyweightPeerFlag = false; + Desktop::getInstance().removeDesktopComponent (this); + internalHierarchyChanged(); // give comps a chance to react to the peer change before the old peer is deleted. + + if (safePointer == nullptr) + return; + + setTopLeftPosition (topLeft); + } + + if (parentComponent != nullptr) + parentComponent->removeChildComponent (this); + + if (safePointer != nullptr) + { + flags.hasHeavyweightPeerFlag = true; + + peer = createNewPeer (styleWanted, nativeWindowToAttachTo); + + Desktop::getInstance().addDesktopComponent (this); + + bounds.setPosition (topLeft); + peer->updateBounds(); + + if (oldRenderingEngine >= 0) + peer->setCurrentRenderingEngine (oldRenderingEngine); + + peer->setVisible (isVisible()); + + peer = ComponentPeer::getPeerFor (this); + if (peer == nullptr) + return; + + if (wasFullscreen) + { + peer->setFullScreen (true); + peer->setNonFullScreenBounds (oldNonFullScreenBounds); + } + + if (wasMinimised) + peer->setMinimised (true); + + #if JUCE_WINDOWS + if (isAlwaysOnTop()) + peer->setAlwaysOnTop (true); + #endif + + peer->setConstrainer (currentConstrainer); + + repaint(); + internalHierarchyChanged(); + } + } +} + +void Component::removeFromDesktop() +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (flags.hasHeavyweightPeerFlag) + { + ComponentPeer* const peer = ComponentPeer::getPeerFor (this); + jassert (peer != nullptr); + + flags.hasHeavyweightPeerFlag = false; + delete peer; + + Desktop::getInstance().removeDesktopComponent (this); + } +} + +bool Component::isOnDesktop() const noexcept +{ + return flags.hasHeavyweightPeerFlag; +} + +ComponentPeer* Component::getPeer() const +{ + if (flags.hasHeavyweightPeerFlag) + return ComponentPeer::getPeerFor (this); + + if (parentComponent == nullptr) + return nullptr; + + return parentComponent->getPeer(); +} + +void Component::userTriedToCloseWindow() +{ + /* This means that the user's trying to get rid of your window with the 'close window' system + menu option (on windows) or possibly the task manager - you should really handle this + and delete or hide your component in an appropriate way. + + If you want to ignore the event and don't want to trigger this assertion, just override + this method and do nothing. + */ + jassertfalse; +} + +void Component::minimisationStateChanged (bool) {} + +float Component::getDesktopScaleFactor() const { return Desktop::getInstance().getGlobalScaleFactor(); } + +//============================================================================== +void Component::setOpaque (const bool shouldBeOpaque) +{ + if (shouldBeOpaque != flags.opaqueFlag) + { + flags.opaqueFlag = shouldBeOpaque; + + if (flags.hasHeavyweightPeerFlag) + if (const ComponentPeer* const peer = ComponentPeer::getPeerFor (this)) + addToDesktop (peer->getStyleFlags()); // recreates the heavyweight window + + repaint(); + } +} + +bool Component::isOpaque() const noexcept +{ + return flags.opaqueFlag; +} + +//============================================================================== +class StandardCachedComponentImage : public CachedComponentImage +{ +public: + StandardCachedComponentImage (Component& c) noexcept : owner (c), scale (1.0f) {} + + void paint (Graphics& g) override + { + scale = g.getInternalContext().getPhysicalPixelScaleFactor(); + const Rectangle compBounds (owner.getLocalBounds()); + const Rectangle imageBounds (compBounds * scale); + + if (image.isNull() || image.getBounds() != imageBounds) + { + image = Image (owner.isOpaque() ? Image::RGB + : Image::ARGB, + jmax (1, imageBounds.getWidth()), + jmax (1, imageBounds.getHeight()), + ! owner.isOpaque()); + + validArea.clear(); + } + + if (! validArea.containsRectangle (compBounds)) + { + Graphics imG (image); + LowLevelGraphicsContext& lg = imG.getInternalContext(); + + for (const Rectangle* i = validArea.begin(), * const e = validArea.end(); i != e; ++i) + lg.excludeClipRectangle (*i); + + if (! owner.isOpaque()) + { + lg.setFill (Colours::transparentBlack); + lg.fillRect (imageBounds, true); + lg.setFill (Colours::black); + } + + lg.addTransform (AffineTransform::scale (scale)); + owner.paintEntireComponent (imG, true); + } + + validArea = imageBounds; + + g.setColour (Colours::black.withAlpha (owner.getAlpha())); + g.drawImageTransformed (image, AffineTransform::scale (compBounds.getWidth() / (float) imageBounds.getWidth(), + compBounds.getHeight() / (float) imageBounds.getHeight()), false); + } + + bool invalidateAll() override { validArea.clear(); return true; } + bool invalidate (const Rectangle& area) override { validArea.subtract (area * scale); return true; } + void releaseResources() override { image = Image::null; } + +private: + Image image; + RectangleList validArea; + Component& owner; + float scale; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) +}; + +void Component::setCachedComponentImage (CachedComponentImage* newCachedImage) +{ + if (cachedImage != newCachedImage) + { + cachedImage = newCachedImage; + repaint(); + } +} + +void Component::setBufferedToImage (const bool shouldBeBuffered) +{ + // This assertion means that this component is already using a custom CachedComponentImage, + // so by calling setBufferedToImage, you'll be deleting the custom one - this is almost certainly + // not what you wanted to happen... If you really do know what you're doing here, and want to + // avoid this assertion, just call setCachedComponentImage (nullptr) before setBufferedToImage(). + jassert (cachedImage == nullptr || dynamic_cast (cachedImage.get()) != nullptr); + + if (shouldBeBuffered) + { + if (cachedImage == nullptr) + cachedImage = new StandardCachedComponentImage (*this); + } + else + { + cachedImage = nullptr; + } +} + +//============================================================================== +void Component::reorderChildInternal (const int sourceIndex, const int destIndex) +{ + if (sourceIndex != destIndex) + { + Component* const c = childComponentList.getUnchecked (sourceIndex); + jassert (c != nullptr); + c->repaintParent(); + + childComponentList.move (sourceIndex, destIndex); + + sendFakeMouseMove(); + internalChildrenChanged(); + } +} + +void Component::toFront (const bool setAsForeground) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (flags.hasHeavyweightPeerFlag) + { + if (ComponentPeer* const peer = getPeer()) + { + peer->toFront (setAsForeground); + + if (setAsForeground && ! hasKeyboardFocus (true)) + grabKeyboardFocus(); + } + } + else if (parentComponent != nullptr) + { + const Array& childList = parentComponent->childComponentList; + + if (childList.getLast() != this) + { + const int index = childList.indexOf (this); + + if (index >= 0) + { + int insertIndex = -1; + + if (! flags.alwaysOnTopFlag) + { + insertIndex = childList.size() - 1; + + while (insertIndex > 0 && childList.getUnchecked (insertIndex)->isAlwaysOnTop()) + --insertIndex; + } + + parentComponent->reorderChildInternal (index, insertIndex); + } + } + + if (setAsForeground) + { + internalBroughtToFront(); + grabKeyboardFocus(); + } + } +} + +void Component::toBehind (Component* const other) +{ + if (other != nullptr && other != this) + { + // the two components must belong to the same parent.. + jassert (parentComponent == other->parentComponent); + + if (parentComponent != nullptr) + { + const Array& childList = parentComponent->childComponentList; + const int index = childList.indexOf (this); + + if (index >= 0 && childList [index + 1] != other) + { + int otherIndex = childList.indexOf (other); + + if (otherIndex >= 0) + { + if (index < otherIndex) + --otherIndex; + + parentComponent->reorderChildInternal (index, otherIndex); + } + } + } + else if (isOnDesktop()) + { + jassert (other->isOnDesktop()); + + if (other->isOnDesktop()) + { + ComponentPeer* const us = getPeer(); + ComponentPeer* const them = other->getPeer(); + + jassert (us != nullptr && them != nullptr); + if (us != nullptr && them != nullptr) + us->toBehind (them); + } + } + } +} + +void Component::toBack() +{ + if (isOnDesktop()) + { + jassertfalse; //xxx need to add this to native window + } + else if (parentComponent != nullptr) + { + const Array& childList = parentComponent->childComponentList; + + if (childList.getFirst() != this) + { + const int index = childList.indexOf (this); + + if (index > 0) + { + int insertIndex = 0; + + if (flags.alwaysOnTopFlag) + while (insertIndex < childList.size() && ! childList.getUnchecked (insertIndex)->isAlwaysOnTop()) + ++insertIndex; + + parentComponent->reorderChildInternal (index, insertIndex); + } + } + } +} + +void Component::setAlwaysOnTop (const bool shouldStayOnTop) +{ + if (shouldStayOnTop != flags.alwaysOnTopFlag) + { + BailOutChecker checker (this); + + flags.alwaysOnTopFlag = shouldStayOnTop; + + if (isOnDesktop()) + { + if (ComponentPeer* const peer = getPeer()) + { + if (! peer->setAlwaysOnTop (shouldStayOnTop)) + { + // some kinds of peer can't change their always-on-top status, so + // for these, we'll need to create a new window + const int oldFlags = peer->getStyleFlags(); + removeFromDesktop(); + addToDesktop (oldFlags); + } + } + } + + if (shouldStayOnTop && ! checker.shouldBailOut()) + toFront (false); + + if (! checker.shouldBailOut()) + internalHierarchyChanged(); + } +} + +bool Component::isAlwaysOnTop() const noexcept +{ + return flags.alwaysOnTopFlag; +} + +//============================================================================== +int Component::proportionOfWidth (const float proportion) const noexcept { return roundToInt (proportion * bounds.getWidth()); } +int Component::proportionOfHeight (const float proportion) const noexcept { return roundToInt (proportion * bounds.getHeight()); } + +int Component::getParentWidth() const noexcept +{ + return parentComponent != nullptr ? parentComponent->getWidth() + : getParentMonitorArea().getWidth(); +} + +int Component::getParentHeight() const noexcept +{ + return parentComponent != nullptr ? parentComponent->getHeight() + : getParentMonitorArea().getHeight(); +} + +int Component::getScreenX() const { return getScreenPosition().x; } +int Component::getScreenY() const { return getScreenPosition().y; } + +Point Component::getScreenPosition() const { return localPointToGlobal (Point()); } +Rectangle Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } + +Rectangle Component::getParentMonitorArea() const +{ + return Desktop::getInstance().getDisplays().getDisplayContaining (getScreenBounds().getCentre()).userArea; +} + +Point Component::getLocalPoint (const Component* source, Point point) const +{ + return ComponentHelpers::convertCoordinate (this, source, point); +} + +Point Component::getLocalPoint (const Component* source, Point point) const +{ + return ComponentHelpers::convertCoordinate (this, source, point); +} + +Rectangle Component::getLocalArea (const Component* source, const Rectangle& area) const +{ + return ComponentHelpers::convertCoordinate (this, source, area); +} + +Point Component::localPointToGlobal (Point point) const +{ + return ComponentHelpers::convertCoordinate (nullptr, this, point); +} + +Point Component::localPointToGlobal (Point point) const +{ + return ComponentHelpers::convertCoordinate (nullptr, this, point); +} + +Rectangle Component::localAreaToGlobal (const Rectangle& area) const +{ + return ComponentHelpers::convertCoordinate (nullptr, this, area); +} + +// Deprecated methods... +Point Component::relativePositionToGlobal (Point relativePosition) const +{ + return localPointToGlobal (relativePosition); +} + +Point Component::globalPositionToRelative (Point screenPosition) const +{ + return getLocalPoint (nullptr, screenPosition); +} + +Point Component::relativePositionToOtherComponent (const Component* const targetComponent, Point positionRelativeToThis) const +{ + return targetComponent == nullptr ? localPointToGlobal (positionRelativeToThis) + : targetComponent->getLocalPoint (this, positionRelativeToThis); +} + + +//============================================================================== +void Component::setBounds (const int x, const int y, int w, int h) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (w < 0) w = 0; + if (h < 0) h = 0; + + const bool wasResized = (getWidth() != w || getHeight() != h); + const bool wasMoved = (getX() != x || getY() != y); + + #if JUCE_DEBUG + // It's a very bad idea to try to resize a window during its paint() method! + jassert (! (flags.isInsidePaintCall && wasResized && isOnDesktop())); + #endif + + if (wasMoved || wasResized) + { + const bool showing = isShowing(); + if (showing) + { + // send a fake mouse move to trigger enter/exit messages if needed.. + sendFakeMouseMove(); + + if (! flags.hasHeavyweightPeerFlag) + repaintParent(); + } + + bounds.setBounds (x, y, w, h); + + if (showing) + { + if (wasResized) + repaint(); + else if (! flags.hasHeavyweightPeerFlag) + repaintParent(); + } + else if (cachedImage != nullptr) + { + cachedImage->invalidateAll(); + } + + flags.isMoveCallbackPending = wasMoved; + flags.isResizeCallbackPending = wasResized; + + if (flags.hasHeavyweightPeerFlag) + if (ComponentPeer* const peer = getPeer()) + peer->updateBounds(); + + sendMovedResizedMessagesIfPending(); + } +} + +void Component::sendMovedResizedMessagesIfPending() +{ + if (flags.isMoveCallbackPending || flags.isResizeCallbackPending) + { + sendMovedResizedMessages (flags.isMoveCallbackPending, flags.isResizeCallbackPending); + + flags.isMoveCallbackPending = false; + flags.isResizeCallbackPending = false; + } +} + +void Component::sendMovedResizedMessages (const bool wasMoved, const bool wasResized) +{ + BailOutChecker checker (this); + + if (wasMoved) + { + moved(); + + if (checker.shouldBailOut()) + return; + } + + if (wasResized) + { + resized(); + + if (checker.shouldBailOut()) + return; + + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked(i)->parentSizeChanged(); + + if (checker.shouldBailOut()) + return; + + i = jmin (i, childComponentList.size()); + } + } + + if (parentComponent != nullptr) + parentComponent->childBoundsChanged (this); + + if (! checker.shouldBailOut()) + componentListeners.callChecked (checker, &ComponentListener::componentMovedOrResized, + *this, wasMoved, wasResized); +} + +void Component::setSize (const int w, const int h) +{ + setBounds (getX(), getY(), w, h); +} + +void Component::setTopLeftPosition (const int x, const int y) +{ + setBounds (x, y, getWidth(), getHeight()); +} + +void Component::setTopLeftPosition (Point pos) +{ + setBounds (pos.x, pos.y, getWidth(), getHeight()); +} + +void Component::setTopRightPosition (const int x, const int y) +{ + setTopLeftPosition (x - getWidth(), y); +} + +void Component::setBounds (const Rectangle& r) +{ + setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight()); +} + +void Component::setBounds (const RelativeRectangle& newBounds) +{ + newBounds.applyToComponent (*this); +} + +void Component::setBounds (const String& newBoundsExpression) +{ + setBounds (RelativeRectangle (newBoundsExpression)); +} + +void Component::setBoundsRelative (const float x, const float y, + const float w, const float h) +{ + const int pw = getParentWidth(); + const int ph = getParentHeight(); + + setBounds (roundToInt (x * pw), + roundToInt (y * ph), + roundToInt (w * pw), + roundToInt (h * ph)); +} + +void Component::setCentrePosition (const int x, const int y) +{ + setTopLeftPosition (x - getWidth() / 2, + y - getHeight() / 2); +} + +void Component::setCentreRelative (const float x, const float y) +{ + setCentrePosition (roundToInt (getParentWidth() * x), + roundToInt (getParentHeight() * y)); +} + +void Component::centreWithSize (const int width, const int height) +{ + const Rectangle parentArea (ComponentHelpers::getParentOrMainMonitorBounds (*this)); + + setBounds (parentArea.getCentreX() - width / 2, + parentArea.getCentreY() - height / 2, + width, height); +} + +void Component::setBoundsInset (const BorderSize& borders) +{ + setBounds (borders.subtractedFrom (ComponentHelpers::getParentOrMainMonitorBounds (*this))); +} + +void Component::setBoundsToFit (int x, int y, int width, int height, + Justification justification, + const bool onlyReduceInSize) +{ + // it's no good calling this method unless both the component and + // target rectangle have a finite size. + jassert (getWidth() > 0 && getHeight() > 0 && width > 0 && height > 0); + + if (getWidth() > 0 && getHeight() > 0 + && width > 0 && height > 0) + { + int newW, newH; + + if (onlyReduceInSize && getWidth() <= width && getHeight() <= height) + { + newW = getWidth(); + newH = getHeight(); + } + else + { + const double imageRatio = getHeight() / (double) getWidth(); + const double targetRatio = height / (double) width; + + if (imageRatio <= targetRatio) + { + newW = width; + newH = jmin (height, roundToInt (newW * imageRatio)); + } + else + { + newH = height; + newW = jmin (width, roundToInt (newH / imageRatio)); + } + } + + if (newW > 0 && newH > 0) + setBounds (justification.appliedToRectangle (Rectangle (newW, newH), + Rectangle (x, y, width, height))); + } +} + +//============================================================================== +void Component::setTransform (const AffineTransform& newTransform) +{ + // If you pass in a transform with no inverse, the component will have no dimensions, + // and there will be all sorts of maths errors when converting coordinates. + jassert (! newTransform.isSingularity()); + + if (newTransform.isIdentity()) + { + if (affineTransform != nullptr) + { + repaint(); + affineTransform = nullptr; + repaint(); + + sendMovedResizedMessages (false, false); + } + } + else if (affineTransform == nullptr) + { + repaint(); + affineTransform = new AffineTransform (newTransform); + repaint(); + sendMovedResizedMessages (false, false); + } + else if (*affineTransform != newTransform) + { + repaint(); + *affineTransform = newTransform; + repaint(); + sendMovedResizedMessages (false, false); + } +} + +bool Component::isTransformed() const noexcept +{ + return affineTransform != nullptr; +} + +AffineTransform Component::getTransform() const +{ + return affineTransform != nullptr ? *affineTransform : AffineTransform::identity; +} + +//============================================================================== +bool Component::hitTest (int x, int y) +{ + if (! flags.ignoresMouseClicksFlag) + return true; + + if (flags.allowChildMouseClicksFlag) + { + for (int i = childComponentList.size(); --i >= 0;) + { + Component& child = *childComponentList.getUnchecked (i); + + if (child.isVisible() + && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y)))) + return true; + } + } + + return false; +} + +void Component::setInterceptsMouseClicks (const bool allowClicks, + const bool allowClicksOnChildComponents) noexcept +{ + flags.ignoresMouseClicksFlag = ! allowClicks; + flags.allowChildMouseClicksFlag = allowClicksOnChildComponents; +} + +void Component::getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, + bool& allowsClicksOnChildComponents) const noexcept +{ + allowsClicksOnThisComponent = ! flags.ignoresMouseClicksFlag; + allowsClicksOnChildComponents = flags.allowChildMouseClicksFlag; +} + +bool Component::contains (Point point) +{ + if (ComponentHelpers::hitTest (*this, point)) + { + if (parentComponent != nullptr) + return parentComponent->contains (ComponentHelpers::convertToParentSpace (*this, point)); + + if (flags.hasHeavyweightPeerFlag) + if (const ComponentPeer* const peer = getPeer()) + return peer->contains (ComponentHelpers::localPositionToRawPeerPos (*this, point), true); + } + + return false; +} + +bool Component::reallyContains (Point point, const bool returnTrueIfWithinAChild) +{ + if (! contains (point)) + return false; + + Component* const top = getTopLevelComponent(); + const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, point)); + + return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition)); +} + +Component* Component::getComponentAt (Point position) +{ + if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) + { + for (int i = childComponentList.size(); --i >= 0;) + { + Component* child = childComponentList.getUnchecked(i); + child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); + + if (child != nullptr) + return child; + } + + return this; + } + + return nullptr; +} + +Component* Component::getComponentAt (const int x, const int y) +{ + return getComponentAt (Point (x, y)); +} + +//============================================================================== +void Component::addChildComponent (Component& child, int zOrder) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + if (child.parentComponent != this) + { + if (child.parentComponent != nullptr) + child.parentComponent->removeChildComponent (&child); + else + child.removeFromDesktop(); + + child.parentComponent = this; + + if (child.isVisible()) + child.repaintParent(); + + if (! child.isAlwaysOnTop()) + { + if (zOrder < 0 || zOrder > childComponentList.size()) + zOrder = childComponentList.size(); + + while (zOrder > 0) + { + if (! childComponentList.getUnchecked (zOrder - 1)->isAlwaysOnTop()) + break; + + --zOrder; + } + } + + childComponentList.insert (zOrder, &child); + + child.internalHierarchyChanged(); + internalChildrenChanged(); + } +} + +void Component::addAndMakeVisible (Component& child, int zOrder) +{ + child.setVisible (true); + addChildComponent (child, zOrder); +} + +void Component::addChildComponent (Component* const child, int zOrder) +{ + if (child != nullptr) + addChildComponent (*child, zOrder); +} + +void Component::addAndMakeVisible (Component* const child, int zOrder) +{ + if (child != nullptr) + addAndMakeVisible (*child, zOrder); +} + +void Component::addChildAndSetID (Component* const child, const String& childID) +{ + if (child != nullptr) + { + child->setComponentID (childID); + addAndMakeVisible (child); + } +} + +void Component::removeChildComponent (Component* const child) +{ + removeChildComponent (childComponentList.indexOf (child), true, true); +} + +Component* Component::removeChildComponent (const int index) +{ + return removeChildComponent (index, true, true); +} + +Component* Component::removeChildComponent (const int index, bool sendParentEvents, const bool sendChildEvents) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN + + Component* const child = childComponentList [index]; + + if (child != nullptr) + { + sendParentEvents = sendParentEvents && child->isShowing(); + + if (sendParentEvents) + { + sendFakeMouseMove(); + + if (child->isVisible()) + child->repaintParent(); + } + + childComponentList.remove (index); + child->parentComponent = nullptr; + + if (child->cachedImage != nullptr) + child->cachedImage->releaseResources(); + + // (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) + if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) + { + if (sendParentEvents) + { + const WeakReference thisPointer (this); + + giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); + + if (thisPointer == nullptr) + return child; + + grabKeyboardFocus(); + } + else + { + giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child); + } + } + + if (sendChildEvents) + child->internalHierarchyChanged(); + + if (sendParentEvents) + internalChildrenChanged(); + } + + return child; +} + +//============================================================================== +void Component::removeAllChildren() +{ + while (childComponentList.size() > 0) + removeChildComponent (childComponentList.size() - 1); +} + +void Component::deleteAllChildren() +{ + while (childComponentList.size() > 0) + delete (removeChildComponent (childComponentList.size() - 1)); +} + +int Component::getNumChildComponents() const noexcept +{ + return childComponentList.size(); +} + +Component* Component::getChildComponent (const int index) const noexcept +{ + return childComponentList [index]; +} + +int Component::getIndexOfChildComponent (const Component* const child) const noexcept +{ + return childComponentList.indexOf (const_cast (child)); +} + +Component* Component::findChildWithID (StringRef targetID) const noexcept +{ + for (int i = childComponentList.size(); --i >= 0;) + { + Component* const c = childComponentList.getUnchecked(i); + if (c->componentID == targetID) + return c; + } + + return nullptr; +} + +Component* Component::getTopLevelComponent() const noexcept +{ + const Component* comp = this; + + while (comp->parentComponent != nullptr) + comp = comp->parentComponent; + + return const_cast (comp); +} + +bool Component::isParentOf (const Component* possibleChild) const noexcept +{ + while (possibleChild != nullptr) + { + possibleChild = possibleChild->parentComponent; + + if (possibleChild == this) + return true; + } + + return false; +} + +//============================================================================== +void Component::parentHierarchyChanged() {} +void Component::childrenChanged() {} + +void Component::internalChildrenChanged() +{ + if (componentListeners.isEmpty()) + { + childrenChanged(); + } + else + { + BailOutChecker checker (this); + + childrenChanged(); + + if (! checker.shouldBailOut()) + componentListeners.callChecked (checker, &ComponentListener::componentChildrenChanged, *this); + } +} + +void Component::internalHierarchyChanged() +{ + BailOutChecker checker (this); + + parentHierarchyChanged(); + + if (checker.shouldBailOut()) + return; + + componentListeners.callChecked (checker, &ComponentListener::componentParentHierarchyChanged, *this); + + if (checker.shouldBailOut()) + return; + + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked (i)->internalHierarchyChanged(); + + if (checker.shouldBailOut()) + { + // you really shouldn't delete the parent component during a callback telling you + // that it's changed.. + jassertfalse; + return; + } + + i = jmin (i, childComponentList.size()); + } +} + +//============================================================================== +#if JUCE_MODAL_LOOPS_PERMITTED +int Component::runModalLoop() +{ + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + // use a callback so this can be called from non-gui threads + return (int) (pointer_sized_int) MessageManager::getInstance() + ->callFunctionOnMessageThread (&ComponentHelpers::runModalLoopCallback, this); + } + + if (! isCurrentlyModal()) + enterModalState (true); + + return ModalComponentManager::getInstance()->runEventLoopForCurrentComponent(); +} +#endif + +//============================================================================== +void Component::enterModalState (const bool shouldTakeKeyboardFocus, + ModalComponentManager::Callback* callback, + const bool deleteWhenDismissed) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + // Check for an attempt to make a component modal when it already is! + // This can cause nasty problems.. + jassert (! flags.currentlyModalFlag); + + if (! isCurrentlyModal()) + { + ModalComponentManager* const mcm = ModalComponentManager::getInstance(); + mcm->startModal (this, deleteWhenDismissed); + mcm->attachCallback (this, callback); + + flags.currentlyModalFlag = true; + setVisible (true); + + if (shouldTakeKeyboardFocus) + grabKeyboardFocus(); + } +} + +void Component::exitModalState (const int returnValue) +{ + if (flags.currentlyModalFlag) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + ModalComponentManager::getInstance()->endModal (this, returnValue); + flags.currentlyModalFlag = false; + + ModalComponentManager::getInstance()->bringModalComponentsToFront(); + } + else + { + class ExitModalStateMessage : public CallbackMessage + { + public: + ExitModalStateMessage (Component* const c, const int res) + : target (c), result (res) {} + + void messageCallback() override + { + if (target.get() != nullptr) // (get() required for VS2003 bug) + target->exitModalState (result); + } + + private: + WeakReference target; + int result; + }; + + (new ExitModalStateMessage (this, returnValue))->post(); + } + } +} + +bool Component::isCurrentlyModal() const noexcept +{ + return flags.currentlyModalFlag + && getCurrentlyModalComponent() == this; +} + +bool Component::isCurrentlyBlockedByAnotherModalComponent() const +{ + Component* const mc = getCurrentlyModalComponent(); + + return ! (mc == nullptr || mc == this || mc->isParentOf (this) + || mc->canModalEventBeSentToComponent (this)); +} + +int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() noexcept +{ + return ModalComponentManager::getInstance()->getNumModalComponents(); +} + +Component* JUCE_CALLTYPE Component::getCurrentlyModalComponent (int index) noexcept +{ + return ModalComponentManager::getInstance()->getModalComponent (index); +} + +//============================================================================== +void Component::setBroughtToFrontOnMouseClick (const bool shouldBeBroughtToFront) noexcept +{ + flags.bringToFrontOnClickFlag = shouldBeBroughtToFront; +} + +bool Component::isBroughtToFrontOnMouseClick() const noexcept +{ + return flags.bringToFrontOnClickFlag; +} + +//============================================================================== +void Component::setMouseCursor (const MouseCursor& newCursor) +{ + if (cursor != newCursor) + { + cursor = newCursor; + + if (flags.visibleFlag) + updateMouseCursor(); + } +} + +MouseCursor Component::getMouseCursor() +{ + return cursor; +} + +void Component::updateMouseCursor() const +{ + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); +} + +//============================================================================== +void Component::setRepaintsOnMouseActivity (const bool shouldRepaint) noexcept +{ + flags.repaintOnMouseActivityFlag = shouldRepaint; +} + +//============================================================================== +void Component::setAlpha (const float newAlpha) +{ + const uint8 newIntAlpha = (uint8) (255 - jlimit (0, 255, roundToInt (newAlpha * 255.0))); + + if (componentTransparency != newIntAlpha) + { + componentTransparency = newIntAlpha; + + if (flags.hasHeavyweightPeerFlag) + { + if (ComponentPeer* const peer = getPeer()) + peer->setAlpha (newAlpha); + } + else + { + repaint(); + } + } +} + +float Component::getAlpha() const +{ + return (255 - componentTransparency) / 255.0f; +} + +//============================================================================== +void Component::repaint() +{ + internalRepaintUnchecked (getLocalBounds(), true); +} + +void Component::repaint (const int x, const int y, const int w, const int h) +{ + internalRepaint (Rectangle (x, y, w, h)); +} + +void Component::repaint (const Rectangle& area) +{ + internalRepaint (area); +} + +void Component::repaintParent() +{ + if (parentComponent != nullptr) + parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, getLocalBounds())); +} + +void Component::internalRepaint (const Rectangle& area) +{ + const Rectangle r (area.getIntersection (getLocalBounds())); + + if (! r.isEmpty()) + internalRepaintUnchecked (r, false); +} + +void Component::internalRepaintUnchecked (const Rectangle& area, const bool isEntireComponent) +{ + if (flags.visibleFlag) + { + if (cachedImage != nullptr) + if (! (isEntireComponent ? cachedImage->invalidateAll() + : cachedImage->invalidate (area))) + return; + + if (flags.hasHeavyweightPeerFlag) + { + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (ComponentPeer* const peer = getPeer()) + { + // Tweak the scaling so that the component's integer size exactly aligns with the peer's scaled size + const Rectangle peerBounds (peer->getBounds()); + const Rectangle scaled (area * Point (peerBounds.getWidth() / (float) getWidth(), + peerBounds.getHeight() / (float) getHeight())); + + peer->repaint (affineTransform != nullptr ? scaled.transformedBy (*affineTransform) : scaled); + } + } + else + { + if (parentComponent != nullptr) + parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, area)); + } + } +} + +//============================================================================== +void Component::paint (Graphics&) +{ + // if your component is marked as opaque, you must implement a paint + // method and ensure that its entire area is completely painted. + jassert (getBounds().isEmpty() || ! isOpaque()); +} + +void Component::paintOverChildren (Graphics&) +{ + // all painting is done in the subclasses +} + +//============================================================================== +void Component::paintWithinParentContext (Graphics& g) +{ + g.setOrigin (getPosition()); + + if (cachedImage != nullptr) + cachedImage->paint (g); + else + paintEntireComponent (g, false); +} + +void Component::paintComponentAndChildren (Graphics& g) +{ + const Rectangle clipBounds (g.getClipBounds()); + + if (flags.dontClipGraphicsFlag) + { + paint (g); + } + else + { + g.saveState(); + + if (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point()) || ! g.isClipEmpty()) + paint (g); + + g.restoreState(); + } + + for (int i = 0; i < childComponentList.size(); ++i) + { + Component& child = *childComponentList.getUnchecked (i); + + if (child.isVisible()) + { + if (child.affineTransform != nullptr) + { + g.saveState(); + g.addTransform (*child.affineTransform); + + if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) || g.reduceClipRegion (child.getBounds())) + child.paintWithinParentContext (g); + + g.restoreState(); + } + else if (clipBounds.intersects (child.getBounds())) + { + g.saveState(); + + if (child.flags.dontClipGraphicsFlag) + { + child.paintWithinParentContext (g); + } + else if (g.reduceClipRegion (child.getBounds())) + { + bool nothingClipped = true; + + for (int j = i + 1; j < childComponentList.size(); ++j) + { + const Component& sibling = *childComponentList.getUnchecked (j); + + if (sibling.flags.opaqueFlag && sibling.isVisible() && sibling.affineTransform == nullptr) + { + nothingClipped = false; + g.excludeClipRegion (sibling.getBounds()); + } + } + + if (nothingClipped || ! g.isClipEmpty()) + child.paintWithinParentContext (g); + } + + g.restoreState(); + } + } + } + + g.saveState(); + paintOverChildren (g); + g.restoreState(); +} + +void Component::paintEntireComponent (Graphics& g, const bool ignoreAlphaLevel) +{ + // If sizing a top-level-window and the OS paint message is delivered synchronously + // before resized() is called, then we'll invoke the callback here, to make sure + // the components inside have had a chance to sort their sizes out.. + #if JUCE_DEBUG + if (! flags.isInsidePaintCall) // (avoids an assertion in plugins hosted in WaveLab) + #endif + sendMovedResizedMessagesIfPending(); + + #if JUCE_DEBUG + flags.isInsidePaintCall = true; + #endif + + if (effect != nullptr) + { + const float scale = g.getInternalContext().getPhysicalPixelScaleFactor(); + + const Rectangle scaledBounds (getLocalBounds() * scale); + + Image effectImage (flags.opaqueFlag ? Image::RGB : Image::ARGB, + scaledBounds.getWidth(), scaledBounds.getHeight(), ! flags.opaqueFlag); + { + Graphics g2 (effectImage); + g2.addTransform (AffineTransform::scale (scaledBounds.getWidth() / (float) getWidth(), + scaledBounds.getHeight() / (float) getHeight())); + paintComponentAndChildren (g2); + } + + g.saveState(); + g.addTransform (AffineTransform::scale (1.0f / scale)); + effect->applyEffect (effectImage, g, scale, ignoreAlphaLevel ? 1.0f : getAlpha()); + g.restoreState(); + } + else if (componentTransparency > 0 && ! ignoreAlphaLevel) + { + if (componentTransparency < 255) + { + g.beginTransparencyLayer (getAlpha()); + paintComponentAndChildren (g); + g.endTransparencyLayer(); + } + } + else + { + paintComponentAndChildren (g); + } + + #if JUCE_DEBUG + flags.isInsidePaintCall = false; + #endif +} + +void Component::setPaintingIsUnclipped (const bool shouldPaintWithoutClipping) noexcept +{ + flags.dontClipGraphicsFlag = shouldPaintWithoutClipping; +} + +//============================================================================== +Image Component::createComponentSnapshot (const Rectangle& areaToGrab, + bool clipImageToComponentBounds, float scaleFactor) +{ + Rectangle r (areaToGrab); + + if (clipImageToComponentBounds) + r = r.getIntersection (getLocalBounds()); + + if (r.isEmpty()) + return Image(); + + const int w = roundToInt (scaleFactor * r.getWidth()); + const int h = roundToInt (scaleFactor * r.getHeight()); + + Image image (flags.opaqueFlag ? Image::RGB : Image::ARGB, w, h, true); + + Graphics g (image); + + if (w != getWidth() || h != getHeight()) + g.addTransform (AffineTransform::scale (w / (float) r.getWidth(), + h / (float) r.getHeight())); + g.setOrigin (-r.getPosition()); + + paintEntireComponent (g, true); + + return image; +} + +void Component::setComponentEffect (ImageEffectFilter* const newEffect) +{ + if (effect != newEffect) + { + effect = newEffect; + repaint(); + } +} + +//============================================================================== +LookAndFeel& Component::getLookAndFeel() const noexcept +{ + for (const Component* c = this; c != nullptr; c = c->parentComponent) + if (c->lookAndFeel != nullptr) + return *(c->lookAndFeel); + + return LookAndFeel::getDefaultLookAndFeel(); +} + +void Component::setLookAndFeel (LookAndFeel* const newLookAndFeel) +{ + if (lookAndFeel != newLookAndFeel) + { + lookAndFeel = newLookAndFeel; + sendLookAndFeelChange(); + } +} + +void Component::lookAndFeelChanged() {} +void Component::colourChanged() {} + +void Component::sendLookAndFeelChange() +{ + const WeakReference safePointer (this); + repaint(); + lookAndFeelChanged(); + + if (safePointer != nullptr) + { + colourChanged(); + + if (safePointer != nullptr) + { + for (int i = childComponentList.size(); --i >= 0;) + { + childComponentList.getUnchecked (i)->sendLookAndFeelChange(); + + if (safePointer == nullptr) + return; + + i = jmin (i, childComponentList.size()); + } + } + } +} + +Colour Component::findColour (const int colourId, const bool inheritFromParent) const +{ + if (const var* const v = properties.getVarPointer (ComponentHelpers::getColourPropertyId (colourId))) + return Colour ((uint32) static_cast (*v)); + + if (inheritFromParent && parentComponent != nullptr + && (lookAndFeel == nullptr || ! lookAndFeel->isColourSpecified (colourId))) + return parentComponent->findColour (colourId, true); + + return getLookAndFeel().findColour (colourId); +} + +bool Component::isColourSpecified (const int colourId) const +{ + return properties.contains (ComponentHelpers::getColourPropertyId (colourId)); +} + +void Component::removeColour (const int colourId) +{ + if (properties.remove (ComponentHelpers::getColourPropertyId (colourId))) + colourChanged(); +} + +void Component::setColour (const int colourId, Colour colour) +{ + if (properties.set (ComponentHelpers::getColourPropertyId (colourId), (int) colour.getARGB())) + colourChanged(); +} + +void Component::copyAllExplicitColoursTo (Component& target) const +{ + bool changed = false; + + for (int i = properties.size(); --i >= 0;) + { + const Identifier name (properties.getName(i)); + + if (name.toString().startsWith ("jcclr_")) + if (target.properties.set (name, properties [name])) + changed = true; + } + + if (changed) + target.colourChanged(); +} + +//============================================================================== +MarkerList* Component::getMarkers (bool /*xAxis*/) +{ + return nullptr; +} + +//============================================================================== +Component::Positioner::Positioner (Component& c) noexcept + : component (c) +{ +} + +Component::Positioner* Component::getPositioner() const noexcept +{ + return positioner; +} + +void Component::setPositioner (Positioner* newPositioner) +{ + // You can only assign a positioner to the component that it was created for! + jassert (newPositioner == nullptr || this == &(newPositioner->getComponent())); + positioner = newPositioner; +} + +//============================================================================== +Rectangle Component::getLocalBounds() const noexcept +{ + return bounds.withZeroOrigin(); +} + +Rectangle Component::getBoundsInParent() const noexcept +{ + return affineTransform == nullptr ? bounds + : bounds.transformedBy (*affineTransform); +} + +//============================================================================== +void Component::mouseEnter (const MouseEvent&) {} +void Component::mouseExit (const MouseEvent&) {} +void Component::mouseDown (const MouseEvent&) {} +void Component::mouseUp (const MouseEvent&) {} +void Component::mouseDrag (const MouseEvent&) {} +void Component::mouseMove (const MouseEvent&) {} +void Component::mouseDoubleClick (const MouseEvent&) {} + +void Component::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel) +{ + // the base class just passes this event up to its parent.. + if (parentComponent != nullptr) + parentComponent->mouseWheelMove (e.getEventRelativeTo (parentComponent), wheel); +} + +void Component::mouseMagnify (const MouseEvent& e, float magnifyAmount) +{ + // the base class just passes this event up to its parent.. + if (parentComponent != nullptr) + parentComponent->mouseMagnify (e.getEventRelativeTo (parentComponent), magnifyAmount); +} + +//============================================================================== +void Component::resized() {} +void Component::moved() {} +void Component::childBoundsChanged (Component*) {} +void Component::parentSizeChanged() {} + +void Component::addComponentListener (ComponentListener* const newListener) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS + if (getParentComponent() != nullptr) + ASSERT_MESSAGE_MANAGER_IS_LOCKED; + #endif + + componentListeners.add (newListener); +} + +void Component::removeComponentListener (ComponentListener* const listenerToRemove) +{ + componentListeners.remove (listenerToRemove); +} + +//============================================================================== +void Component::inputAttemptWhenModal() +{ + ModalComponentManager::getInstance()->bringModalComponentsToFront(); + getLookAndFeel().playAlertSound(); +} + +bool Component::canModalEventBeSentToComponent (const Component*) +{ + return false; +} + +void Component::internalModalInputAttempt() +{ + if (Component* const current = getCurrentlyModalComponent()) + current->inputAttemptWhenModal(); +} + +//============================================================================== +void Component::postCommandMessage (const int commandId) +{ + class CustomCommandMessage : public CallbackMessage + { + public: + CustomCommandMessage (Component* const c, const int command) + : target (c), commandId (command) {} + + void messageCallback() override + { + if (target.get() != nullptr) // (get() required for VS2003 bug) + target->handleCommandMessage (commandId); + } + + private: + WeakReference target; + int commandId; + }; + + (new CustomCommandMessage (this, commandId))->post(); +} + +void Component::handleCommandMessage (int) +{ + // used by subclasses +} + +//============================================================================== +void Component::addMouseListener (MouseListener* const newListener, + const bool wantsEventsForAllNestedChildComponents) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + // If you register a component as a mouselistener for itself, it'll receive all the events + // twice - once via the direct callback that all components get anyway, and then again as a listener! + jassert ((newListener != this) || wantsEventsForAllNestedChildComponents); + + if (mouseListeners == nullptr) + mouseListeners = new MouseListenerList(); + + mouseListeners->addListener (newListener, wantsEventsForAllNestedChildComponents); +} + +void Component::removeMouseListener (MouseListener* const listenerToRemove) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (mouseListeners != nullptr) + mouseListeners->removeListener (listenerToRemove); +} + +//============================================================================== +void Component::internalMouseEnter (MouseInputSource source, Point relativePos, Time time) +{ + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // if something else is modal, always just show a normal mouse cursor + source.showMouseCursor (MouseCursor::NormalCursor); + return; + } + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, 0, false); + mouseEnter (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, &MouseListener::mouseEnter, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseEnter, me); +} + +void Component::internalMouseExit (MouseInputSource source, Point relativePos, Time time) +{ + if (flags.repaintOnMouseActivityFlag) + repaint(); + + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, 0, false); + + mouseExit (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, &MouseListener::mouseExit, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); +} + +void Component::internalMouseDown (MouseInputSource source, Point relativePos, Time time) +{ + Desktop& desktop = Desktop::getInstance(); + BailOutChecker checker (this); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + flags.mouseDownWasBlocked = true; + internalModalInputAttempt(); + + if (checker.shouldBailOut()) + return; + + // If processing the input attempt has exited the modal loop, we'll allow the event + // to be delivered.. + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, + source.getNumberOfMultipleClicks(), false); + + desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); + return; + } + } + + flags.mouseDownWasBlocked = false; + + for (Component* c = this; c != nullptr; c = c->parentComponent) + { + if (c->isBroughtToFrontOnMouseClick()) + { + c->toFront (true); + + if (checker.shouldBailOut()) + return; + } + } + + if (! flags.dontFocusOnMouseClickFlag) + { + grabFocusInternal (focusChangedByMouseClick, true); + + if (checker.shouldBailOut()) + return; + } + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, + source.getNumberOfMultipleClicks(), false); + mouseDown (me); + + if (checker.shouldBailOut()) + return; + + desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDown, me); +} + +void Component::internalMouseUp (MouseInputSource source, Point relativePos, + Time time, const ModifierKeys oldModifiers) +{ + if (flags.mouseDownWasBlocked && isCurrentlyBlockedByAnotherModalComponent()) + return; + + BailOutChecker checker (this); + + if (flags.repaintOnMouseActivityFlag) + repaint(); + + const MouseEvent me (source, relativePos, + oldModifiers, this, this, time, + getLocalPoint (nullptr, source.getLastMouseDownPosition()), + source.getLastMouseDownTime(), + source.getNumberOfMultipleClicks(), + source.hasMouseMovedSignificantlySincePressed()); + mouseUp (me); + + if (checker.shouldBailOut()) + return; + + Desktop& desktop = Desktop::getInstance(); + desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseUp, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseUp, me); + + if (checker.shouldBailOut()) + return; + + // check for double-click + if (me.getNumberOfClicks() >= 2) + { + mouseDoubleClick (me); + + if (checker.shouldBailOut()) + return; + + desktop.mouseListeners.callChecked (checker, &MouseListener::mouseDoubleClick, me); + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDoubleClick, me); + } +} + +void Component::internalMouseDrag (MouseInputSource source, Point relativePos, Time time) +{ + if (! isCurrentlyBlockedByAnotherModalComponent()) + { + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, + source.getCurrentModifiers(), this, this, time, + getLocalPoint (nullptr, source.getLastMouseDownPosition()), + source.getLastMouseDownTime(), + source.getNumberOfMultipleClicks(), + source.hasMouseMovedSignificantlySincePressed()); + mouseDrag (me); + + if (checker.shouldBailOut()) + return; + + Desktop::getInstance().getMouseListeners().callChecked (checker, &MouseListener::mouseDrag, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseDrag, me); + } +} + +void Component::internalMouseMove (MouseInputSource source, Point relativePos, Time time) +{ + Desktop& desktop = Desktop::getInstance(); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + desktop.sendMouseMove(); + } + else + { + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, 0, false); + mouseMove (me); + + if (checker.shouldBailOut()) + return; + + desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseMove, me); + + MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseMove, me); + } +} + +void Component::internalMouseWheel (MouseInputSource source, Point relativePos, + Time time, const MouseWheelDetails& wheel) +{ + Desktop& desktop = Desktop::getInstance(); + BailOutChecker checker (this); + + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, 0, false); + + if (isCurrentlyBlockedByAnotherModalComponent()) + { + // allow blocked mouse-events to go to global listeners.. + desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheel); + } + else + { + mouseWheelMove (me, wheel); + + if (checker.shouldBailOut()) + return; + + desktop.mouseListeners.callChecked (checker, &MouseListener::mouseWheelMove, me, wheel); + + if (! checker.shouldBailOut()) + MouseListenerList::sendWheelEvent (*this, checker, me, wheel); + } +} + +void Component::internalMagnifyGesture (MouseInputSource source, Point relativePos, + Time time, float amount) +{ + if (! isCurrentlyBlockedByAnotherModalComponent()) + { + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + this, this, time, relativePos, time, 0, false); + + mouseMagnify (me, amount); + } +} + +void Component::sendFakeMouseMove() const +{ + MouseInputSource mainMouse = Desktop::getInstance().getMainMouseSource(); + + if (! mainMouse.isDragging()) + mainMouse.triggerFakeMove(); +} + +void JUCE_CALLTYPE Component::beginDragAutoRepeat (const int interval) +{ + Desktop::getInstance().beginDragAutoRepeat (interval); +} + +//============================================================================== +void Component::broughtToFront() +{ +} + +void Component::internalBroughtToFront() +{ + if (flags.hasHeavyweightPeerFlag) + Desktop::getInstance().componentBroughtToFront (this); + + BailOutChecker checker (this); + broughtToFront(); + + if (checker.shouldBailOut()) + return; + + componentListeners.callChecked (checker, &ComponentListener::componentBroughtToFront, *this); + + if (checker.shouldBailOut()) + return; + + // When brought to the front and there's a modal component blocking this one, + // we need to bring the modal one to the front instead.. + if (Component* const cm = getCurrentlyModalComponent()) + if (cm->getTopLevelComponent() != getTopLevelComponent()) + ModalComponentManager::getInstance()->bringModalComponentsToFront (false); // very important that this is false, otherwise in Windows, + // non-front components can't get focus when another modal comp is + // active, and therefore can't receive mouse-clicks +} + +//============================================================================== +void Component::focusGained (FocusChangeType) {} +void Component::focusLost (FocusChangeType) {} +void Component::focusOfChildComponentChanged (FocusChangeType) {} + +void Component::internalFocusGain (const FocusChangeType cause) +{ + internalFocusGain (cause, WeakReference (this)); +} + +void Component::internalFocusGain (const FocusChangeType cause, const WeakReference& safePointer) +{ + focusGained (cause); + + if (safePointer != nullptr) + internalChildFocusChange (cause, safePointer); +} + +void Component::internalFocusLoss (const FocusChangeType cause) +{ + const WeakReference safePointer (this); + + focusLost (focusChangedDirectly); + + if (safePointer != nullptr) + internalChildFocusChange (cause, safePointer); +} + +void Component::internalChildFocusChange (FocusChangeType cause, const WeakReference& safePointer) +{ + const bool childIsNowFocused = hasKeyboardFocus (true); + + if (flags.childCompFocusedFlag != childIsNowFocused) + { + flags.childCompFocusedFlag = childIsNowFocused; + + focusOfChildComponentChanged (cause); + + if (safePointer == nullptr) + return; + } + + if (parentComponent != nullptr) + parentComponent->internalChildFocusChange (cause, WeakReference (parentComponent)); +} + +void Component::setWantsKeyboardFocus (const bool wantsFocus) noexcept +{ + flags.wantsFocusFlag = wantsFocus; +} + +void Component::setMouseClickGrabsKeyboardFocus (const bool shouldGrabFocus) +{ + flags.dontFocusOnMouseClickFlag = ! shouldGrabFocus; +} + +bool Component::getMouseClickGrabsKeyboardFocus() const noexcept +{ + return ! flags.dontFocusOnMouseClickFlag; +} + +bool Component::getWantsKeyboardFocus() const noexcept +{ + return flags.wantsFocusFlag && ! flags.isDisabledFlag; +} + +void Component::setFocusContainer (const bool shouldBeFocusContainer) noexcept +{ + flags.isFocusContainerFlag = shouldBeFocusContainer; +} + +bool Component::isFocusContainer() const noexcept +{ + return flags.isFocusContainerFlag; +} + +static const Identifier juce_explicitFocusOrderId ("_jexfo"); + +int Component::getExplicitFocusOrder() const +{ + return properties [juce_explicitFocusOrderId]; +} + +void Component::setExplicitFocusOrder (const int newFocusOrderIndex) +{ + properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); +} + +KeyboardFocusTraverser* Component::createFocusTraverser() +{ + if (flags.isFocusContainerFlag || parentComponent == nullptr) + return new KeyboardFocusTraverser(); + + return parentComponent->createFocusTraverser(); +} + +void Component::takeKeyboardFocus (const FocusChangeType cause) +{ + // give the focus to this component + if (currentlyFocusedComponent != this) + { + // get the focus onto our desktop window + if (ComponentPeer* const peer = getPeer()) + { + const WeakReference safePointer (this); + peer->grabFocus(); + + if (peer->isFocused() && currentlyFocusedComponent != this) + { + WeakReference componentLosingFocus (currentlyFocusedComponent); + currentlyFocusedComponent = this; + + Desktop::getInstance().triggerFocusCallback(); + + // call this after setting currentlyFocusedComponent so that the one that's + // losing it has a chance to see where focus is going + if (componentLosingFocus != nullptr) + componentLosingFocus->internalFocusLoss (cause); + + if (currentlyFocusedComponent == this) + internalFocusGain (cause, safePointer); + } + } + } +} + +void Component::grabFocusInternal (const FocusChangeType cause, const bool canTryParent) +{ + if (isShowing()) + { + if (flags.wantsFocusFlag && (isEnabled() || parentComponent == nullptr)) + { + takeKeyboardFocus (cause); + } + else + { + if (isParentOf (currentlyFocusedComponent) + && currentlyFocusedComponent->isShowing()) + { + // do nothing if the focused component is actually a child of ours.. + } + else + { + // find the default child component.. + ScopedPointer traverser (createFocusTraverser()); + + if (traverser != nullptr) + { + Component* const defaultComp = traverser->getDefaultComponent (this); + traverser = nullptr; + + if (defaultComp != nullptr) + { + defaultComp->grabFocusInternal (cause, false); + return; + } + } + + if (canTryParent && parentComponent != nullptr) + { + // if no children want it and we're allowed to try our parent comp, + // then pass up to parent, which will try our siblings. + parentComponent->grabFocusInternal (cause, true); + } + } + } + } +} + +void Component::grabKeyboardFocus() +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + grabFocusInternal (focusChangedDirectly, true); +} + +void Component::moveKeyboardFocusToSibling (const bool moveToNext) +{ + // if component methods are being called from threads other than the message + // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (parentComponent != nullptr) + { + ScopedPointer traverser (createFocusTraverser()); + + if (traverser != nullptr) + { + Component* const nextComp = moveToNext ? traverser->getNextComponent (this) + : traverser->getPreviousComponent (this); + traverser = nullptr; + + if (nextComp != nullptr) + { + if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) + { + const WeakReference nextCompPointer (nextComp); + internalModalInputAttempt(); + + if (nextCompPointer == nullptr || nextComp->isCurrentlyBlockedByAnotherModalComponent()) + return; + } + + nextComp->grabFocusInternal (focusChangedByTabKey, true); + return; + } + } + + parentComponent->moveKeyboardFocusToSibling (moveToNext); + } +} + +bool Component::hasKeyboardFocus (const bool trueIfChildIsFocused) const +{ + return (currentlyFocusedComponent == this) + || (trueIfChildIsFocused && isParentOf (currentlyFocusedComponent)); +} + +Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() noexcept +{ + return currentlyFocusedComponent; +} + +void JUCE_CALLTYPE Component::unfocusAllComponents() +{ + if (Component* c = getCurrentlyFocusedComponent()) + c->giveAwayFocus (true); +} + +void Component::giveAwayFocus (const bool sendFocusLossEvent) +{ + Component* const componentLosingFocus = currentlyFocusedComponent; + currentlyFocusedComponent = nullptr; + + if (sendFocusLossEvent && componentLosingFocus != nullptr) + componentLosingFocus->internalFocusLoss (focusChangedDirectly); + + Desktop::getInstance().triggerFocusCallback(); +} + +//============================================================================== +bool Component::isEnabled() const noexcept +{ + return (! flags.isDisabledFlag) + && (parentComponent == nullptr || parentComponent->isEnabled()); +} + +void Component::setEnabled (const bool shouldBeEnabled) +{ + if (flags.isDisabledFlag == shouldBeEnabled) + { + flags.isDisabledFlag = ! shouldBeEnabled; + + // if any parent components are disabled, setting our flag won't make a difference, + // so no need to send a change message + if (parentComponent == nullptr || parentComponent->isEnabled()) + sendEnablementChangeMessage(); + } +} + +void Component::enablementChanged() {} + +void Component::sendEnablementChangeMessage() +{ + const WeakReference safePointer (this); + + enablementChanged(); + + if (safePointer == nullptr) + return; + + for (int i = getNumChildComponents(); --i >= 0;) + { + if (Component* const c = getChildComponent (i)) + { + c->sendEnablementChangeMessage(); + + if (safePointer == nullptr) + return; + } + } +} + +//============================================================================== +bool Component::isMouseOver (const bool includeChildren) const +{ + const Array& mouseSources = Desktop::getInstance().getMouseSources(); + + for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi) + { + Component* const c = mi->getComponentUnderMouse(); + + if ((c == this || (includeChildren && isParentOf (c))) + && c->reallyContains (c->getLocalPoint (nullptr, mi->getScreenPosition()).roundToInt(), false) + && (mi->isMouse() || mi->isDragging())) + return true; + } + + return false; +} + +bool Component::isMouseButtonDown() const +{ + const Array& mouseSources = Desktop::getInstance().getMouseSources(); + + for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi) + if (mi->isDragging() && mi->getComponentUnderMouse() == this) + return true; + + return false; +} + +bool Component::isMouseOverOrDragging() const +{ + const Array& mouseSources = Desktop::getInstance().getMouseSources(); + + for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi) + if (mi->getComponentUnderMouse() == this + && (mi->isMouse() || mi->isDragging())) + return true; + + return false; +} + +bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() noexcept +{ + return ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown(); +} + +Point Component::getMouseXYRelative() const +{ + return getLocalPoint (nullptr, Desktop::getMousePosition()); +} + +//============================================================================== +void Component::addKeyListener (KeyListener* const newListener) +{ + if (keyListeners == nullptr) + keyListeners = new Array(); + + keyListeners->addIfNotAlreadyThere (newListener); +} + +void Component::removeKeyListener (KeyListener* const listenerToRemove) +{ + if (keyListeners != nullptr) + keyListeners->removeFirstMatchingValue (listenerToRemove); +} + +bool Component::keyPressed (const KeyPress&) { return false; } +bool Component::keyStateChanged (const bool /*isKeyDown*/) { return false; } + +void Component::modifierKeysChanged (const ModifierKeys& modifiers) +{ + if (parentComponent != nullptr) + parentComponent->modifierKeysChanged (modifiers); +} + +void Component::internalModifierKeysChanged() +{ + sendFakeMouseMove(); + modifierKeysChanged (ModifierKeys::getCurrentModifiers()); +} + +//============================================================================== +Component::BailOutChecker::BailOutChecker (Component* const component) + : safePointer (component) +{ + jassert (component != nullptr); +} + +bool Component::BailOutChecker::shouldBailOut() const noexcept +{ + return safePointer == nullptr; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h new file mode 100644 index 0000000..edc1957 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h @@ -0,0 +1,2378 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_COMPONENT_H_INCLUDED +#define JUCE_COMPONENT_H_INCLUDED + + +//============================================================================== +/** + The base class for all JUCE user-interface objects. +*/ +class JUCE_API Component : public MouseListener +{ +public: + //============================================================================== + /** Creates a component. + + To get it to actually appear, you'll also need to: + - Either add it to a parent component or use the addToDesktop() method to + make it a desktop window + - Set its size and position to something sensible + - Use setVisible() to make it visible + + And for it to serve any useful purpose, you'll need to write a + subclass of Component or use one of the other types of component from + the library. + */ + Component() noexcept; + + /** Destructor. + + Note that when a component is deleted, any child components it contains are NOT + automatically deleted. It's your responsibilty to manage their lifespan - you + may want to use helper methods like deleteAllChildren(), or less haphazard + approaches like using ScopedPointers or normal object aggregation to manage them. + + If the component being deleted is currently the child of another one, then during + deletion, it will be removed from its parent, and the parent will receive a childrenChanged() + callback. Any ComponentListener objects that have registered with it will also have their + ComponentListener::componentBeingDeleted() methods called. + */ + virtual ~Component(); + + //============================================================================== + /** Creates a component, setting its name at the same time. + @see getName, setName + */ + explicit Component (const String& componentName) noexcept; + + /** Returns the name of this component. + @see setName + */ + const String& getName() const noexcept { return componentName; } + + /** Sets the name of this component. + + When the name changes, all registered ComponentListeners will receive a + ComponentListener::componentNameChanged() callback. + + @see getName + */ + virtual void setName (const String& newName); + + /** Returns the ID string that was set by setComponentID(). + @see setComponentID, findChildWithID + */ + const String& getComponentID() const noexcept { return componentID; } + + /** Sets the component's ID string. + You can retrieve the ID using getComponentID(). + @see getComponentID, findChildWithID + */ + void setComponentID (const String& newID); + + //============================================================================== + /** Makes the component visible or invisible. + + This method will show or hide the component. + Note that components default to being non-visible when first created. + Also note that visible components won't be seen unless all their parent components + are also visible. + + This method will call visibilityChanged() and also componentVisibilityChanged() + for any component listeners that are interested in this component. + + @param shouldBeVisible whether to show or hide the component + @see isVisible, isShowing, visibilityChanged, ComponentListener::componentVisibilityChanged + */ + virtual void setVisible (bool shouldBeVisible); + + /** Tests whether the component is visible or not. + + this doesn't necessarily tell you whether this comp is actually on the screen + because this depends on whether all the parent components are also visible - use + isShowing() to find this out. + + @see isShowing, setVisible + */ + bool isVisible() const noexcept { return flags.visibleFlag; } + + /** Called when this component's visibility changes. + @see setVisible, isVisible + */ + virtual void visibilityChanged(); + + /** Tests whether this component and all its parents are visible. + + @returns true only if this component and all its parents are visible. + @see isVisible + */ + bool isShowing() const; + + //============================================================================== + /** Makes this component appear as a window on the desktop. + + Note that before calling this, you should make sure that the component's opacity is + set correctly using setOpaque(). If the component is non-opaque, the windowing + system will try to create a special transparent window for it, which will generally take + a lot more CPU to operate (and might not even be possible on some platforms). + + If the component is inside a parent component at the time this method is called, it + will be first be removed from that parent. Likewise if a component on the desktop + is subsequently added to another component, it'll be removed from the desktop. + + @param windowStyleFlags a combination of the flags specified in the + ComponentPeer::StyleFlags enum, which define the + window's characteristics. + @param nativeWindowToAttachTo this allows an OS object to be passed-in as the window + in which the juce component should place itself. On Windows, + this would be a HWND, a HIViewRef on the Mac. Not necessarily + supported on all platforms, and best left as 0 unless you know + what you're doing + @see removeFromDesktop, isOnDesktop, userTriedToCloseWindow, + getPeer, ComponentPeer::setMinimised, ComponentPeer::StyleFlags, + ComponentPeer::getStyleFlags, ComponentPeer::setFullScreen + */ + virtual void addToDesktop (int windowStyleFlags, + void* nativeWindowToAttachTo = nullptr); + + /** If the component is currently showing on the desktop, this will hide it. + + You can also use setVisible() to hide a desktop window temporarily, but + removeFromDesktop() will free any system resources that are being used up. + + @see addToDesktop, isOnDesktop + */ + void removeFromDesktop(); + + /** Returns true if this component is currently showing on the desktop. + @see addToDesktop, removeFromDesktop + */ + bool isOnDesktop() const noexcept; + + /** Returns the heavyweight window that contains this component. + + If this component is itself on the desktop, this will return the window + object that it is using. Otherwise, it will return the window of + its top-level parent component. + + This may return nullptr if there isn't a desktop component. + + @see addToDesktop, isOnDesktop + */ + ComponentPeer* getPeer() const; + + /** For components on the desktop, this is called if the system wants to close the window. + + This is a signal that either the user or the system wants the window to close. The + default implementation of this method will trigger an assertion to warn you that your + component should do something about it, but you can override this to ignore the event + if you want. + */ + virtual void userTriedToCloseWindow(); + + /** Called for a desktop component which has just been minimised or un-minimised. + This will only be called for components on the desktop. + @see getPeer, ComponentPeer::setMinimised, ComponentPeer::isMinimised + */ + virtual void minimisationStateChanged (bool isNowMinimised); + + /** Returns the default scale factor to use for this component when it is placed + on the desktop. + The default implementation of this method just returns the value from + Desktop::getGlobalScaleFactor(), but it can be overridden if a particular component + has different requirements. The method only used if this component is added + to the desktop - it has no effect for child components. + */ + virtual float getDesktopScaleFactor() const; + + //============================================================================== + /** Brings the component to the front of its siblings. + + If some of the component's siblings have had their 'always-on-top' flag set, + then they will still be kept in front of this one (unless of course this + one is also 'always-on-top'). + + @param shouldAlsoGainFocus if true, this will also try to assign keyboard focus + to the component (see grabKeyboardFocus() for more details) + @see toBack, toBehind, setAlwaysOnTop + */ + void toFront (bool shouldAlsoGainFocus); + + /** Changes this component's z-order to be at the back of all its siblings. + + If the component is set to be 'always-on-top', it will only be moved to the + back of the other other 'always-on-top' components. + + @see toFront, toBehind, setAlwaysOnTop + */ + void toBack(); + + /** Changes this component's z-order so that it's just behind another component. + @see toFront, toBack + */ + void toBehind (Component* other); + + /** Sets whether the component should always be kept at the front of its siblings. + @see isAlwaysOnTop + */ + void setAlwaysOnTop (bool shouldStayOnTop); + + /** Returns true if this component is set to always stay in front of its siblings. + @see setAlwaysOnTop + */ + bool isAlwaysOnTop() const noexcept; + + //============================================================================== + /** Returns the x coordinate of the component's left edge. + This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getX() const noexcept { return bounds.getX(); } + + /** Returns the y coordinate of the top of this component. + This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getY() const noexcept { return bounds.getY(); } + + /** Returns the component's width in pixels. */ + int getWidth() const noexcept { return bounds.getWidth(); } + + /** Returns the component's height in pixels. */ + int getHeight() const noexcept { return bounds.getHeight(); } + + /** Returns the x coordinate of the component's right-hand edge. + This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getRight() const noexcept { return bounds.getRight(); } + + /** Returns the component's top-left position as a Point. */ + Point getPosition() const noexcept { return bounds.getPosition(); } + + /** Returns the y coordinate of the bottom edge of this component. + This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + int getBottom() const noexcept { return bounds.getBottom(); } + + /** Returns this component's bounding box. + The rectangle returned is relative to the top-left of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. + */ + const Rectangle& getBounds() const noexcept { return bounds; } + + /** Returns the component's bounds, relative to its own origin. + This is like getBounds(), but returns the rectangle in local coordinates, In practice, it'll + return a rectangle with position (0, 0), and the same size as this component. + */ + Rectangle getLocalBounds() const noexcept; + + /** Returns the area of this component's parent which this component covers. + + The returned area is relative to the parent's coordinate space. + If the component has an affine transform specified, then the resulting area will be + the smallest rectangle that fully covers the component's transformed bounding box. + If this component has no parent, the return value will simply be the same as getBounds(). + */ + Rectangle getBoundsInParent() const noexcept; + + //============================================================================== + /** Returns this component's x coordinate relative the screen's top-left origin. + @see getX, localPointToGlobal + */ + int getScreenX() const; + + /** Returns this component's y coordinate relative the screen's top-left origin. + @see getY, localPointToGlobal + */ + int getScreenY() const; + + /** Returns the position of this component's top-left corner relative to the screen's top-left. + @see getScreenBounds + */ + Point getScreenPosition() const; + + /** Returns the bounds of this component, relative to the screen's top-left. + @see getScreenPosition + */ + Rectangle getScreenBounds() const; + + /** Converts a point to be relative to this component's coordinate space. + + This takes a point relative to a different component, and returns its position relative to this + component. If the sourceComponent parameter is null, the source point is assumed to be a global + screen coordinate. + */ + Point getLocalPoint (const Component* sourceComponent, + Point pointRelativeToSourceComponent) const; + + /** Converts a point to be relative to this component's coordinate space. + + This takes a point relative to a different component, and returns its position relative to this + component. If the sourceComponent parameter is null, the source point is assumed to be a global + screen coordinate. + */ + Point getLocalPoint (const Component* sourceComponent, + Point pointRelativeToSourceComponent) const; + + /** Converts a rectangle to be relative to this component's coordinate space. + + This takes a rectangle that is relative to a different component, and returns its position relative + to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be + a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + */ + Rectangle getLocalArea (const Component* sourceComponent, + const Rectangle& areaRelativeToSourceComponent) const; + + /** Converts a point relative to this component's top-left into a screen coordinate. + @see getLocalPoint, localAreaToGlobal + */ + Point localPointToGlobal (Point localPoint) const; + + /** Converts a point relative to this component's top-left into a screen coordinate. + @see getLocalPoint, localAreaToGlobal + */ + Point localPointToGlobal (Point localPoint) const; + + /** Converts a rectangle from this component's coordinate space to a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. + @see getLocalPoint, localPointToGlobal + */ + Rectangle localAreaToGlobal (const Rectangle& localArea) const; + + //============================================================================== + /** Moves the component to a new position. + + Changes the component's top-left position (without changing its size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds, ComponentListener::componentMovedOrResized + */ + void setTopLeftPosition (int x, int y); + + /** Moves the component to a new position. + + Changes the component's top-left position (without changing its size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds, ComponentListener::componentMovedOrResized + */ + void setTopLeftPosition (Point newTopLeftPosition); + + /** Moves the component to a new position. + + Changes the position of the component's top-right corner (keeping it the same size). + The position is relative to the top-left of the component's parent. + + If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + */ + void setTopRightPosition (int x, int y); + + /** Changes the size of the component. + + A synchronous call to resized() will be occur if the size actually changes. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + */ + void setSize (int newWidth, int newHeight); + + /** Changes the component's position and size. + + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen is the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setTopLeftPosition, setSize, ComponentListener::componentMovedOrResized + */ + void setBounds (int x, int y, int width, int height); + + /** Changes the component's position and size. + + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen is the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + + @see setBounds + */ + void setBounds (const Rectangle& newBounds); + + /** Changes the component's position and size. + + This is similar to the other setBounds() methods, but uses RelativeRectangle::applyToComponent() + to set the position, This uses a Component::Positioner to make sure that any dynamic + expressions are used in the RelativeRectangle will be automatically re-applied to the + component's bounds when the source values change. See RelativeRectangle::applyToComponent() + for more details. + + When using relative expressions, the following symbols are available: + - "left", "right", "top", "bottom" refer to the position of those edges in this component, so + e.g. for a component whose width is always 100, you might set the right edge to the "left + 100". + - "[id].left", "[id].right", "[id].top", "[id].bottom", "[id].width", "[id].height", where [id] is + the identifier of one of this component's siblings. A component's identifier is set with + Component::setComponentID(). So for example if you want your component to always be 50 pixels to the + right of the one called "xyz", you could set your left edge to be "xyz.right + 50". + - Instead of an [id], you can use the name "parent" to refer to this component's parent. Like + any other component, these values are relative to their component's parent, so "parent.right" won't be + very useful for positioning a component because it refers to a position with the parent's parent.. but + "parent.width" can be used for setting positions relative to the parent's size. E.g. to make a 10x10 + component which remains 1 pixel away from its parent's bottom-right, you could use + "right - 10, bottom - 10, parent.width - 1, parent.height - 1". + - The name of one of the parent component's markers can also be used as a symbol. For markers to be + used, the parent component must implement its Component::getMarkers() method, and return at least one + valid MarkerList. So if you want your component's top edge to be 10 pixels below the + marker called "foobar", you'd set it to "foobar + 10". + + See the Expression class for details about the operators that are supported, but for example + if you wanted to make your component remain centred within its parent with a size of 100, 100, + you could express it as: + @code myComp.setBounds (RelativeBounds ("parent.width / 2 - 50, parent.height / 2 - 50, left + 100, top + 100")); + @endcode + ..or an alternative way to achieve the same thing: + @code myComp.setBounds (RelativeBounds ("right - 100, bottom - 100, parent.width / 2 + 50, parent.height / 2 + 50")); + @endcode + + Or if you wanted a 100x100 component whose top edge is lined up to a marker called "topMarker" and + which is positioned 50 pixels to the right of another component called "otherComp", you could write: + @code myComp.setBounds (RelativeBounds ("otherComp.right + 50, topMarker, left + 100, top + 100")); + @endcode + + Be careful not to make your coordinate expressions recursive, though, or exceptions and assertions will + be thrown! + + @see setBounds, RelativeRectangle::applyToComponent(), Expression + */ + void setBounds (const RelativeRectangle& newBounds); + + /** Sets the component's bounds with an expression. + The string is parsed as a RelativeRectangle expression - see the notes for + Component::setBounds (const RelativeRectangle&) for more information. This method is + basically just a shortcut for writing setBounds (RelativeRectangle ("...")) + */ + void setBounds (const String& newBoundsExpression); + + /** Changes the component's position and size in terms of fractions of its parent's size. + + The values are factors of the parent's size, so for example + setBoundsRelative (0.2f, 0.2f, 0.5f, 0.5f) would give it half the + width and height of the parent, with its top-left position 20% of + the way across and down the parent. + + @see setBounds + */ + void setBoundsRelative (float proportionalX, float proportionalY, + float proportionalWidth, float proportionalHeight); + + /** Changes the component's position and size based on the amount of space to leave around it. + + This will position the component within its parent, leaving the specified number of + pixels around each edge. + + @see setBounds + */ + void setBoundsInset (const BorderSize& borders); + + /** Positions the component within a given rectangle, keeping its proportions + unchanged. + + If onlyReduceInSize is false, the component will be resized to fill as much of the + rectangle as possible without changing its aspect ratio (the component's + current size is used to determine its aspect ratio, so a zero-size component + won't work here). If onlyReduceInSize is true, it will only be resized if it's + too big to fit inside the rectangle. + + It will then be positioned within the rectangle according to the justification flags + specified. + + @see setBounds + */ + void setBoundsToFit (int x, int y, int width, int height, + Justification justification, + bool onlyReduceInSize); + + /** Changes the position of the component's centre. + + Leaves the component's size unchanged, but sets the position of its centre + relative to its parent's top-left. + + @see setBounds + */ + void setCentrePosition (int x, int y); + + /** Changes the position of the component's centre. + + Leaves the position unchanged, but positions its centre relative to its + parent's size. E.g. setCentreRelative (0.5f, 0.5f) would place it centrally in + its parent. + */ + void setCentreRelative (float x, float y); + + /** Changes the component's size and centres it within its parent. + + After changing the size, the component will be moved so that it's + centred within its parent. If the component is on the desktop (or has no + parent component), then it'll be centred within the main monitor area. + */ + void centreWithSize (int width, int height); + + //============================================================================== + /** Sets a transform matrix to be applied to this component. + + If you set a transform for a component, the component's position will be warped by it, relative to + the component's parent's top-left origin. This means that the values you pass into setBounds() will no + longer reflect the actual area within the parent that the component covers, as the bounds will be + transformed and the component will probably end up actually appearing somewhere else within its parent. + + When using transforms you need to be extremely careful when converting coordinates between the + coordinate spaces of different components or the screen - you should always use getLocalPoint(), + getLocalArea(), etc to do this, and never just manually add a component's position to a point in order to + convert it between different components (but I'm sure you would never have done that anyway...). + + Currently, transforms are not supported for desktop windows, so the transform will be ignored if you + put a component on the desktop. + + To remove a component's transform, simply pass AffineTransform::identity as the parameter to this method. + */ + void setTransform (const AffineTransform& transform); + + /** Returns the transform that is currently being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + AffineTransform getTransform() const; + + /** Returns true if a non-identity transform is being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + bool isTransformed() const noexcept; + + //============================================================================== + /** Returns a proportion of the component's width. + This is a handy equivalent of (getWidth() * proportion). + */ + int proportionOfWidth (float proportion) const noexcept; + + /** Returns a proportion of the component's height. + This is a handy equivalent of (getHeight() * proportion). + */ + int proportionOfHeight (float proportion) const noexcept; + + /** Returns the width of the component's parent. + + If the component has no parent (i.e. if it's on the desktop), this will return + the width of the screen. + */ + int getParentWidth() const noexcept; + + /** Returns the height of the component's parent. + + If the component has no parent (i.e. if it's on the desktop), this will return + the height of the screen. + */ + int getParentHeight() const noexcept; + + /** Returns the screen coordinates of the monitor that contains this component. + + If there's only one monitor, this will return its size - if there are multiple + monitors, it will return the area of the monitor that contains the component's + centre. + */ + Rectangle getParentMonitorArea() const; + + //============================================================================== + /** Returns the number of child components that this component contains. + + @see getChildComponent, getIndexOfChildComponent + */ + int getNumChildComponents() const noexcept; + + /** Returns one of this component's child components, by it index. + + The component with index 0 is at the back of the z-order, the one at the + front will have index (getNumChildComponents() - 1). + + If the index is out-of-range, this will return a null pointer. + + @see getNumChildComponents, getIndexOfChildComponent + */ + Component* getChildComponent (int index) const noexcept; + + /** Returns the index of this component in the list of child components. + + A value of 0 means it is first in the list (i.e. behind all other components). Higher + values are further towards the front. + + Returns -1 if the component passed-in is not a child of this component. + + @see getNumChildComponents, getChildComponent, addChildComponent, toFront, toBack, toBehind + */ + int getIndexOfChildComponent (const Component* child) const noexcept; + + /** Looks for a child component with the specified ID. + @see setComponentID, getComponentID + */ + Component* findChildWithID (StringRef componentID) const noexcept; + + /** Adds a child component to this one. + + Adding a child component does not mean that the component will own or delete the child - it's + your responsibility to delete the component. Note that it's safe to delete a component + without first removing it from its parent - doing so will automatically remove it and + send out the appropriate notifications before the deletion completes. + + If the child is already a child of this component, then no action will be taken, and its + z-order will be left unchanged. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + @see removeChildComponent, addAndMakeVisible, addChildAndSetID, getChild, ComponentListener::componentChildrenChanged + */ + void addChildComponent (Component* child, int zOrder = -1); + + /** Adds a child component to this one. + + Adding a child component does not mean that the component will own or delete the child - it's + your responsibility to delete the component. Note that it's safe to delete a component + without first removing it from its parent - doing so will automatically remove it and + send out the appropriate notifications before the deletion completes. + + If the child is already a child of this component, then no action will be taken, and its + z-order will be left unchanged. + + @param child the new component to add. If the component passed-in is already + the child of another component, it'll first be removed from it current parent. + @param zOrder The index in the child-list at which this component should be inserted. + A value of -1 will insert it in front of the others, 0 is the back. + @see removeChildComponent, addAndMakeVisible, addChildAndSetID, getChild, ComponentListener::componentChildrenChanged + */ + void addChildComponent (Component& child, int zOrder = -1); + + /** Adds a child component to this one, and also makes the child visible if it isn't already. + + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. + */ + void addAndMakeVisible (Component* child, int zOrder = -1); + + /** Adds a child component to this one, and also makes the child visible if it isn't already. + + This is the same as calling setVisible (true) on the child and then addChildComponent(). + See addChildComponent() for more details. + */ + void addAndMakeVisible (Component& child, int zOrder = -1); + + /** Adds a child component to this one, makes it visible, and sets its component ID. + @see addAndMakeVisible, addChildComponent + */ + void addChildAndSetID (Component* child, const String& componentID); + + /** Removes one of this component's child-components. + + If the child passed-in isn't actually a child of this component (either because + it's invalid or is the child of a different parent), then no action is taken. + + Note that removing a child will not delete it! But it's ok to delete a component + without first removing it - doing so will automatically remove it and send out the + appropriate notifications before the deletion completes. + + @see addChildComponent, ComponentListener::componentChildrenChanged + */ + void removeChildComponent (Component* childToRemove); + + /** Removes one of this component's child-components by index. + + This will return a pointer to the component that was removed, or null if + the index was out-of-range. + + Note that removing a child will not delete it! But it's ok to delete a component + without first removing it - doing so will automatically remove it and send out the + appropriate notifications before the deletion completes. + + @see addChildComponent, ComponentListener::componentChildrenChanged + */ + Component* removeChildComponent (int childIndexToRemove); + + /** Removes all this component's children. + Note that this won't delete them! To do that, use deleteAllChildren() instead. + */ + void removeAllChildren(); + + /** Removes all this component's children, and deletes them. + @see removeAllChildren + */ + void deleteAllChildren(); + + /** Returns the component which this component is inside. + + If this is the highest-level component or hasn't yet been added to + a parent, this will return null. + */ + Component* getParentComponent() const noexcept { return parentComponent; } + + /** Searches the parent components for a component of a specified class. + + For example findParentComponentOfClass \() would return the first parent + component that can be dynamically cast to a MyComp, or will return 0 if none + of the parents are suitable. + */ + template + TargetClass* findParentComponentOfClass() const + { + for (Component* p = parentComponent; p != nullptr; p = p->parentComponent) + if (TargetClass* const target = dynamic_cast (p)) + return target; + + return nullptr; + } + + /** Returns the highest-level component which contains this one or its parents. + + This will search upwards in the parent-hierarchy from this component, until it + finds the highest one that doesn't have a parent (i.e. is on the desktop or + not yet added to a parent), and will return that. + */ + Component* getTopLevelComponent() const noexcept; + + /** Checks whether a component is anywhere inside this component or its children. + + This will recursively check through this component's children to see if the + given component is anywhere inside. + */ + bool isParentOf (const Component* possibleChild) const noexcept; + + //============================================================================== + /** Called to indicate that the component's parents have changed. + + When a component is added or removed from its parent, this method will + be called on all of its children (recursively - so all children of its + children will also be called as well). + + Subclasses can override this if they need to react to this in some way. + + @see getParentComponent, isShowing, ComponentListener::componentParentHierarchyChanged + */ + virtual void parentHierarchyChanged(); + + /** Subclasses can use this callback to be told when children are added or removed, or + when their z-order changes. + @see parentHierarchyChanged, ComponentListener::componentChildrenChanged + */ + virtual void childrenChanged(); + + //============================================================================== + /** Tests whether a given point inside the component. + + Overriding this method allows you to create components which only intercept + mouse-clicks within a user-defined area. + + This is called to find out whether a particular x, y coordinate is + considered to be inside the component or not, and is used by methods such + as contains() and getComponentAt() to work out which component + the mouse is clicked on. + + Components with custom shapes will probably want to override it to perform + some more complex hit-testing. + + The default implementation of this method returns either true or false, + depending on the value that was set by calling setInterceptsMouseClicks() (true + is the default return value). + + Note that the hit-test region is not related to the opacity with which + areas of a component are painted. + + Applications should never call hitTest() directly - instead use the + contains() method, because this will also test for occlusion by the + component's parent. + + Note that for components on the desktop, this method will be ignored, because it's + not always possible to implement this behaviour on all platforms. + + @param x the x coordinate to test, relative to the left hand edge of this + component. This value is guaranteed to be greater than or equal to + zero, and less than the component's width + @param y the y coordinate to test, relative to the top edge of this + component. This value is guaranteed to be greater than or equal to + zero, and less than the component's height + @returns true if the click is considered to be inside the component + @see setInterceptsMouseClicks, contains + */ + virtual bool hitTest (int x, int y); + + /** Changes the default return value for the hitTest() method. + + Setting this to false is an easy way to make a component pass its mouse-clicks + through to the components behind it. + + When a component is created, the default setting for this is true. + + @param allowClicksOnThisComponent if true, hitTest() will always return true; if false, it will + return false (or true for child components if allowClicksOnChildComponents + is true) + @param allowClicksOnChildComponents if this is true and allowClicksOnThisComponent is false, then child + components can be clicked on as normal but clicks on this component pass + straight through; if this is false and allowClicksOnThisComponent + is false, then neither this component nor any child components can + be clicked on + @see hitTest, getInterceptsMouseClicks + */ + void setInterceptsMouseClicks (bool allowClicksOnThisComponent, + bool allowClicksOnChildComponents) noexcept; + + /** Retrieves the current state of the mouse-click interception flags. + + On return, the two parameters are set to the state used in the last call to + setInterceptsMouseClicks(). + + @see setInterceptsMouseClicks + */ + void getInterceptsMouseClicks (bool& allowsClicksOnThisComponent, + bool& allowsClicksOnChildComponents) const noexcept; + + + /** Returns true if a given point lies within this component or one of its children. + + Never override this method! Use hitTest to create custom hit regions. + + @param localPoint the coordinate to test, relative to this component's top-left. + @returns true if the point is within the component's hit-test area, but only if + that part of the component isn't clipped by its parent component. Note + that this won't take into account any overlapping sibling components + which might be in the way - for that, see reallyContains() + @see hitTest, reallyContains, getComponentAt + */ + bool contains (Point localPoint); + + /** Returns true if a given point lies in this component, taking any overlapping + siblings into account. + + @param localPoint the coordinate to test, relative to this component's top-left. + @param returnTrueIfWithinAChild if the point actually lies within a child of this component, + this determines whether that is counted as a hit. + @see contains, getComponentAt + */ + bool reallyContains (Point localPoint, bool returnTrueIfWithinAChild); + + /** Returns the component at a certain point within this one. + + @param x the x coordinate to test, relative to this component's left edge. + @param y the y coordinate to test, relative to this component's top edge. + @returns the component that is at this position - which may be 0, this component, + or one of its children. Note that overlapping siblings that might actually + be in the way are not taken into account by this method - to account for these, + instead call getComponentAt on the top-level parent of this component. + @see hitTest, contains, reallyContains + */ + Component* getComponentAt (int x, int y); + + /** Returns the component at a certain point within this one. + + @param position the coordinate to test, relative to this component's top-left. + @returns the component that is at this position - which may be 0, this component, + or one of its children. Note that overlapping siblings that might actually + be in the way are not taken into account by this method - to account for these, + instead call getComponentAt on the top-level parent of this component. + @see hitTest, contains, reallyContains + */ + Component* getComponentAt (Point position); + + //============================================================================== + /** Marks the whole component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the component + as 'dirty'. At some point in the near future the operating system will send a paint + message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + If the setBufferedToImage() method has been used to cause this component to use a + buffer, the repaint() call will invalidate the cached buffer. If setCachedComponentImage() + has been used to provide a custom image cache, that cache will be invalidated appropriately. + + To redraw just a subsection of the component rather than the whole thing, + use the repaint (int, int, int, int) method. + + @see paint + */ + void repaint(); + + /** Marks a subsection of this component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the given region + of the component as 'dirty'. At some point in the near future the operating system + will send a paint message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + The region that is passed in will be clipped to keep it within the bounds of this + component. + + @see repaint() + */ + void repaint (int x, int y, int width, int height); + + /** Marks a subsection of this component as needing to be redrawn. + + Calling this will not do any repainting immediately, but will mark the given region + of the component as 'dirty'. At some point in the near future the operating system + will send a paint message, which will redraw all the dirty regions of all components. + There's no guarantee about how soon after calling repaint() the redraw will actually + happen, and other queued events may be delivered before a redraw is done. + + The region that is passed in will be clipped to keep it within the bounds of this + component. + + @see repaint() + */ + void repaint (const Rectangle& area); + + //============================================================================== + /** Makes the component use an internal buffer to optimise its redrawing. + + Setting this flag to true will cause the component to allocate an + internal buffer into which it paints itself, so that when asked to + redraw itself, it can use this buffer rather than actually calling the + paint() method. + + The buffer is kept until the repaint() method is called directly on + this component (or until it is resized), when the image is invalidated + and then redrawn the next time the component is painted. + + Note that only the drawing that happens within the component's paint() + method is drawn into the buffer, it's child components are not buffered, and + nor is the paintOverChildren() method. + + @see repaint, paint, createComponentSnapshot + */ + void setBufferedToImage (bool shouldBeBuffered); + + /** Generates a snapshot of part of this component. + + This will return a new Image, the size of the rectangle specified, + containing a snapshot of the specified area of the component and all + its children. + + The image may or may not have an alpha-channel, depending on whether the + image is opaque or not. + + If the clipImageToComponentBounds parameter is true and the area is greater than + the size of the component, it'll be clipped. If clipImageToComponentBounds is false + then parts of the component beyond its bounds can be drawn. + + @see paintEntireComponent + */ + Image createComponentSnapshot (const Rectangle& areaToGrab, + bool clipImageToComponentBounds = true, + float scaleFactor = 1.0f); + + /** Draws this component and all its subcomponents onto the specified graphics + context. + + You should very rarely have to use this method, it's simply there in case you need + to draw a component with a custom graphics context for some reason, e.g. for + creating a snapshot of the component. + + It calls paint(), paintOverChildren() and recursively calls paintEntireComponent() + on its children in order to render the entire tree. + + The graphics context may be left in an undefined state after this method returns, + so you may need to reset it if you're going to use it again. + + If ignoreAlphaLevel is false, then the component will be drawn with the opacity level + specified by getAlpha(); if ignoreAlphaLevel is true, then this will be ignored and + an alpha of 1.0 will be used. + */ + void paintEntireComponent (Graphics& context, bool ignoreAlphaLevel); + + /** This allows you to indicate that this component doesn't require its graphics + context to be clipped when it is being painted. + + Most people will never need to use this setting, but in situations where you have a very large + number of simple components being rendered, and where they are guaranteed never to do any drawing + beyond their own boundaries, setting this to true will reduce the overhead involved in clipping + the graphics context that gets passed to the component's paint() callback. + If you enable this mode, you'll need to make sure your paint method doesn't call anything like + Graphics::fillAll(), and doesn't draw beyond the component's bounds, because that'll produce + artifacts. Your component also can't have any child components that may be placed beyond its + bounds. + */ + void setPaintingIsUnclipped (bool shouldPaintWithoutClipping) noexcept; + + //============================================================================== + /** Adds an effect filter to alter the component's appearance. + + When a component has an effect filter set, then this is applied to the + results of its paint() method. There are a few preset effects, such as + a drop-shadow or glow, but they can be user-defined as well. + + The effect that is passed in will not be deleted by the component - the + caller must take care of deleting it. + + To remove an effect from a component, pass a null pointer in as the parameter. + + @see ImageEffectFilter, DropShadowEffect, GlowEffect + */ + void setComponentEffect (ImageEffectFilter* newEffect); + + /** Returns the current component effect. + @see setComponentEffect + */ + ImageEffectFilter* getComponentEffect() const noexcept { return effect; } + + //============================================================================== + /** Finds the appropriate look-and-feel to use for this component. + + If the component hasn't had a look-and-feel explicitly set, this will + return the parent's look-and-feel, or just the default one if there's no + parent. + + @see setLookAndFeel, lookAndFeelChanged + */ + LookAndFeel& getLookAndFeel() const noexcept; + + /** Sets the look and feel to use for this component. + + This will also change the look and feel for any child components that haven't + had their look set explicitly. + + The object passed in will not be deleted by the component, so it's the caller's + responsibility to manage it. It may be used at any time until this component + has been deleted. + + Calling this method will also invoke the sendLookAndFeelChange() method. + + @see getLookAndFeel, lookAndFeelChanged + */ + void setLookAndFeel (LookAndFeel* newLookAndFeel); + + /** Called to let the component react to a change in the look-and-feel setting. + + When the look-and-feel is changed for a component, this will be called in + all its child components, recursively. + + It can also be triggered manually by the sendLookAndFeelChange() method, in case + an application uses a LookAndFeel class that might have changed internally. + + @see sendLookAndFeelChange, getLookAndFeel + */ + virtual void lookAndFeelChanged(); + + /** Calls the lookAndFeelChanged() method in this component and all its children. + + This will recurse through the children and their children, calling lookAndFeelChanged() + on them all. + + @see lookAndFeelChanged + */ + void sendLookAndFeelChange(); + + //============================================================================== + /** Indicates whether any parts of the component might be transparent. + + Components that always paint all of their contents with solid colour and + thus completely cover any components behind them should use this method + to tell the repaint system that they are opaque. + + This information is used to optimise drawing, because it means that + objects underneath opaque windows don't need to be painted. + + By default, components are considered transparent, unless this is used to + make it otherwise. + + @see isOpaque + */ + void setOpaque (bool shouldBeOpaque); + + /** Returns true if no parts of this component are transparent. + + @returns the value that was set by setOpaque, (the default being false) + @see setOpaque + */ + bool isOpaque() const noexcept; + + //============================================================================== + /** Indicates whether the component should be brought to the front when clicked. + + Setting this flag to true will cause the component to be brought to the front + when the mouse is clicked somewhere inside it or its child components. + + Note that a top-level desktop window might still be brought to the front by the + operating system when it's clicked, depending on how the OS works. + + By default this is set to false. + + @see setMouseClickGrabsKeyboardFocus + */ + void setBroughtToFrontOnMouseClick (bool shouldBeBroughtToFront) noexcept; + + /** Indicates whether the component should be brought to the front when clicked-on. + @see setBroughtToFrontOnMouseClick + */ + bool isBroughtToFrontOnMouseClick() const noexcept; + + //============================================================================== + // Keyboard focus methods + + /** Sets a flag to indicate whether this component needs keyboard focus or not. + + By default components aren't actually interested in gaining the + focus, but this method can be used to turn this on. + + See the grabKeyboardFocus() method for details about the way a component + is chosen to receive the focus. + + @see grabKeyboardFocus, getWantsKeyboardFocus + */ + void setWantsKeyboardFocus (bool wantsFocus) noexcept; + + /** Returns true if the component is interested in getting keyboard focus. + + This returns the flag set by setWantsKeyboardFocus(). The default + setting is false. + + @see setWantsKeyboardFocus + */ + bool getWantsKeyboardFocus() const noexcept; + + //============================================================================== + /** Chooses whether a click on this component automatically grabs the focus. + + By default this is set to true, but you might want a component which can + be focused, but where you don't want the user to be able to affect it directly + by clicking. + */ + void setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus); + + /** Returns the last value set with setMouseClickGrabsKeyboardFocus(). + See setMouseClickGrabsKeyboardFocus() for more info. + */ + bool getMouseClickGrabsKeyboardFocus() const noexcept; + + //============================================================================== + /** Tries to give keyboard focus to this component. + + When the user clicks on a component or its grabKeyboardFocus() + method is called, the following procedure is used to work out which + component should get it: + + - if the component that was clicked on actually wants focus (as indicated + by calling getWantsKeyboardFocus), it gets it. + - if the component itself doesn't want focus, it will try to pass it + on to whichever of its children is the default component, as determined by + KeyboardFocusTraverser::getDefaultComponent() + - if none of its children want focus at all, it will pass it up to its + parent instead, unless it's a top-level component without a parent, + in which case it just takes the focus itself. + + @see setWantsKeyboardFocus, getWantsKeyboardFocus, hasKeyboardFocus, + getCurrentlyFocusedComponent, focusGained, focusLost, + keyPressed, keyStateChanged + */ + void grabKeyboardFocus(); + + /** Returns true if this component currently has the keyboard focus. + + @param trueIfChildIsFocused if this is true, then the method returns true if + either this component or any of its children (recursively) + have the focus. If false, the method only returns true if + this component has the focus. + + @see grabKeyboardFocus, setWantsKeyboardFocus, getCurrentlyFocusedComponent, + focusGained, focusLost + */ + bool hasKeyboardFocus (bool trueIfChildIsFocused) const; + + /** Returns the component that currently has the keyboard focus. + @returns the focused component, or null if nothing is focused. + */ + static Component* JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept; + + /** If any component has keyboard focus, this will defocus it. */ + static void JUCE_CALLTYPE unfocusAllComponents(); + + //============================================================================== + /** Tries to move the keyboard focus to one of this component's siblings. + + This will try to move focus to either the next or previous component. (This + is the method that is used when shifting focus by pressing the tab key). + + Components for which getWantsKeyboardFocus() returns false are not looked at. + + @param moveToNext if true, the focus will move forwards; if false, it will + move backwards + @see grabKeyboardFocus, setFocusContainer, setWantsKeyboardFocus + */ + void moveKeyboardFocusToSibling (bool moveToNext); + + /** Creates a KeyboardFocusTraverser object to use to determine the logic by + which focus should be passed from this component. + + The default implementation of this method will return a default + KeyboardFocusTraverser if this component is a focus container (as determined + by the setFocusContainer() method). If the component isn't a focus + container, then it will recursively ask its parents for a KeyboardFocusTraverser. + + If you overrride this to return a custom KeyboardFocusTraverser, then + this component and all its sub-components will use the new object to + make their focusing decisions. + + The method should return a new object, which the caller is required to + delete when no longer needed. + */ + virtual KeyboardFocusTraverser* createFocusTraverser(); + + /** Returns the focus order of this component, if one has been specified. + + By default components don't have a focus order - in that case, this + will return 0. Lower numbers indicate that the component will be + earlier in the focus traversal order. + + To change the order, call setExplicitFocusOrder(). + + The focus order may be used by the KeyboardFocusTraverser class as part of + its algorithm for deciding the order in which components should be traversed. + See the KeyboardFocusTraverser class for more details on this. + + @see moveKeyboardFocusToSibling, createFocusTraverser, KeyboardFocusTraverser + */ + int getExplicitFocusOrder() const; + + /** Sets the index used in determining the order in which focusable components + should be traversed. + + A value of 0 or less is taken to mean that no explicit order is wanted, and + that traversal should use other factors, like the component's position. + + @see getExplicitFocusOrder, moveKeyboardFocusToSibling + */ + void setExplicitFocusOrder (int newFocusOrderIndex); + + /** Indicates whether this component is a parent for components that can have + their focus traversed. + + This flag is used by the default implementation of the createFocusTraverser() + method, which uses the flag to find the first parent component (of the currently + focused one) which wants to be a focus container. + + So using this method to set the flag to 'true' causes this component to + act as the top level within which focus is passed around. + + @see isFocusContainer, createFocusTraverser, moveKeyboardFocusToSibling + */ + void setFocusContainer (bool shouldBeFocusContainer) noexcept; + + /** Returns true if this component has been marked as a focus container. + + See setFocusContainer() for more details. + + @see setFocusContainer, moveKeyboardFocusToSibling, createFocusTraverser + */ + bool isFocusContainer() const noexcept; + + //============================================================================== + /** Returns true if the component (and all its parents) are enabled. + + Components are enabled by default, and can be disabled with setEnabled(). Exactly + what difference this makes to the component depends on the type. E.g. buttons + and sliders will choose to draw themselves differently, etc. + + Note that if one of this component's parents is disabled, this will always + return false, even if this component itself is enabled. + + @see setEnabled, enablementChanged + */ + bool isEnabled() const noexcept; + + /** Enables or disables this component. + + Disabling a component will also cause all of its child components to become + disabled. + + Similarly, enabling a component which is inside a disabled parent + component won't make any difference until the parent is re-enabled. + + @see isEnabled, enablementChanged + */ + void setEnabled (bool shouldBeEnabled); + + /** Callback to indicate that this component has been enabled or disabled. + + This can be triggered by one of the component's parent components + being enabled or disabled, as well as changes to the component itself. + + The default implementation of this method does nothing; your class may + wish to repaint itself or something when this happens. + + @see setEnabled, isEnabled + */ + virtual void enablementChanged(); + + /** Changes the transparency of this component. + When painted, the entire component and all its children will be rendered + with this as the overall opacity level, where 0 is completely invisible, and + 1.0 is fully opaque (i.e. normal). + + @see getAlpha + */ + void setAlpha (float newAlpha); + + /** Returns the component's current transparancy level. + See setAlpha() for more details. + */ + float getAlpha() const; + + //============================================================================== + /** Changes the mouse cursor shape to use when the mouse is over this component. + + Note that the cursor set by this method can be overridden by the getMouseCursor + method. + + @see MouseCursor + */ + void setMouseCursor (const MouseCursor& cursorType); + + /** Returns the mouse cursor shape to use when the mouse is over this component. + + The default implementation will return the cursor that was set by setCursor() + but can be overridden for more specialised purposes, e.g. returning different + cursors depending on the mouse position. + + @see MouseCursor + */ + virtual MouseCursor getMouseCursor(); + + /** Forces the current mouse cursor to be updated. + + If you're overriding the getMouseCursor() method to control which cursor is + displayed, then this will only be checked each time the user moves the mouse. So + if you want to force the system to check that the cursor being displayed is + up-to-date (even if the mouse is just sitting there), call this method. + + (If you're changing the cursor using setMouseCursor(), you don't need to bother + calling this). + */ + void updateMouseCursor() const; + + //============================================================================== + /** Components can override this method to draw their content. + + The paint() method gets called when a region of a component needs redrawing, + either because the component's repaint() method has been called, or because + something has happened on the screen that means a section of a window needs + to be redrawn. + + Any child components will draw themselves over whatever this method draws. If + you need to paint over the top of your child components, you can also implement + the paintOverChildren() method to do this. + + If you want to cause a component to redraw itself, this is done asynchronously - + calling the repaint() method marks a region of the component as "dirty", and the + paint() method will automatically be called sometime later, by the message thread, + to paint any bits that need refreshing. In Juce (and almost all modern UI frameworks), + you never redraw something synchronously. + + You should never need to call this method directly - to take a snapshot of the + component you could use createComponentSnapshot() or paintEntireComponent(). + + @param g the graphics context that must be used to do the drawing operations. + @see repaint, paintOverChildren, Graphics + */ + virtual void paint (Graphics& g); + + /** Components can override this method to draw over the top of their children. + + For most drawing operations, it's better to use the normal paint() method, + but if you need to overlay something on top of the children, this can be + used. + + @see paint, Graphics + */ + virtual void paintOverChildren (Graphics& g); + + + //============================================================================== + /** Called when the mouse moves inside a component. + + If the mouse button isn't pressed and the mouse moves over a component, + this will be called to let the component react to this. + + A component will always get a mouseEnter callback before a mouseMove. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseEnter, mouseExit, mouseDrag, contains + */ + virtual void mouseMove (const MouseEvent& event) override; + + /** Called when the mouse first enters a component. + + If the mouse button isn't pressed and the mouse moves into a component, + this will be called to let the component react to this. + + When the mouse button is pressed and held down while being moved in + or out of a component, no mouseEnter or mouseExit callbacks are made - only + mouseDrag messages are sent to the component that the mouse was originally + clicked on, until the button is released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseExit, mouseDrag, mouseMove, contains + */ + virtual void mouseEnter (const MouseEvent& event) override; + + /** Called when the mouse moves out of a component. + + This will be called when the mouse moves off the edge of this + component. + + If the mouse button was pressed, and it was then dragged off the + edge of the component and released, then this callback will happen + when the button is released, after the mouseUp callback. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseEnter, mouseDrag, mouseMove, contains + */ + virtual void mouseExit (const MouseEvent& event) override; + + /** Called when a mouse button is pressed. + + The MouseEvent object passed in contains lots of methods for finding out + which button was pressed, as well as which modifier keys (e.g. shift, ctrl) + were held down at the time. + + Once a button is held down, the mouseDrag method will be called when the + mouse moves, until the button is released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseUp, mouseDrag, mouseDoubleClick, contains + */ + virtual void mouseDown (const MouseEvent& event) override; + + /** Called when the mouse is moved while a button is held down. + + When a mouse button is pressed inside a component, that component + receives mouseDrag callbacks each time the mouse moves, even if the + mouse strays outside the component's bounds. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseUp, mouseMove, contains, setDragRepeatInterval + */ + virtual void mouseDrag (const MouseEvent& event) override; + + /** Called when a mouse button is released. + + A mouseUp callback is sent to the component in which a button was pressed + even if the mouse is actually over a different component when the + button is released. + + The MouseEvent object passed in contains lots of methods for finding out + which buttons were down just before they were released. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseDrag, mouseDoubleClick, contains + */ + virtual void mouseUp (const MouseEvent& event) override; + + /** Called when a mouse button has been double-clicked on a component. + + The MouseEvent object passed in contains lots of methods for finding out + which button was pressed, as well as which modifier keys (e.g. shift, ctrl) + were held down at the time. + + @param event details about the position and status of the mouse event, including + the source component in which it occurred + @see mouseDown, mouseUp + */ + virtual void mouseDoubleClick (const MouseEvent& event) override; + + /** Called when the mouse-wheel is moved. + + This callback is sent to the component that the mouse is over when the + wheel is moved. + + If not overridden, a component will forward this message to its parent, so + that parent components can collect mouse-wheel messages that happen to + child components which aren't interested in them. (Bear in mind that if + you attach a component as a mouse-listener to other components, then + those wheel moves will also end up calling this method and being passed up + to the parents, which may not be what you intended to happen). + + @param event details about the mouse event + @param wheel details about the mouse wheel movement + */ + virtual void mouseWheelMove (const MouseEvent& event, + const MouseWheelDetails& wheel) override; + + /** Called when a pinch-to-zoom mouse-gesture is used. + + If not overridden, a component will forward this message to its parent, so + that parent components can collect gesture messages that are unused by child + components. + + @param event details about the mouse event + @param scaleFactor a multiplier to indicate by how much the size of the target + should be changed. A value of 1.0 would indicate no change, + values greater than 1.0 mean it should be enlarged. + */ + virtual void mouseMagnify (const MouseEvent& event, float scaleFactor); + + //============================================================================== + /** Ensures that a non-stop stream of mouse-drag events will be sent during the + current mouse-drag operation. + + This allows you to make sure that mouseDrag() events are sent continuously, even + when the mouse isn't moving. This can be useful for things like auto-scrolling + components when the mouse is near an edge. + + Call this method during a mouseDown() or mouseDrag() callback, specifying the + minimum interval between consecutive mouse drag callbacks. The callbacks + will continue until the mouse is released, and then the interval will be reset, + so you need to make sure it's called every time you begin a drag event. + Passing an interval of 0 or less will cancel the auto-repeat. + + @see mouseDrag, Desktop::beginDragAutoRepeat + */ + static void JUCE_CALLTYPE beginDragAutoRepeat (int millisecondsBetweenCallbacks); + + /** Causes automatic repaints when the mouse enters or exits this component. + + If turned on, then when the mouse enters/exits, or when the button is pressed/released + on the component, it will trigger a repaint. + + This is handy for things like buttons that need to draw themselves differently when + the mouse moves over them, and it avoids having to override all the different mouse + callbacks and call repaint(). + + @see mouseEnter, mouseExit, mouseDown, mouseUp + */ + void setRepaintsOnMouseActivity (bool shouldRepaint) noexcept; + + /** Registers a listener to be told when mouse events occur in this component. + + If you need to get informed about mouse events in a component but can't or + don't want to override its methods, you can attach any number of listeners + to the component, and these will get told about the events in addition to + the component's own callbacks being called. + + Note that a MouseListener can also be attached to more than one component. + + @param newListener the listener to register + @param wantsEventsForAllNestedChildComponents if true, the listener will receive callbacks + for events that happen to any child component + within this component, including deeply-nested + child components. If false, it will only be + told about events that this component handles. + @see MouseListener, removeMouseListener + */ + void addMouseListener (MouseListener* newListener, + bool wantsEventsForAllNestedChildComponents); + + /** Deregisters a mouse listener. + @see addMouseListener, MouseListener + */ + void removeMouseListener (MouseListener* listenerToRemove); + + //============================================================================== + /** Adds a listener that wants to hear about keypresses that this component receives. + + The listeners that are registered with a component are called by its keyPressed() or + keyStateChanged() methods (assuming these haven't been overridden to do something else). + + If you add an object as a key listener, be careful to remove it when the object + is deleted, or the component will be left with a dangling pointer. + + @see keyPressed, keyStateChanged, removeKeyListener + */ + void addKeyListener (KeyListener* newListener); + + /** Removes a previously-registered key listener. + @see addKeyListener + */ + void removeKeyListener (KeyListener* listenerToRemove); + + /** Called when a key is pressed. + + When a key is pressed, the component that has the keyboard focus will have this + method called. Remember that a component will only be given the focus if its + setWantsKeyboardFocus() method has been used to enable this. + + If your implementation returns true, the event will be consumed and not passed + on to any other listeners. If it returns false, the key will be passed to any + KeyListeners that have been registered with this component. As soon as one of these + returns true, the process will stop, but if they all return false, the event will + be passed upwards to this component's parent, and so on. + + The default implementation of this method does nothing and returns false. + + @see keyStateChanged, getCurrentlyFocusedComponent, addKeyListener + */ + virtual bool keyPressed (const KeyPress& key); + + /** Called when a key is pressed or released. + + Whenever a key on the keyboard is pressed or released (including modifier keys + like shift and ctrl), this method will be called on the component that currently + has the keyboard focus. Remember that a component will only be given the focus if + its setWantsKeyboardFocus() method has been used to enable this. + + If your implementation returns true, the event will be consumed and not passed + on to any other listeners. If it returns false, then any KeyListeners that have + been registered with this component will have their keyStateChanged methods called. + As soon as one of these returns true, the process will stop, but if they all return + false, the event will be passed upwards to this component's parent, and so on. + + The default implementation of this method does nothing and returns false. + + To find out which keys are up or down at any time, see the KeyPress::isKeyCurrentlyDown() + method. + + @param isKeyDown true if a key has been pressed; false if it has been released + + @see keyPressed, KeyPress, getCurrentlyFocusedComponent, addKeyListener + */ + virtual bool keyStateChanged (bool isKeyDown); + + /** Called when a modifier key is pressed or released. + + Whenever the shift, control, alt or command keys are pressed or released, + this method will be called on the component that currently has the keyboard focus. + Remember that a component will only be given the focus if its setWantsKeyboardFocus() + method has been used to enable this. + + The default implementation of this method actually calls its parent's modifierKeysChanged + method, so that focused components which aren't interested in this will give their + parents a chance to act on the event instead. + + @see keyStateChanged, ModifierKeys + */ + virtual void modifierKeysChanged (const ModifierKeys& modifiers); + + //============================================================================== + /** Enumeration used by the focusChanged() and focusLost() methods. */ + enum FocusChangeType + { + focusChangedByMouseClick, /**< Means that the user clicked the mouse to change focus. */ + focusChangedByTabKey, /**< Means that the user pressed the tab key to move the focus. */ + focusChangedDirectly /**< Means that the focus was changed by a call to grabKeyboardFocus(). */ + }; + + /** Called to indicate that this component has just acquired the keyboard focus. + @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusGained (FocusChangeType cause); + + /** Called to indicate that this component has just lost the keyboard focus. + @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusLost (FocusChangeType cause); + + /** Called to indicate a change in whether or not this component is the parent of the + currently-focused component. + + Essentially this is called when the return value of a call to hasKeyboardFocus (true) has + changed. It happens when focus moves from one of this component's children (at any depth) + to a component that isn't contained in this one, (or vice-versa). + Note that this method does NOT get called to when focus simply moves from one of its + child components to another. + + @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusOfChildComponentChanged (FocusChangeType cause); + + //============================================================================== + /** Returns true if the mouse is currently over this component. + + If the mouse isn't over the component, this will return false, even if the + mouse is currently being dragged - so you can use this in your mouseDrag + method to find out whether it's really over the component or not. + + Note that when the mouse button is being held down, then the only component + for which this method will return true is the one that was originally + clicked on. + + If includeChildren is true, then this will also return true if the mouse is over + any of the component's children (recursively) as well as the component itself. + + @see isMouseButtonDown. isMouseOverOrDragging, mouseDrag + */ + bool isMouseOver (bool includeChildren = false) const; + + /** Returns true if the mouse button is currently held down in this component. + + Note that this is a test to see whether the mouse is being pressed in this + component, so it'll return false if called on component A when the mouse + is actually being dragged in component B. + + @see isMouseButtonDownAnywhere, isMouseOver, isMouseOverOrDragging + */ + bool isMouseButtonDown() const; + + /** True if the mouse is over this component, or if it's being dragged in this component. + This is a handy equivalent to (isMouseOver() || isMouseButtonDown()). + @see isMouseOver, isMouseButtonDown, isMouseButtonDownAnywhere + */ + bool isMouseOverOrDragging() const; + + /** Returns true if a mouse button is currently down. + + Unlike isMouseButtonDown, this will test the current state of the + buttons without regard to which component (if any) it has been + pressed in. + + @see isMouseButtonDown, ModifierKeys + */ + static bool JUCE_CALLTYPE isMouseButtonDownAnywhere() noexcept; + + /** Returns the mouse's current position, relative to this component. + The return value is relative to the component's top-left corner. + */ + Point getMouseXYRelative() const; + + //============================================================================== + /** Called when this component's size has been changed. + + A component can implement this method to do things such as laying out its + child components when its width or height changes. + + The method is called synchronously as a result of the setBounds or setSize + methods, so repeatedly changing a components size will repeatedly call its + resized method (unlike things like repainting, where multiple calls to repaint + are coalesced together). + + If the component is a top-level window on the desktop, its size could also + be changed by operating-system factors beyond the application's control. + + @see moved, setSize + */ + virtual void resized(); + + /** Called when this component's position has been changed. + + This is called when the position relative to its parent changes, not when + its absolute position on the screen changes (so it won't be called for + all child components when a parent component is moved). + + The method is called synchronously as a result of the setBounds, setTopLeftPosition + or any of the other repositioning methods, and like resized(), it will be + called each time those methods are called. + + If the component is a top-level window on the desktop, its position could also + be changed by operating-system factors beyond the application's control. + + @see resized, setBounds + */ + virtual void moved(); + + /** Called when one of this component's children is moved or resized. + + If the parent wants to know about changes to its immediate children (not + to children of its children), this is the method to override. + + @see moved, resized, parentSizeChanged + */ + virtual void childBoundsChanged (Component* child); + + /** Called when this component's immediate parent has been resized. + + If the component is a top-level window, this indicates that the screen size + has changed. + + @see childBoundsChanged, moved, resized + */ + virtual void parentSizeChanged(); + + /** Called when this component has been moved to the front of its siblings. + + The component may have been brought to the front by the toFront() method, or + by the operating system if it's a top-level window. + + @see toFront + */ + virtual void broughtToFront(); + + /** Adds a listener to be told about changes to the component hierarchy or position. + + Component listeners get called when this component's size, position or children + change - see the ComponentListener class for more details. + + @param newListener the listener to register - if this is already registered, it + will be ignored. + @see ComponentListener, removeComponentListener + */ + void addComponentListener (ComponentListener* newListener); + + /** Removes a component listener. + @see addComponentListener + */ + void removeComponentListener (ComponentListener* listenerToRemove); + + //============================================================================== + /** Dispatches a numbered message to this component. + + This is a quick and cheap way of allowing simple asynchronous messages to + be sent to components. It's also safe, because if the component that you + send the message to is a null or dangling pointer, this won't cause an error. + + The command ID is later delivered to the component's handleCommandMessage() method by + the application's message queue. + + @see handleCommandMessage + */ + void postCommandMessage (int commandId); + + /** Called to handle a command that was sent by postCommandMessage(). + + This is called by the message thread when a command message arrives, and + the component can override this method to process it in any way it needs to. + + @see postCommandMessage + */ + virtual void handleCommandMessage (int commandId); + + //============================================================================== + /** Runs a component modally, waiting until the loop terminates. + + This method first makes the component visible, brings it to the front and + gives it the keyboard focus. + + It then runs a loop, dispatching messages from the system message queue, but + blocking all mouse or keyboard messages from reaching any components other + than this one and its children. + + This loop continues until the component's exitModalState() method is called (or + the component is deleted), and then this method returns, returning the value + passed into exitModalState(). + + @see enterModalState, exitModalState, isCurrentlyModal, getCurrentlyModalComponent, + isCurrentlyBlockedByAnotherModalComponent, ModalComponentManager + */ + #if JUCE_MODAL_LOOPS_PERMITTED + int runModalLoop(); + #endif + + /** Puts the component into a modal state. + + This makes the component modal, so that messages are blocked from reaching + any components other than this one and its children, but unlike runModalLoop(), + this method returns immediately. + + If takeKeyboardFocus is true, the component will use grabKeyboardFocus() to + get the focus, which is usually what you'll want it to do. If not, it will leave + the focus unchanged. + + The callback is an optional object which will receive a callback when the modal + component loses its modal status, either by being hidden or when exitModalState() + is called. If you pass an object in here, the system will take care of deleting it + later, after making the callback + + If deleteWhenDismissed is true, then when it is dismissed, the component will be + deleted and then the callback will be called. (This will safely handle the situation + where the component is deleted before its exitModalState() method is called). + + @see exitModalState, runModalLoop, ModalComponentManager::attachCallback + */ + void enterModalState (bool takeKeyboardFocus = true, + ModalComponentManager::Callback* callback = nullptr, + bool deleteWhenDismissed = false); + + /** Ends a component's modal state. + + If this component is currently modal, this will turn off its modalness, and return + a value to the runModalLoop() method that might have be running its modal loop. + + @see runModalLoop, enterModalState, isCurrentlyModal + */ + void exitModalState (int returnValue); + + /** Returns true if this component is the modal one. + + It's possible to have nested modal components, e.g. a pop-up dialog box + that launches another pop-up, but this will only return true for + the one at the top of the stack. + + @see getCurrentlyModalComponent + */ + bool isCurrentlyModal() const noexcept; + + /** Returns the number of components that are currently in a modal state. + @see getCurrentlyModalComponent + */ + static int JUCE_CALLTYPE getNumCurrentlyModalComponents() noexcept; + + /** Returns one of the components that are currently modal. + + The index specifies which of the possible modal components to return. The order + of the components in this list is the reverse of the order in which they became + modal - so the component at index 0 is always the active component, and the others + are progressively earlier ones that are themselves now blocked by later ones. + + @returns the modal component, or null if no components are modal (or if the + index is out of range) + @see getNumCurrentlyModalComponents, runModalLoop, isCurrentlyModal + */ + static Component* JUCE_CALLTYPE getCurrentlyModalComponent (int index = 0) noexcept; + + /** Checks whether there's a modal component somewhere that's stopping this one + from receiving messages. + + If there is a modal component, its canModalEventBeSentToComponent() method + will be called to see if it will still allow this component to receive events. + + @see runModalLoop, getCurrentlyModalComponent + */ + bool isCurrentlyBlockedByAnotherModalComponent() const; + + /** When a component is modal, this callback allows it to choose which other + components can still receive events. + + When a modal component is active and the user clicks on a non-modal component, + this method is called on the modal component, and if it returns true, the + event is allowed to reach its target. If it returns false, the event is blocked + and the inputAttemptWhenModal() callback is made. + + It called by the isCurrentlyBlockedByAnotherModalComponent() method. The default + implementation just returns false in all cases. + */ + virtual bool canModalEventBeSentToComponent (const Component* targetComponent); + + /** Called when the user tries to click on a component that is blocked by another + modal component. + + When a component is modal and the user clicks on one of the other components, + the modal component will receive this callback. + + The default implementation of this method will play a beep, and bring the currently + modal component to the front, but it can be overridden to do other tasks. + + @see isCurrentlyBlockedByAnotherModalComponent, canModalEventBeSentToComponent + */ + virtual void inputAttemptWhenModal(); + + + //============================================================================== + /** Returns the set of properties that belong to this component. + Each component has a NamedValueSet object which you can use to attach arbitrary + items of data to it. + */ + NamedValueSet& getProperties() noexcept { return properties; } + + /** Returns the set of properties that belong to this component. + Each component has a NamedValueSet object which you can use to attach arbitrary + items of data to it. + */ + const NamedValueSet& getProperties() const noexcept { return properties; } + + //============================================================================== + /** Looks for a colour that has been registered with the given colour ID number. + + If a colour has been set for this ID number using setColour(), then it is + returned. If none has been set, the method will try calling the component's + LookAndFeel class's findColour() method. If none has been registered with the + look-and-feel either, it will just return black. + + The colour IDs for various purposes are stored as enums in the components that + they are relevent to - for an example, see Slider::ColourIds, + Label::ColourIds, TextEditor::ColourIds, TreeView::ColourIds, etc. + + @see setColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour + */ + Colour findColour (int colourId, bool inheritFromParent = false) const; + + /** Registers a colour to be used for a particular purpose. + + Changing a colour will cause a synchronous callback to the colourChanged() + method, which your component can override if it needs to do something when + colours are altered. + + For more details about colour IDs, see the comments for findColour(). + + @see findColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour + */ + void setColour (int colourId, Colour newColour); + + /** If a colour has been set with setColour(), this will remove it. + This allows you to make a colour revert to its default state. + */ + void removeColour (int colourId); + + /** Returns true if the specified colour ID has been explicitly set for this + component using the setColour() method. + */ + bool isColourSpecified (int colourId) const; + + /** This looks for any colours that have been specified for this component, + and copies them to the specified target component. + */ + void copyAllExplicitColoursTo (Component& target) const; + + /** This method is called when a colour is changed by the setColour() method. + @see setColour, findColour + */ + virtual void colourChanged(); + + //============================================================================== + /** Components can implement this method to provide a MarkerList. + The default implementation of this method returns nullptr, but you can override + it to return a pointer to the component's marker list. If xAxis is true, it should + return the X marker list; if false, it should return the Y markers. + */ + virtual MarkerList* getMarkers (bool xAxis); + + //============================================================================== + /** Returns the underlying native window handle for this component. + + This is platform-dependent and strictly for power-users only! + */ + void* getWindowHandle() const; + + //============================================================================== + /** Holds a pointer to some type of Component, which automatically becomes null if + the component is deleted. + + If you're using a component which may be deleted by another event that's outside + of your control, use a SafePointer instead of a normal pointer to refer to it, + and you can test whether it's null before using it to see if something has deleted + it. + + The ComponentType typedef must be Component, or some subclass of Component. + + You may also want to use a WeakReference object for the same purpose. + */ + template + class SafePointer + { + public: + /** Creates a null SafePointer. */ + SafePointer() noexcept {} + + /** Creates a SafePointer that points at the given component. */ + SafePointer (ComponentType* component) : weakRef (component) {} + + /** Creates a copy of another SafePointer. */ + SafePointer (const SafePointer& other) noexcept : weakRef (other.weakRef) {} + + /** Copies another pointer to this one. */ + SafePointer& operator= (const SafePointer& other) { weakRef = other.weakRef; return *this; } + + /** Copies another pointer to this one. */ + SafePointer& operator= (ComponentType* newComponent) { weakRef = newComponent; return *this; } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + ComponentType* getComponent() const noexcept { return dynamic_cast (weakRef.get()); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + operator ComponentType*() const noexcept { return getComponent(); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + ComponentType* operator->() noexcept { return getComponent(); } + + /** Returns the component that this pointer refers to, or null if the component no longer exists. */ + const ComponentType* operator->() const noexcept { return getComponent(); } + + /** If the component is valid, this deletes it and sets this pointer to null. */ + void deleteAndZero() { delete getComponent(); } + + bool operator== (ComponentType* component) const noexcept { return weakRef == component; } + bool operator!= (ComponentType* component) const noexcept { return weakRef != component; } + + private: + WeakReference weakRef; + }; + + //============================================================================== + /** A class to keep an eye on a component and check for it being deleted. + + This is designed for use with the ListenerList::callChecked() methods, to allow + the list iterator to stop cleanly if the component is deleted by a listener callback + while the list is still being iterated. + */ + class JUCE_API BailOutChecker + { + public: + /** Creates a checker that watches one component. */ + BailOutChecker (Component* component); + + /** Returns true if either of the two components have been deleted since this object was created. */ + bool shouldBailOut() const noexcept; + + private: + const WeakReference safePointer; + + JUCE_DECLARE_NON_COPYABLE (BailOutChecker) + }; + + //============================================================================== + /** + Base class for objects that can be used to automatically position a component according to + some kind of algorithm. + + The component class simply holds onto a reference to a Positioner, but doesn't actually do + anything with it - all the functionality must be implemented by the positioner itself (e.g. + it might choose to watch some kind of value and move the component when the value changes). + */ + class JUCE_API Positioner + { + public: + /** Creates a Positioner which can control the specified component. */ + explicit Positioner (Component& component) noexcept; + /** Destructor. */ + virtual ~Positioner() {} + + /** Returns the component that this positioner controls. */ + Component& getComponent() const noexcept { return component; } + + /** Attempts to set the component's position to the given rectangle. + Unlike simply calling Component::setBounds(), this may involve the positioner + being smart enough to adjust itself to fit the new bounds, e.g. a RelativeRectangle's + positioner may try to reverse the expressions used to make them fit these new coordinates. + */ + virtual void applyNewBounds (const Rectangle& newBounds) = 0; + + private: + Component& component; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner) + }; + + /** Returns the Positioner object that has been set for this component. + @see setPositioner() + */ + Positioner* getPositioner() const noexcept; + + /** Sets a new Positioner object for this component. + If there's currently another positioner set, it will be deleted. The object that is passed in + will be deleted automatically by this component when it's no longer required. Pass a null pointer + to clear the current positioner. + @see getPositioner() + */ + void setPositioner (Positioner* newPositioner); + + /** Gives the component a CachedComponentImage that should be used to buffer its painting. + The object that is passed-in will be owned by this component, and will be deleted automatically + later on. + @see setBufferedToImage + */ + void setCachedComponentImage (CachedComponentImage* newCachedImage); + + /** Returns the object that was set by setCachedComponentImage(). + @see setCachedComponentImage + */ + CachedComponentImage* getCachedComponentImage() const noexcept { return cachedImage; } + + //============================================================================== + // These methods are deprecated - use localPointToGlobal, getLocalPoint, getLocalPoint, etc instead. + JUCE_DEPRECATED (Point relativePositionToGlobal (Point) const); + JUCE_DEPRECATED (Point globalPositionToRelative (Point) const); + JUCE_DEPRECATED (Point relativePositionToOtherComponent (const Component*, Point) const); + +private: + //============================================================================== + friend class ComponentPeer; + friend class MouseInputSource; + friend class MouseInputSourceInternal; + + #ifndef DOXYGEN + static Component* currentlyFocusedComponent; + + //============================================================================== + String componentName, componentID; + Component* parentComponent; + Rectangle bounds; + ScopedPointer positioner; + ScopedPointer affineTransform; + Array childComponentList; + LookAndFeel* lookAndFeel; + MouseCursor cursor; + ImageEffectFilter* effect; + ScopedPointer cachedImage; + + class MouseListenerList; + friend class MouseListenerList; + friend struct ContainerDeletePolicy; + ScopedPointer mouseListeners; + ScopedPointer > keyListeners; + ListenerList componentListeners; + NamedValueSet properties; + + friend class WeakReference; + WeakReference::Master masterReference; + + struct ComponentFlags + { + bool hasHeavyweightPeerFlag : 1; + bool visibleFlag : 1; + bool opaqueFlag : 1; + bool ignoresMouseClicksFlag : 1; + bool allowChildMouseClicksFlag : 1; + bool wantsFocusFlag : 1; + bool isFocusContainerFlag : 1; + bool dontFocusOnMouseClickFlag : 1; + bool alwaysOnTopFlag : 1; + bool bufferToImageFlag : 1; + bool bringToFrontOnClickFlag : 1; + bool repaintOnMouseActivityFlag : 1; + bool currentlyModalFlag : 1; + bool isDisabledFlag : 1; + bool childCompFocusedFlag : 1; + bool dontClipGraphicsFlag : 1; + bool mouseDownWasBlocked : 1; + bool isMoveCallbackPending : 1; + bool isResizeCallbackPending : 1; + #if JUCE_DEBUG + bool isInsidePaintCall : 1; + #endif + }; + + union + { + uint32 componentFlags; + ComponentFlags flags; + }; + + uint8 componentTransparency; + + //============================================================================== + void internalMouseEnter (MouseInputSource, Point, Time); + void internalMouseExit (MouseInputSource, Point, Time); + void internalMouseDown (MouseInputSource, Point, Time); + void internalMouseUp (MouseInputSource, Point, Time, const ModifierKeys oldModifiers); + void internalMouseDrag (MouseInputSource, Point, Time); + void internalMouseMove (MouseInputSource, Point, Time); + void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); + void internalMagnifyGesture (MouseInputSource, Point, Time, float); + void internalBroughtToFront(); + void internalFocusGain (FocusChangeType, const WeakReference&); + void internalFocusGain (FocusChangeType); + void internalFocusLoss (FocusChangeType); + void internalChildFocusChange (FocusChangeType, const WeakReference&); + void internalModalInputAttempt(); + void internalModifierKeysChanged(); + void internalChildrenChanged(); + void internalHierarchyChanged(); + void internalRepaint (const Rectangle&); + void internalRepaintUnchecked (const Rectangle&, bool); + Component* removeChildComponent (int index, bool sendParentEvents, bool sendChildEvents); + void reorderChildInternal (int sourceIndex, int destIndex); + void paintComponentAndChildren (Graphics&); + void paintWithinParentContext (Graphics&); + void sendMovedResizedMessages (bool wasMoved, bool wasResized); + void sendMovedResizedMessagesIfPending(); + void repaintParent(); + void sendFakeMouseMove() const; + void takeKeyboardFocus (const FocusChangeType); + void grabFocusInternal (const FocusChangeType, bool canTryParent); + static void giveAwayFocus (bool sendFocusLossEvent); + void sendEnablementChangeMessage(); + void sendVisibilityChangeMessage(); + + struct ComponentHelpers; + friend struct ComponentHelpers; + + /* Components aren't allowed to have copy constructors, as this would mess up parent hierarchies. + You might need to give your subclasses a private dummy constructor to avoid compiler warnings. + */ + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Component) + + //============================================================================== + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // This is included here just to cause a compile error if your code is still handling + // drag-and-drop with this method. If so, just update it to use the new FileDragAndDropTarget + // class, which is easy (just make your class inherit from FileDragAndDropTarget, and + // implement its methods instead of this Component method). + virtual void filesDropped (const StringArray&, int, int) {} + + // This is included here to cause an error if you use or overload it - it has been deprecated in + // favour of contains (Point) + void contains (int, int) JUCE_DELETED_FUNCTION; + #endif + +protected: + //============================================================================== + /** @internal */ + virtual ComponentPeer* createNewPeer (int styleFlags, void* nativeWindowToAttachTo); + #endif +}; + + +#endif // JUCE_COMPONENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.cpp new file mode 100644 index 0000000..3e5cd35 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.cpp @@ -0,0 +1,31 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +void ComponentListener::componentMovedOrResized (Component&, bool, bool) {} +void ComponentListener::componentBroughtToFront (Component&) {} +void ComponentListener::componentVisibilityChanged (Component&) {} +void ComponentListener::componentChildrenChanged (Component&) {} +void ComponentListener::componentParentHierarchyChanged (Component&) {} +void ComponentListener::componentNameChanged (Component&) {} +void ComponentListener::componentBeingDeleted (Component&) {} diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.h new file mode 100644 index 0000000..56c0631 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ComponentListener.h @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_COMPONENTLISTENER_H_INCLUDED +#define JUCE_COMPONENTLISTENER_H_INCLUDED + + +//============================================================================== +/** + Gets informed about changes to a component's hierarchy or position. + + To monitor a component for changes, register a subclass of ComponentListener + with the component using Component::addComponentListener(). + + Be sure to deregister listeners before you delete them! + + @see Component::addComponentListener, Component::removeComponentListener +*/ +class JUCE_API ComponentListener +{ +public: + /** Destructor. */ + virtual ~ComponentListener() {} + + /** Called when the component's position or size changes. + + @param component the component that was moved or resized + @param wasMoved true if the component's top-left corner has just moved + @param wasResized true if the component's width or height has just changed + @see Component::setBounds, Component::resized, Component::moved + */ + virtual void componentMovedOrResized (Component& component, + bool wasMoved, + bool wasResized); + + /** Called when the component is brought to the top of the z-order. + + @param component the component that was moved + @see Component::toFront, Component::broughtToFront + */ + virtual void componentBroughtToFront (Component& component); + + /** Called when the component is made visible or invisible. + + @param component the component that changed + @see Component::setVisible + */ + virtual void componentVisibilityChanged (Component& component); + + /** Called when the component has children added or removed, or their z-order + changes. + + @param component the component whose children have changed + @see Component::childrenChanged, Component::addChildComponent, + Component::removeChildComponent + */ + virtual void componentChildrenChanged (Component& component); + + /** Called to indicate that the component's parents have changed. + + When a component is added or removed from its parent, all of its children + will produce this notification (recursively - so all children of its + children will also be called as well). + + @param component the component that this listener is registered with + @see Component::parentHierarchyChanged + */ + virtual void componentParentHierarchyChanged (Component& component); + + /** Called when the component's name is changed. + + @see Component::setName, Component::getName + */ + virtual void componentNameChanged (Component& component); + + /** Called when the component is in the process of being deleted. + + This callback is made from inside the destructor, so be very, very cautious + about what you do in here. + + In particular, bear in mind that it's the Component base class's destructor that calls + this - so if the object that's being deleted is a subclass of Component, then the + subclass layers of the object will already have been destructed when it gets to this + point! + */ + virtual void componentBeingDeleted (Component& component); +}; + + +#endif // JUCE_COMPONENTLISTENER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.cpp new file mode 100644 index 0000000..bb77371 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.cpp @@ -0,0 +1,415 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +Desktop::Desktop() + : mouseSources (new MouseInputSource::SourceList()), + mouseClickCounter (0), mouseWheelCounter (0), + kioskModeComponent (nullptr), + kioskModeReentrant (false), + allowedOrientations (allOrientations), + masterScaleFactor ((float) getDefaultMasterScale()) +{ + displays = new Displays (*this); +} + +Desktop::~Desktop() +{ + setScreenSaverEnabled (true); + + jassert (instance == this); + instance = nullptr; + + // doh! If you don't delete all your windows before exiting, you're going to + // be leaking memory! + jassert (desktopComponents.size() == 0); +} + +Desktop& JUCE_CALLTYPE Desktop::getInstance() +{ + if (instance == nullptr) + instance = new Desktop(); + + return *instance; +} + +Desktop* Desktop::instance = nullptr; + +//============================================================================== +int Desktop::getNumComponents() const noexcept +{ + return desktopComponents.size(); +} + +Component* Desktop::getComponent (const int index) const noexcept +{ + return desktopComponents [index]; +} + +Component* Desktop::findComponentAt (Point screenPosition) const +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + for (int i = desktopComponents.size(); --i >= 0;) + { + Component* const c = desktopComponents.getUnchecked(i); + + if (c->isVisible()) + { + const Point relative (c->getLocalPoint (nullptr, screenPosition)); + + if (c->contains (relative)) + return c->getComponentAt (relative); + } + } + + return nullptr; +} + +//============================================================================== +LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept +{ + if (currentLookAndFeel == nullptr) + { + if (defaultLookAndFeel == nullptr) + defaultLookAndFeel = new LookAndFeel_V2(); + + currentLookAndFeel = defaultLookAndFeel; + } + + return *currentLookAndFeel; +} + +void Desktop::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel) +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + currentLookAndFeel = newDefaultLookAndFeel; + + for (int i = getNumComponents(); --i >= 0;) + if (Component* const c = getComponent (i)) + c->sendLookAndFeelChange(); +} + +//============================================================================== +void Desktop::addDesktopComponent (Component* const c) +{ + jassert (c != nullptr); + jassert (! desktopComponents.contains (c)); + desktopComponents.addIfNotAlreadyThere (c); +} + +void Desktop::removeDesktopComponent (Component* const c) +{ + desktopComponents.removeFirstMatchingValue (c); +} + +void Desktop::componentBroughtToFront (Component* const c) +{ + const int index = desktopComponents.indexOf (c); + jassert (index >= 0); + + if (index >= 0) + { + int newIndex = -1; + + if (! c->isAlwaysOnTop()) + { + newIndex = desktopComponents.size(); + + while (newIndex > 0 && desktopComponents.getUnchecked (newIndex - 1)->isAlwaysOnTop()) + --newIndex; + + --newIndex; + } + + desktopComponents.move (index, newIndex); + } +} + +//============================================================================== +Point Desktop::getMousePosition() +{ + return getMousePositionFloat().roundToInt(); +} + +Point Desktop::getMousePositionFloat() +{ + return getInstance().getMainMouseSource().getScreenPosition(); +} + +void Desktop::setMousePosition (Point newPosition) +{ + getInstance().getMainMouseSource().setScreenPosition (newPosition.toFloat()); +} + +Point Desktop::getLastMouseDownPosition() +{ + return getInstance().getMainMouseSource().getLastMouseDownPosition().roundToInt(); +} + +int Desktop::getMouseButtonClickCounter() const noexcept { return mouseClickCounter; } +int Desktop::getMouseWheelMoveCounter() const noexcept { return mouseWheelCounter; } + +void Desktop::incrementMouseClickCounter() noexcept { ++mouseClickCounter; } +void Desktop::incrementMouseWheelCounter() noexcept { ++mouseWheelCounter; } + +const Array& Desktop::getMouseSources() const noexcept { return mouseSources->sourceArray; } +int Desktop::getNumMouseSources() const noexcept { return mouseSources->sources.size(); } +int Desktop::getNumDraggingMouseSources() const noexcept { return mouseSources->getNumDraggingMouseSources(); } +MouseInputSource* Desktop::getMouseSource (int index) const noexcept { return mouseSources->getMouseSource (index); } +MouseInputSource* Desktop::getDraggingMouseSource (int index) const noexcept { return mouseSources->getDraggingMouseSource (index); } +MouseInputSource Desktop::getMainMouseSource() const noexcept { return MouseInputSource (mouseSources->sources.getUnchecked(0)); } +void Desktop::beginDragAutoRepeat (int interval) { mouseSources->beginDragAutoRepeat (interval); } + +//============================================================================== +void Desktop::addFocusChangeListener (FocusChangeListener* const listener) { focusListeners.add (listener); } +void Desktop::removeFocusChangeListener (FocusChangeListener* const listener) { focusListeners.remove (listener); } +void Desktop::triggerFocusCallback() { triggerAsyncUpdate(); } + +void Desktop::handleAsyncUpdate() +{ + // The component may be deleted during this operation, but we'll use a SafePointer rather than a + // BailOutChecker so that any remaining listeners will still get a callback (with a null pointer). + WeakReference currentFocus (Component::getCurrentlyFocusedComponent()); + focusListeners.call (&FocusChangeListener::globalFocusChanged, currentFocus); +} + +//============================================================================== +void Desktop::resetTimer() +{ + if (mouseListeners.size() == 0) + stopTimer(); + else + startTimer (100); + + lastFakeMouseMove = getMousePositionFloat(); +} + +ListenerList& Desktop::getMouseListeners() +{ + resetTimer(); + return mouseListeners; +} + +void Desktop::addGlobalMouseListener (MouseListener* const listener) +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + mouseListeners.add (listener); + resetTimer(); +} + +void Desktop::removeGlobalMouseListener (MouseListener* const listener) +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + mouseListeners.remove (listener); + resetTimer(); +} + +void Desktop::timerCallback() +{ + if (lastFakeMouseMove != getMousePositionFloat()) + sendMouseMove(); +} + +void Desktop::sendMouseMove() +{ + if (! mouseListeners.isEmpty()) + { + startTimer (20); + + lastFakeMouseMove = getMousePositionFloat(); + + if (Component* const target = findComponentAt (lastFakeMouseMove.roundToInt())) + { + Component::BailOutChecker checker (target); + const Point pos (target->getLocalPoint (nullptr, lastFakeMouseMove)); + const Time now (Time::getCurrentTime()); + + const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), + target, target, now, pos, now, 0, false); + + if (me.mods.isAnyMouseButtonDown()) + mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); + else + mouseListeners.callChecked (checker, &MouseListener::mouseMove, me); + } + } +} + + +//============================================================================== +Desktop::Displays::Displays (Desktop& desktop) { init (desktop); } +Desktop::Displays::~Displays() {} + +const Desktop::Displays::Display& Desktop::Displays::getMainDisplay() const noexcept +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + jassert (displays.getReference(0).isMain); + return displays.getReference(0); +} + +const Desktop::Displays::Display& Desktop::Displays::getDisplayContaining (Point position) const noexcept +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + const Display* best = &displays.getReference(0); + double bestDistance = 1.0e10; + + for (int i = displays.size(); --i >= 0;) + { + const Display& d = displays.getReference(i); + + if (d.totalArea.contains (position)) + { + best = &d; + break; + } + + const double distance = d.totalArea.getCentre().getDistanceFrom (position); + + if (distance < bestDistance) + { + bestDistance = distance; + best = &d; + } + } + + return *best; +} + +RectangleList Desktop::Displays::getRectangleList (bool userAreasOnly) const +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + RectangleList rl; + + for (int i = 0; i < displays.size(); ++i) + { + const Display& d = displays.getReference(i); + rl.addWithoutMerging (userAreasOnly ? d.userArea : d.totalArea); + } + + return rl; +} + +Rectangle Desktop::Displays::getTotalBounds (bool userAreasOnly) const +{ + return getRectangleList (userAreasOnly).getBounds(); +} + +bool operator== (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept; +bool operator== (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept +{ + return d1.userArea == d2.userArea + && d1.totalArea == d2.totalArea + && d1.scale == d2.scale + && d1.isMain == d2.isMain; +} + +bool operator!= (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept; +bool operator!= (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept +{ + return ! (d1 == d2); +} + +void Desktop::Displays::init (Desktop& desktop) +{ + findDisplays (desktop.getGlobalScaleFactor()); + jassert (displays.size() > 0); +} + +void Desktop::Displays::refresh() +{ + Array oldDisplays; + oldDisplays.swapWith (displays); + + init (Desktop::getInstance()); + jassert (displays.size() > 0); + + if (oldDisplays != displays) + { + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + if (ComponentPeer* const peer = ComponentPeer::getPeer (i)) + peer->handleScreenSizeChange(); + } +} + +//============================================================================== +void Desktop::setKioskModeComponent (Component* componentToUse, const bool allowMenusAndBars) +{ + if (kioskModeReentrant) + return; + + const ScopedValueSetter setter (kioskModeReentrant, true, false); + + if (kioskModeComponent != componentToUse) + { + // agh! Don't delete or remove a component from the desktop while it's still the kiosk component! + jassert (kioskModeComponent == nullptr || ComponentPeer::getPeerFor (kioskModeComponent) != nullptr); + + if (Component* const oldKioskComp = kioskModeComponent) + { + kioskModeComponent = nullptr; // (to make sure that isKioskMode() returns false when resizing the old one) + setKioskComponent (oldKioskComp, false, allowMenusAndBars); + oldKioskComp->setBounds (kioskComponentOriginalBounds); + } + + kioskModeComponent = componentToUse; + + if (kioskModeComponent != nullptr) + { + // Only components that are already on the desktop can be put into kiosk mode! + jassert (ComponentPeer::getPeerFor (kioskModeComponent) != nullptr); + + kioskComponentOriginalBounds = kioskModeComponent->getBounds(); + setKioskComponent (kioskModeComponent, true, allowMenusAndBars); + } + } +} + +//============================================================================== +void Desktop::setOrientationsEnabled (const int newOrientations) +{ + // Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation. + jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0); + + allowedOrientations = newOrientations; +} + +bool Desktop::isOrientationEnabled (const DisplayOrientation orientation) const noexcept +{ + // Make sure you only pass one valid flag in here... + jassert (orientation == upright || orientation == upsideDown + || orientation == rotatedClockwise || orientation == rotatedAntiClockwise); + + return (allowedOrientations & orientation) != 0; +} + +void Desktop::setGlobalScaleFactor (float newScaleFactor) noexcept +{ + ASSERT_MESSAGE_MANAGER_IS_LOCKED + + if (masterScaleFactor != newScaleFactor) + { + masterScaleFactor = newScaleFactor; + displays->refresh(); + } +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.h new file mode 100644 index 0000000..4cc3317 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.h @@ -0,0 +1,460 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DESKTOP_H_INCLUDED +#define JUCE_DESKTOP_H_INCLUDED + + +//============================================================================== +/** + Classes can implement this interface and register themselves with the Desktop class + to receive callbacks when the currently focused component changes. + + @see Desktop::addFocusChangeListener, Desktop::removeFocusChangeListener +*/ +class JUCE_API FocusChangeListener +{ +public: + /** Destructor. */ + virtual ~FocusChangeListener() {} + + /** Callback to indicate that the currently focused component has changed. */ + virtual void globalFocusChanged (Component* focusedComponent) = 0; +}; + + +//============================================================================== +/** + Describes and controls aspects of the computer's desktop. + +*/ +class JUCE_API Desktop : private DeletedAtShutdown, + private Timer, + private AsyncUpdater +{ +public: + //============================================================================== + /** There's only one desktop object, and this method will return it. */ + static Desktop& JUCE_CALLTYPE getInstance(); + + //============================================================================== + /** Returns the mouse position. + + The coordinates are relative to the top-left of the main monitor. + + Note that this is just a shortcut for calling getMainMouseSource().getScreenPosition(), and + you should only resort to grabbing the global mouse position if there's really no + way to get the coordinates via a mouse event callback instead. + */ + static Point getMousePosition(); + + /** Makes the mouse pointer jump to a given location. + The coordinates are relative to the top-left of the main monitor. + */ + static void setMousePosition (Point newPosition); + + /** Returns the last position at which a mouse button was pressed. + + Note that this is just a shortcut for calling getMainMouseSource().getLastMouseDownPosition(), + and in a multi-touch environment, it doesn't make much sense. ALWAYS prefer to + get this information via other means, such as MouseEvent::getMouseDownScreenPosition() + if possible, and only ever call this as a last resort. + */ + static Point getLastMouseDownPosition(); + + /** Returns the number of times the mouse button has been clicked since the app started. + Each mouse-down event increments this number by 1. + @see getMouseWheelMoveCounter + */ + int getMouseButtonClickCounter() const noexcept; + + /** Returns the number of times the mouse wheel has been moved since the app started. + Each mouse-wheel event increments this number by 1. + @see getMouseButtonClickCounter + */ + int getMouseWheelMoveCounter() const noexcept; + + //============================================================================== + /** This lets you prevent the screensaver from becoming active. + + Handy if you're running some sort of presentation app where having a screensaver + appear would be annoying. + + Pass false to disable the screensaver, and true to re-enable it. (Note that this + won't enable a screensaver unless the user has actually set one up). + + The disablement will only happen while the Juce application is the foreground + process - if another task is running in front of it, then the screensaver will + be unaffected. + + @see isScreenSaverEnabled + */ + static void setScreenSaverEnabled (bool isEnabled); + + /** Returns true if the screensaver has not been turned off. + + This will return the last value passed into setScreenSaverEnabled(). Note that + it won't tell you whether the user is actually using a screen saver, just + whether this app is deliberately preventing one from running. + + @see setScreenSaverEnabled + */ + static bool isScreenSaverEnabled(); + + //============================================================================== + /** Registers a MouseListener that will receive all mouse events that occur on + any component. + + @see removeGlobalMouseListener + */ + void addGlobalMouseListener (MouseListener* listener); + + /** Unregisters a MouseListener that was added with the addGlobalMouseListener() + method. + + @see addGlobalMouseListener + */ + void removeGlobalMouseListener (MouseListener* listener); + + //============================================================================== + /** Registers a MouseListener that will receive a callback whenever the focused + component changes. + */ + void addFocusChangeListener (FocusChangeListener* listener); + + /** Unregisters a listener that was added with addFocusChangeListener(). */ + void removeFocusChangeListener (FocusChangeListener* listener); + + //============================================================================== + /** Takes a component and makes it full-screen, removing the taskbar, dock, etc. + + The component must already be on the desktop for this method to work. It will + be resized to completely fill the screen and any extraneous taskbars, menu bars, + etc will be hidden. + + To exit kiosk mode, just call setKioskModeComponent (nullptr). When this is called, + the component that's currently being used will be resized back to the size + and position it was in before being put into this mode. + + If allowMenusAndBars is true, things like the menu and dock (on mac) are still + allowed to pop up when the mouse moves onto them. If this is false, it'll try + to hide as much on-screen paraphenalia as possible. + */ + void setKioskModeComponent (Component* componentToUse, + bool allowMenusAndBars = true); + + /** Returns the component that is currently being used in kiosk-mode. + + This is the component that was last set by setKioskModeComponent(). If none + has been set, this returns nullptr. + */ + Component* getKioskModeComponent() const noexcept { return kioskModeComponent; } + + //============================================================================== + /** Returns the number of components that are currently active as top-level + desktop windows. + + @see getComponent, Component::addToDesktop + */ + int getNumComponents() const noexcept; + + /** Returns one of the top-level desktop window components. + + The index is from 0 to getNumComponents() - 1. This could return 0 if the + index is out-of-range. + + @see getNumComponents, Component::addToDesktop + */ + Component* getComponent (int index) const noexcept; + + /** Finds the component at a given screen location. + + This will drill down into top-level windows to find the child component at + the given position. + + Returns nullptr if the coordinates are inside a non-Juce window. + */ + Component* findComponentAt (Point screenPosition) const; + + /** The Desktop object has a ComponentAnimator instance which can be used for performing + your animations. + + Having a single shared ComponentAnimator object makes it more efficient when multiple + components are being moved around simultaneously. It's also more convenient than having + to manage your own instance of one. + + @see ComponentAnimator + */ + ComponentAnimator& getAnimator() noexcept { return animator; } + + //============================================================================== + /** Returns the current default look-and-feel for components which don't have one + explicitly set. + @see setDefaultLookAndFeel + */ + LookAndFeel& getDefaultLookAndFeel() noexcept; + + /** Changes the default look-and-feel. + @param newDefaultLookAndFeel the new look-and-feel object to use - if this is + set to nullptr, it will revert to using the system's + default one. The object passed-in must be deleted by the + caller when it's no longer needed. + @see getDefaultLookAndFeel + */ + void setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel); + + //============================================================================== + /** Provides access to the array of mouse sources, for iteration. + In a traditional single-mouse system, there might be only one MouseInputSource. On a + multi-touch system, there could be one input source per potential finger. The number + of mouse sources returned here may increase dynamically as the program runs. + To find out how many mouse events are currently happening, use getNumDraggingMouseSources(). + */ + const Array& getMouseSources() const noexcept; + + /** Returns the number of MouseInputSource objects the system has at its disposal. + In a traditional single-mouse system, there might be only one MouseInputSource. On a + multi-touch system, there could be one input source per potential finger. The number + of mouse sources returned here may increase dynamically as the program runs. + To find out how many mouse events are currently happening, use getNumDraggingMouseSources(). + @see getMouseSource + */ + int getNumMouseSources() const noexcept; + + /** Returns one of the system's MouseInputSource objects. + The index should be from 0 to getNumMouseSources() - 1. Out-of-range indexes will return + a null pointer. + In a traditional single-mouse system, there might be only one object. On a multi-touch + system, there could be one input source per potential finger. + */ + MouseInputSource* getMouseSource (int index) const noexcept; + + /** Returns the main mouse input device that the system is using. + @see getNumMouseSources() + */ + MouseInputSource getMainMouseSource() const noexcept; + + /** Returns the number of mouse-sources that are currently being dragged. + In a traditional single-mouse system, this will be 0 or 1, depending on whether a + juce component has the button down on it. In a multi-touch system, this could + be any number from 0 to the number of simultaneous touches that can be detected. + */ + int getNumDraggingMouseSources() const noexcept; + + /** Returns one of the mouse sources that's currently being dragged. + The index should be between 0 and getNumDraggingMouseSources() - 1. If the index is + out of range, or if no mice or fingers are down, this will return a null pointer. + */ + MouseInputSource* getDraggingMouseSource (int index) const noexcept; + + /** Ensures that a non-stop stream of mouse-drag events will be sent during the + current mouse-drag operation. + + This allows you to make sure that mouseDrag() events are sent continuously, even + when the mouse isn't moving. This can be useful for things like auto-scrolling + components when the mouse is near an edge. + + Call this method during a mouseDown() or mouseDrag() callback, specifying the + minimum interval between consecutive mouse drag callbacks. The callbacks + will continue until the mouse is released, and then the interval will be reset, + so you need to make sure it's called every time you begin a drag event. + Passing an interval of 0 or less will cancel the auto-repeat. + + @see mouseDrag + */ + void beginDragAutoRepeat (int millisecondsBetweenCallbacks); + + //============================================================================== + /** In a tablet device which can be turned around, this is used to inidicate the orientation. */ + enum DisplayOrientation + { + upright = 1, /**< Indicates that the display is the normal way up. */ + upsideDown = 2, /**< Indicates that the display is upside-down. */ + rotatedClockwise = 4, /**< Indicates that the display is turned 90 degrees clockwise from its upright position. */ + rotatedAntiClockwise = 8, /**< Indicates that the display is turned 90 degrees anti-clockwise from its upright position. */ + + allOrientations = 1 + 2 + 4 + 8 /**< A combination of all the orientation values */ + }; + + /** In a tablet device which can be turned around, this returns the current orientation. */ + DisplayOrientation getCurrentOrientation() const; + + /** Sets which orientations the display is allowed to auto-rotate to. + + For devices that support rotating desktops, this lets you specify which of the orientations your app can use. + + The parameter is a bitwise or-ed combination of the values in DisplayOrientation, and must contain at least one + set bit. + */ + void setOrientationsEnabled (int allowedOrientations); + + /** Returns whether the display is allowed to auto-rotate to the given orientation. + Each orientation can be enabled using setOrientationEnabled(). By default, all orientations are allowed. + */ + bool isOrientationEnabled (DisplayOrientation orientation) const noexcept; + + //============================================================================== + class JUCE_API Displays + { + public: + /** Contains details about a display device. */ + struct Display + { + /** This is the bounds of the area of this display which isn't covered by + OS-dependent objects like the taskbar, menu bar, etc. */ + Rectangle userArea; + + /** This is the total physical area of this display, including any taskbars, etc */ + Rectangle totalArea; + + /** This is the scale-factor of this display. + If you create a component with size 1x1, this scale factor indicates the actual + size of the component in terms of physical pixels. + For higher-resolution displays, it may be a value greater than 1.0 + */ + double scale; + + /** The DPI of the display. + This is the number of physical pixels per inch. To get the number of logical + pixels per inch, divide this by the Display::scale value. + */ + double dpi; + + /** This will be true if this is the user's main screen. */ + bool isMain; + }; + + /** Returns the display which acts as user's main screen. */ + const Display& getMainDisplay() const noexcept; + + /** Returns the display which contains a particular point. + If the point lies outside all the displays, the nearest one will be returned. + */ + const Display& getDisplayContaining (Point position) const noexcept; + + /** Returns a RectangleList made up of all the displays. */ + RectangleList getRectangleList (bool userAreasOnly) const; + + /** Returns the smallest bounding box which contains all the displays. */ + Rectangle getTotalBounds (bool userAreasOnly) const; + + /** The list of displays. */ + Array displays; + + #ifndef DOXYGEN + /** @internal */ + void refresh(); + #endif + + private: + friend class Desktop; + friend struct ContainerDeletePolicy; + Displays (Desktop&); + ~Displays(); + + void init (Desktop&); + void findDisplays (float masterScale); + }; + + const Displays& getDisplays() const noexcept { return *displays; } + + //============================================================================== + /** Sets a global scale factor to be used for all desktop windows. + Setting this will also scale the monitor sizes that are returned by getDisplays(). + */ + void setGlobalScaleFactor (float newScaleFactor) noexcept; + + /** Returns the current global scale factor, as set by setGlobalScaleFactor(). + @see setGlobalScaleFactor + */ + float getGlobalScaleFactor() const noexcept { return masterScaleFactor; } + + //============================================================================== + /** True if the OS supports semitransparent windows */ + static bool canUseSemiTransparentWindows() noexcept; + +private: + //============================================================================== + static Desktop* instance; + + friend class Component; + friend class ComponentPeer; + friend class MouseInputSourceInternal; + friend class DeletedAtShutdown; + friend class TopLevelWindowManager; + + ScopedPointer mouseSources; + + ListenerList mouseListeners; + ListenerList focusListeners; + + Array desktopComponents; + Array peers; + + ScopedPointer displays; + + Point lastFakeMouseMove; + void sendMouseMove(); + + int mouseClickCounter, mouseWheelCounter; + void incrementMouseClickCounter() noexcept; + void incrementMouseWheelCounter() noexcept; + + ScopedPointer defaultLookAndFeel; + WeakReference currentLookAndFeel; + + Component* kioskModeComponent; + Rectangle kioskComponentOriginalBounds; + bool kioskModeReentrant; + + int allowedOrientations; + float masterScaleFactor; + + ComponentAnimator animator; + + void timerCallback() override; + void resetTimer(); + ListenerList& getMouseListeners(); + + void addDesktopComponent (Component*); + void removeDesktopComponent (Component*); + void componentBroughtToFront (Component*); + + void setKioskComponent (Component*, bool shouldBeEnabled, bool allowMenusAndBars); + + void triggerFocusCallback(); + void handleAsyncUpdate() override; + + static Point getMousePositionFloat(); + + static double getDefaultMasterScale(); + + Desktop(); + ~Desktop(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop) +}; + + +#endif // JUCE_DESKTOP_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp new file mode 100644 index 0000000..12b81df --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp @@ -0,0 +1,294 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class ModalComponentManager::ModalItem : public ComponentMovementWatcher +{ +public: + ModalItem (Component* const comp, const bool autoDelete_) + : ComponentMovementWatcher (comp), + component (comp), returnValue (0), + isActive (true), autoDelete (autoDelete_) + { + jassert (comp != nullptr); + } + + void componentMovedOrResized (bool, bool) override {} + + void componentPeerChanged() override + { + if (! component->isShowing()) + cancel(); + } + + void componentVisibilityChanged() override + { + if (! component->isShowing()) + cancel(); + } + + void componentBeingDeleted (Component& comp) override + { + ComponentMovementWatcher::componentBeingDeleted (comp); + + if (component == &comp || comp.isParentOf (component)) + { + autoDelete = false; + cancel(); + } + } + + void cancel() + { + if (isActive) + { + isActive = false; + + if (ModalComponentManager* mcm = ModalComponentManager::getInstanceWithoutCreating()) + mcm->triggerAsyncUpdate(); + } + } + + Component* component; + OwnedArray callbacks; + int returnValue; + bool isActive, autoDelete; + +private: + JUCE_DECLARE_NON_COPYABLE (ModalItem) +}; + +//============================================================================== +ModalComponentManager::ModalComponentManager() +{ +} + +ModalComponentManager::~ModalComponentManager() +{ + stack.clear(); + clearSingletonInstance(); +} + +juce_ImplementSingleton_SingleThreaded (ModalComponentManager) + + +//============================================================================== +void ModalComponentManager::startModal (Component* component, bool autoDelete) +{ + if (component != nullptr) + stack.add (new ModalItem (component, autoDelete)); +} + +void ModalComponentManager::attachCallback (Component* component, Callback* callback) +{ + if (callback != nullptr) + { + ScopedPointer callbackDeleter (callback); + + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->callbacks.add (callback); + callbackDeleter.release(); + break; + } + } + } +} + +void ModalComponentManager::endModal (Component* component) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + item->cancel(); + } +} + +void ModalComponentManager::endModal (Component* component, int returnValue) +{ + for (int i = stack.size(); --i >= 0;) + { + ModalItem* const item = stack.getUnchecked(i); + + if (item->component == component) + { + item->returnValue = returnValue; + item->cancel(); + } + } +} + +int ModalComponentManager::getNumModalComponents() const +{ + int n = 0; + for (int i = 0; i < stack.size(); ++i) + if (stack.getUnchecked(i)->isActive) + ++n; + + return n; +} + +Component* ModalComponentManager::getModalComponent (const int index) const +{ + int n = 0; + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive) + if (n++ == index) + return item->component; + } + + return nullptr; +} + +bool ModalComponentManager::isModal (Component* const comp) const +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + if (item->isActive && item->component == comp) + return true; + } + + return false; +} + +bool ModalComponentManager::isFrontModalComponent (Component* const comp) const +{ + return comp == getModalComponent (0); +} + +void ModalComponentManager::handleAsyncUpdate() +{ + for (int i = stack.size(); --i >= 0;) + { + const ModalItem* const item = stack.getUnchecked(i); + + if (! item->isActive) + { + ScopedPointer deleter (stack.removeAndReturn (i)); + Component::SafePointer compToDelete (item->autoDelete ? item->component : nullptr); + + for (int j = item->callbacks.size(); --j >= 0;) + item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue); + + compToDelete.deleteAndZero(); + } + } +} + +void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFocus) +{ + ComponentPeer* lastOne = nullptr; + + for (int i = 0; i < getNumModalComponents(); ++i) + { + Component* const c = getModalComponent (i); + + if (c == nullptr) + break; + + ComponentPeer* peer = c->getPeer(); + + if (peer != nullptr && peer != lastOne) + { + if (lastOne == nullptr) + { + peer->toFront (topOneShouldGrabFocus); + + if (topOneShouldGrabFocus) + peer->grabFocus(); + } + else + peer->toBehind (lastOne); + + lastOne = peer; + } + } +} + +bool ModalComponentManager::cancelAllModalComponents() +{ + const int numModal = getNumModalComponents(); + + for (int i = numModal; --i >= 0;) + if (Component* const c = getModalComponent(i)) + c->exitModalState (0); + + return numModal > 0; +} + +#if JUCE_MODAL_LOOPS_PERMITTED +class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback +{ +public: + ReturnValueRetriever (int& v, bool& done) : value (v), finished (done) {} + + void modalStateFinished (int returnValue) + { + finished = true; + value = returnValue; + } + +private: + int& value; + bool& finished; + + JUCE_DECLARE_NON_COPYABLE (ReturnValueRetriever) +}; + +int ModalComponentManager::runEventLoopForCurrentComponent() +{ + // This can only be run from the message thread! + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + int returnValue = 0; + + if (Component* currentlyModal = getModalComponent (0)) + { + FocusRestorer focusRestorer; + + bool finished = false; + attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished)); + + JUCE_TRY + { + while (! finished) + { + if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) + break; + } + } + JUCE_CATCH_EXCEPTION + } + + return returnValue; +} +#endif diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.h new file mode 100644 index 0000000..1b927e1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.h @@ -0,0 +1,369 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_MODALCOMPONENTMANAGER_H_INCLUDED +#define JUCE_MODALCOMPONENTMANAGER_H_INCLUDED + + +//============================================================================== +/** + Manages the system's stack of modal components. + + Normally you'll just use the Component methods to invoke modal states in components, + and won't have to deal with this class directly, but this is the singleton object that's + used internally to manage the stack. + + @see Component::enterModalState, Component::exitModalState, Component::isCurrentlyModal, + Component::getCurrentlyModalComponent, Component::isCurrentlyBlockedByAnotherModalComponent +*/ +class JUCE_API ModalComponentManager : private AsyncUpdater, + private DeletedAtShutdown +{ +public: + //============================================================================== + /** Receives callbacks when a modal component is dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + + For some quick ways of creating callback objects, see the ModalCallbackFunction class. + @see ModalCallbackFunction + */ + class Callback + { + public: + /** */ + Callback() {} + + /** Destructor. */ + virtual ~Callback() {} + + /** Called to indicate that a modal component has been dismissed. + + You can register a callback using Component::enterModalState() or + ModalComponentManager::attachCallback(). + + The returnValue parameter is the value that was passed to Component::exitModalState() + when the component was dismissed. + + The callback object will be deleted shortly after this method is called. + */ + virtual void modalStateFinished (int returnValue) = 0; + }; + + //============================================================================== + /** Returns the number of components currently being shown modally. + @see getModalComponent + */ + int getNumModalComponents() const; + + /** Returns one of the components being shown modally. + An index of 0 is the most recently-shown, topmost component. + */ + Component* getModalComponent (int index) const; + + /** Returns true if the specified component is in a modal state. */ + bool isModal (Component* component) const; + + /** Returns true if the specified component is currently the topmost modal component. */ + bool isFrontModalComponent (Component* component) const; + + /** Adds a new callback that will be called when the specified modal component is dismissed. + + If the component is modal, then when it is dismissed, either by being hidden, or by calling + Component::exitModalState(), then the Callback::modalStateFinished() method will be + called. + + Each component can have any number of callbacks associated with it, and this one is added + to that list. + + The object that is passed in will be deleted by the manager when it's no longer needed. If + the given component is not currently modal, the callback object is deleted immediately and + no action is taken. + */ + void attachCallback (Component* component, Callback* callback); + + /** Brings any modal components to the front. */ + void bringModalComponentsToFront (bool topOneShouldGrabFocus = true); + + /** Calls exitModalState (0) on any components that are currently modal. + @returns true if any components were modal; false if nothing needed cancelling + */ + bool cancelAllModalComponents(); + + #if JUCE_MODAL_LOOPS_PERMITTED + /** Runs the event loop until the currently topmost modal component is dismissed, and + returns the exit code for that component. + */ + int runEventLoopForCurrentComponent(); + #endif + + //============================================================================== + juce_DeclareSingleton_SingleThreaded_Minimal (ModalComponentManager) + +protected: + /** Creates a ModalComponentManager. + You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance() + */ + ModalComponentManager(); + + /** Destructor. */ + ~ModalComponentManager(); + + /** @internal */ + void handleAsyncUpdate() override; + +private: + //============================================================================== + class ModalItem; + class ReturnValueRetriever; + + friend class Component; + friend struct ContainerDeletePolicy; + OwnedArray stack; + + void startModal (Component*, bool autoDelete); + void endModal (Component*, int returnValue); + void endModal (Component*); + + JUCE_DECLARE_NON_COPYABLE (ModalComponentManager) +}; + +//============================================================================== +/** + This class provides some handy utility methods for creating ModalComponentManager::Callback + objects that will invoke a static function with some parameters when a modal component is dismissed. +*/ +class ModalCallbackFunction +{ +public: + //============================================================================== + /** This is a utility function to create a ModalComponentManager::Callback that will + call a static function with a parameter. + + The function that you supply must take two parameters - the first being an int, which is + the result code that was used when the modal component was dismissed, and the second + can be a custom type. Note that this custom value will be copied and stored, so it must + be a primitive type or a class that provides copy-by-value semantics. + + E.g. @code + static void myCallbackFunction (int modalResult, double customValue) + { + if (modalResult == 1) + doSomethingWith (customValue); + } + + Component* someKindOfComp; + ... + someKindOfComp->enterModalState (ModalCallbackFunction::create (myCallbackFunction, 3.0)); + @endcode + @see ModalComponentManager::Callback + */ + template + static ModalComponentManager::Callback* create (void (*functionToCall) (int, ParamType), + ParamType parameterValue) + { + return new FunctionCaller1 (functionToCall, parameterValue); + } + + //============================================================================== + /** This is a utility function to create a ModalComponentManager::Callback that will + call a static function with two custom parameters. + + The function that you supply must take three parameters - the first being an int, which is + the result code that was used when the modal component was dismissed, and the next two are + your custom types. Note that these custom values will be copied and stored, so they must + be primitive types or classes that provide copy-by-value semantics. + + E.g. @code + static void myCallbackFunction (int modalResult, double customValue1, String customValue2) + { + if (modalResult == 1) + doSomethingWith (customValue1, customValue2); + } + + Component* someKindOfComp; + ... + someKindOfComp->enterModalState (ModalCallbackFunction::create (myCallbackFunction, 3.0, String ("xyz"))); + @endcode + @see ModalComponentManager::Callback + */ + template + static ModalComponentManager::Callback* withParam (void (*functionToCall) (int, ParamType1, ParamType2), + ParamType1 parameterValue1, + ParamType2 parameterValue2) + { + return new FunctionCaller2 (functionToCall, parameterValue1, parameterValue2); + } + + //============================================================================== + /** This is a utility function to create a ModalComponentManager::Callback that will + call a static function with a component. + + The function that you supply must take two parameters - the first being an int, which is + the result code that was used when the modal component was dismissed, and the second + can be a Component class. The component will be stored as a WeakReference, so that if it gets + deleted before this callback is invoked, the pointer that is passed to the function will be null. + + E.g. @code + static void myCallbackFunction (int modalResult, Slider* mySlider) + { + if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..) + mySlider->setValue (0.0); + } + + Component* someKindOfComp; + Slider* mySlider; + ... + someKindOfComp->enterModalState (ModalCallbackFunction::forComponent (myCallbackFunction, mySlider)); + @endcode + @see ModalComponentManager::Callback + */ + template + static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*), + ComponentType* component) + { + return new ComponentCaller1 (functionToCall, component); + } + + //============================================================================== + /** Creates a ModalComponentManager::Callback that will call a static function with a component. + + The function that you supply must take three parameters - the first being an int, which is + the result code that was used when the modal component was dismissed, the second being a Component + class, and the third being a custom type (which must be a primitive type or have copy-by-value semantics). + The component will be stored as a WeakReference, so that if it gets deleted before this callback is + invoked, the pointer that is passed into the function will be null. + + E.g. @code + static void myCallbackFunction (int modalResult, Slider* mySlider, String customParam) + { + if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..) + mySlider->setName (customParam); + } + + Component* someKindOfComp; + Slider* mySlider; + ... + someKindOfComp->enterModalState (ModalCallbackFunction::forComponent (myCallbackFunction, mySlider, String ("hello"))); + @endcode + @see ModalComponentManager::Callback + */ + template + static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*, ParamType), + ComponentType* component, + ParamType param) + { + return new ComponentCaller2 (functionToCall, component, param); + } + +private: + //============================================================================== + template + class FunctionCaller1 : public ModalComponentManager::Callback + { + public: + typedef void (*FunctionType) (int, ParamType); + + FunctionCaller1 (FunctionType& f, ParamType& p1) + : function (f), param (p1) {} + + void modalStateFinished (int returnValue) { function (returnValue, param); } + + private: + const FunctionType function; + ParamType param; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FunctionCaller1) + }; + + template + class FunctionCaller2 : public ModalComponentManager::Callback + { + public: + typedef void (*FunctionType) (int, ParamType1, ParamType2); + + FunctionCaller2 (FunctionType& f, ParamType1& p1, ParamType2& p2) + : function (f), param1 (p1), param2 (p2) {} + + void modalStateFinished (int returnValue) { function (returnValue, param1, param2); } + + private: + const FunctionType function; + ParamType1 param1; + ParamType2 param2; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FunctionCaller2) + }; + + template + class ComponentCaller1 : public ModalComponentManager::Callback + { + public: + typedef void (*FunctionType) (int, ComponentType*); + + ComponentCaller1 (FunctionType& f, ComponentType* c) + : function (f), comp (c) {} + + void modalStateFinished (int returnValue) + { + function (returnValue, static_cast (comp.get())); + } + + private: + const FunctionType function; + WeakReference comp; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentCaller1) + }; + + template + class ComponentCaller2 : public ModalComponentManager::Callback + { + public: + typedef void (*FunctionType) (int, ComponentType*, ParamType1); + + ComponentCaller2 (FunctionType& f, ComponentType* c, ParamType1 p1) + : function (f), comp (c), param1 (p1) {} + + void modalStateFinished (int returnValue) + { + function (returnValue, static_cast (comp.get()), param1); + } + + private: + const FunctionType function; + WeakReference comp; + ParamType1 param1; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentCaller2) + }; + + ModalCallbackFunction(); + ~ModalCallbackFunction(); + JUCE_DECLARE_NON_COPYABLE (ModalCallbackFunction) +}; + + +#endif // JUCE_MODALCOMPONENTMANAGER_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.cpp new file mode 100644 index 0000000..3e88318 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.cpp @@ -0,0 +1,251 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +Drawable::Drawable() +{ + setInterceptsMouseClicks (false, false); + setPaintingIsUnclipped (true); +} + +Drawable::Drawable (const Drawable& other) + : Component (other.getName()) +{ + setComponentID (other.getComponentID()); +} + +Drawable::~Drawable() +{ +} + +//============================================================================== +void Drawable::draw (Graphics& g, float opacity, const AffineTransform& transform) const +{ + const_cast (this)->nonConstDraw (g, opacity, transform); +} + +void Drawable::nonConstDraw (Graphics& g, float opacity, const AffineTransform& transform) +{ + Graphics::ScopedSaveState ss (g); + + g.addTransform (AffineTransform::translation ((float) -(originRelativeToComponent.x), + (float) -(originRelativeToComponent.y)) + .followedBy (getTransform()) + .followedBy (transform)); + + if (! g.isClipEmpty()) + { + if (opacity < 1.0f) + { + g.beginTransparencyLayer (opacity); + paintEntireComponent (g, true); + g.endTransparencyLayer(); + } + else + { + paintEntireComponent (g, true); + } + } +} + +void Drawable::drawAt (Graphics& g, float x, float y, float opacity) const +{ + draw (g, opacity, AffineTransform::translation (x, y)); +} + +void Drawable::drawWithin (Graphics& g, const Rectangle& destArea, + RectanglePlacement placement, float opacity) const +{ + draw (g, opacity, placement.getTransformToFit (getDrawableBounds(), destArea)); +} + +//============================================================================== +DrawableComposite* Drawable::getParent() const +{ + return dynamic_cast (getParentComponent()); +} + +void Drawable::transformContextToCorrectOrigin (Graphics& g) +{ + g.setOrigin (originRelativeToComponent); +} + +void Drawable::parentHierarchyChanged() +{ + setBoundsToEnclose (getDrawableBounds()); +} + +void Drawable::setBoundsToEnclose (const Rectangle& area) +{ + Drawable* const parent = getParent(); + Point parentOrigin; + if (parent != nullptr) + parentOrigin = parent->originRelativeToComponent; + + const Rectangle newBounds (area.getSmallestIntegerContainer() + parentOrigin); + originRelativeToComponent = parentOrigin - newBounds.getPosition(); + setBounds (newBounds); +} + +//============================================================================== +bool Drawable::replaceColour (Colour original, Colour replacement) +{ + bool changed = false; + + for (int i = getNumChildComponents(); --i >= 0;) + if (Drawable* d = dynamic_cast (getChildComponent(i))) + changed = d->replaceColour (original, replacement) || changed; + + return changed; +} + +//============================================================================== +void Drawable::setOriginWithOriginalSize (Point originWithinParent) +{ + setTransform (AffineTransform::translation (originWithinParent.x, originWithinParent.y)); +} + +void Drawable::setTransformToFit (const Rectangle& area, RectanglePlacement placement) +{ + if (! area.isEmpty()) + setTransform (placement.getTransformToFit (getDrawableBounds(), area)); +} + +//============================================================================== +Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes) +{ + Drawable* result = nullptr; + + Image image (ImageFileFormat::loadFrom (data, numBytes)); + + if (image.isValid()) + { + DrawableImage* const di = new DrawableImage(); + di->setImage (image); + result = di; + } + else + { + const String asString (String::createStringFromData (data, (int) numBytes)); + + XmlDocument doc (asString); + ScopedPointer outer (doc.getDocumentElement (true)); + + if (outer != nullptr && outer->hasTagName ("svg")) + { + ScopedPointer svg (doc.getDocumentElement()); + + if (svg != nullptr) + result = Drawable::createFromSVG (*svg); + } + } + + return result; +} + +Drawable* Drawable::createFromImageDataStream (InputStream& dataSource) +{ + MemoryOutputStream mo; + mo << dataSource; + + return createFromImageData (mo.getData(), mo.getDataSize()); +} + +Drawable* Drawable::createFromImageFile (const File& file) +{ + FileInputStream fin (file); + + return fin.openedOk() ? createFromImageDataStream (fin) : nullptr; +} + +//============================================================================== +template +class DrawableTypeHandler : public ComponentBuilder::TypeHandler +{ +public: + DrawableTypeHandler() + : ComponentBuilder::TypeHandler (DrawableClass::valueTreeType) + { + } + + Component* addNewComponentFromState (const ValueTree& state, Component* parent) + { + DrawableClass* const d = new DrawableClass(); + + if (parent != nullptr) + parent->addAndMakeVisible (d); + + updateComponentFromState (d, state); + return d; + } + + void updateComponentFromState (Component* component, const ValueTree& state) + { + DrawableClass* const d = dynamic_cast (component); + jassert (d != nullptr); + d->refreshFromValueTree (state, *this->getBuilder()); + } +}; + +void Drawable::registerDrawableTypeHandlers (ComponentBuilder& builder) +{ + builder.registerTypeHandler (new DrawableTypeHandler ()); + builder.registerTypeHandler (new DrawableTypeHandler ()); + builder.registerTypeHandler (new DrawableTypeHandler ()); + builder.registerTypeHandler (new DrawableTypeHandler ()); + builder.registerTypeHandler (new DrawableTypeHandler ()); +} + +Drawable* Drawable::createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider) +{ + ComponentBuilder builder (tree); + builder.setImageProvider (imageProvider); + registerDrawableTypeHandlers (builder); + + ScopedPointer comp (builder.createComponent()); + Drawable* const d = dynamic_cast (static_cast (comp)); + + if (d != nullptr) + comp.release(); + + return d; +} + +//============================================================================== +Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& state_) + : state (state_) +{ +} + +String Drawable::ValueTreeWrapperBase::getID() const +{ + return state [ComponentBuilder::idProperty]; +} + +void Drawable::ValueTreeWrapperBase::setID (const String& newID) +{ + if (newID.isEmpty()) + state.removeProperty (ComponentBuilder::idProperty, nullptr); + else + state.setProperty (ComponentBuilder::idProperty, newID, nullptr); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h new file mode 100644 index 0000000..b8956d5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_Drawable.h @@ -0,0 +1,259 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLE_H_INCLUDED +#define JUCE_DRAWABLE_H_INCLUDED + + +//============================================================================== +/** + The base class for objects which can draw themselves, e.g. polygons, images, etc. + + @see DrawableComposite, DrawableImage, DrawablePath, DrawableText +*/ +class JUCE_API Drawable : public Component +{ +protected: + //============================================================================== + /** The base class can't be instantiated directly. + + @see DrawableComposite, DrawableImage, DrawablePath, DrawableText + */ + Drawable(); + +public: + /** Destructor. */ + virtual ~Drawable(); + + //============================================================================== + /** Creates a deep copy of this Drawable object. + + Use this to create a new copy of this and any sub-objects in the tree. + */ + virtual Drawable* createCopy() const = 0; + + //============================================================================== + /** Renders this Drawable object. + + Note that the preferred way to render a drawable in future is by using it + as a component and adding it to a parent, so you might want to consider that + before using this method. + + @see drawWithin + */ + void draw (Graphics& g, float opacity, + const AffineTransform& transform = AffineTransform::identity) const; + + /** Renders the Drawable at a given offset within the Graphics context. + + The coordinates passed-in are used to translate the object relative to its own + origin before drawing it - this is basically a quick way of saying: + + @code + draw (g, AffineTransform::translation (x, y)). + @endcode + + Note that the preferred way to render a drawable in future is by using it + as a component and adding it to a parent, so you might want to consider that + before using this method. + */ + void drawAt (Graphics& g, float x, float y, float opacity) const; + + /** Renders the Drawable within a rectangle, scaling it to fit neatly inside without + changing its aspect-ratio. + + The object can placed arbitrarily within the rectangle based on a Justification type, + and can either be made as big as possible, or just reduced to fit. + + Note that the preferred way to render a drawable in future is by using it + as a component and adding it to a parent, so you might want to consider that + before using this method. + + @param g the graphics context to render onto + @param destArea the target rectangle to fit the drawable into + @param placement defines the alignment and rescaling to use to fit + this object within the target rectangle. + @param opacity the opacity to use, in the range 0 to 1.0 + */ + void drawWithin (Graphics& g, + const Rectangle& destArea, + RectanglePlacement placement, + float opacity) const; + + + //============================================================================== + /** Resets any transformations on this drawable, and positions its origin within + its parent component. + */ + void setOriginWithOriginalSize (Point originWithinParent); + + /** Sets a transform for this drawable that will position it within the specified + area of its parent component. + */ + void setTransformToFit (const Rectangle& areaInParent, RectanglePlacement placement); + + /** Returns the DrawableComposite that contains this object, if there is one. */ + DrawableComposite* getParent() const; + + //============================================================================== + /** Tries to turn some kind of image file into a drawable. + + The data could be an image that the ImageFileFormat class understands, or it + could be SVG. + */ + static Drawable* createFromImageData (const void* data, size_t numBytes); + + /** Tries to turn a stream containing some kind of image data into a drawable. + + The data could be an image that the ImageFileFormat class understands, or it + could be SVG. + */ + static Drawable* createFromImageDataStream (InputStream& dataSource); + + /** Tries to turn a file containing some kind of image data into a drawable. + + The data could be an image that the ImageFileFormat class understands, or it + could be SVG. + */ + static Drawable* createFromImageFile (const File& file); + + /** Attempts to parse an SVG (Scalable Vector Graphics) document, and to turn this + into a Drawable tree. + + The object returned must be deleted by the caller. If something goes wrong + while parsing, it may return nullptr. + + SVG is a pretty large and complex spec, and this doesn't aim to be a full + implementation, but it can return the basic vector objects. + */ + static Drawable* createFromSVG (const XmlElement& svgDocument); + + /** Parses an SVG path string and returns it. */ + static Path parseSVGPath (const String& svgPath); + + //============================================================================== + /** Tries to create a Drawable from a previously-saved ValueTree. + The ValueTree must have been created by the createValueTree() method. + If there are any images used within the drawable, you'll need to provide a valid + ImageProvider object that can be used to retrieve these images from whatever type + of identifier is used to represent them. + Internally, this uses a ComponentBuilder, and registerDrawableTypeHandlers(). + */ + static Drawable* createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider); + + /** Creates a ValueTree to represent this Drawable. + The ValueTree that is returned can be turned back into a Drawable with createFromValueTree(). + If there are any images used in this drawable, you'll need to provide a valid ImageProvider + object that can be used to create storable representations of them. + */ + virtual ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const = 0; + + /** Returns the area that this drawble covers. + The result is expressed in this drawable's own coordinate space, and does not take + into account any transforms that may be applied to the component. + */ + virtual Rectangle getDrawableBounds() const = 0; + + /** Recursively replaces a colour that might be used for filling or stroking. + return true if any instances of this colour were found. + */ + virtual bool replaceColour (Colour originalColour, Colour replacementColour); + + //============================================================================== + /** Internal class used to manage ValueTrees that represent Drawables. */ + class ValueTreeWrapperBase + { + public: + ValueTreeWrapperBase (const ValueTree& state); + + ValueTree& getState() noexcept { return state; } + + String getID() const; + void setID (const String& newID); + + ValueTree state; + }; + + //============================================================================== + /** Registers a set of ComponentBuilder::TypeHandler objects that can be used to + load all the different Drawable types from a saved state. + @see ComponentBuilder::registerTypeHandler() + */ + static void registerDrawableTypeHandlers (ComponentBuilder& componentBuilder); + +protected: + //============================================================================== + friend class DrawableComposite; + friend class DrawableShape; + + /** @internal */ + void transformContextToCorrectOrigin (Graphics&); + /** @internal */ + void parentHierarchyChanged() override; + /** @internal */ + void setBoundsToEnclose (const Rectangle&); + + Point originRelativeToComponent; + + #ifndef DOXYGEN + /** Internal utility class used by Drawables. */ + template + class Positioner : public RelativeCoordinatePositionerBase + { + public: + Positioner (DrawableType& c) + : RelativeCoordinatePositionerBase (c), + owner (c) + {} + + bool registerCoordinates() { return owner.registerCoordinates (*this); } + void applyToComponentBounds() + { + ComponentScope scope (getComponent()); + owner.recalculateCoordinates (&scope); + } + + void applyNewBounds (const Rectangle&) + { + jassertfalse; // drawables can't be resized directly! + } + + private: + DrawableType& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner) + }; + + Drawable (const Drawable&); + #endif + +private: + void nonConstDraw (Graphics&, float opacity, const AffineTransform&); + + Drawable& operator= (const Drawable&); + JUCE_LEAK_DETECTOR (Drawable) +}; + + +#endif // JUCE_DRAWABLE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp new file mode 100644 index 0000000..688507f --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.cpp @@ -0,0 +1,327 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableComposite::DrawableComposite() + : bounds (Point(), Point (100.0f, 0.0f), Point (0.0f, 100.0f)), + updateBoundsReentrant (false) +{ + setContentArea (RelativeRectangle (RelativeCoordinate (0.0), + RelativeCoordinate (100.0), + RelativeCoordinate (0.0), + RelativeCoordinate (100.0))); +} + +DrawableComposite::DrawableComposite (const DrawableComposite& other) + : Drawable (other), + bounds (other.bounds), + markersX (other.markersX), + markersY (other.markersY), + updateBoundsReentrant (false) +{ + for (int i = 0; i < other.getNumChildComponents(); ++i) + if (const Drawable* const d = dynamic_cast (other.getChildComponent(i))) + addAndMakeVisible (d->createCopy()); +} + +DrawableComposite::~DrawableComposite() +{ + deleteAllChildren(); +} + +Drawable* DrawableComposite::createCopy() const +{ + return new DrawableComposite (*this); +} + +//============================================================================== +Rectangle DrawableComposite::getDrawableBounds() const +{ + Rectangle r; + + for (int i = getNumChildComponents(); --i >= 0;) + if (const Drawable* const d = dynamic_cast (getChildComponent(i))) + r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform()) + : d->getDrawableBounds()); + + return r; +} + +MarkerList* DrawableComposite::getMarkers (bool xAxis) +{ + return xAxis ? &markersX : &markersY; +} + +RelativeRectangle DrawableComposite::getContentArea() const +{ + jassert (markersX.getNumMarkers() >= 2 && markersX.getMarker (0)->name == contentLeftMarkerName && markersX.getMarker (1)->name == contentRightMarkerName); + jassert (markersY.getNumMarkers() >= 2 && markersY.getMarker (0)->name == contentTopMarkerName && markersY.getMarker (1)->name == contentBottomMarkerName); + + return RelativeRectangle (markersX.getMarker(0)->position, markersX.getMarker(1)->position, + markersY.getMarker(0)->position, markersY.getMarker(1)->position); +} + +void DrawableComposite::setContentArea (const RelativeRectangle& newArea) +{ + markersX.setMarker (contentLeftMarkerName, newArea.left); + markersX.setMarker (contentRightMarkerName, newArea.right); + markersY.setMarker (contentTopMarkerName, newArea.top); + markersY.setMarker (contentBottomMarkerName, newArea.bottom); +} + +void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBounds) +{ + if (bounds != newBounds) + { + bounds = newBounds; + + if (bounds.isDynamic()) + { + Drawable::Positioner* const p = new Drawable::Positioner (*this); + setPositioner (p); + p->apply(); + } + else + { + setPositioner (nullptr); + recalculateCoordinates (nullptr); + } + } +} + +void DrawableComposite::resetBoundingBoxToContentArea() +{ + const RelativeRectangle content (getContentArea()); + + setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), + RelativePoint (content.right, content.top), + RelativePoint (content.left, content.bottom))); +} + +void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() +{ + const Rectangle activeArea (getDrawableBounds()); + + setContentArea (RelativeRectangle (RelativeCoordinate (activeArea.getX()), + RelativeCoordinate (activeArea.getRight()), + RelativeCoordinate (activeArea.getY()), + RelativeCoordinate (activeArea.getBottom()))); + resetBoundingBoxToContentArea(); +} + +bool DrawableComposite::registerCoordinates (RelativeCoordinatePositionerBase& pos) +{ + bool ok = pos.addPoint (bounds.topLeft); + ok = pos.addPoint (bounds.topRight) && ok; + return pos.addPoint (bounds.bottomLeft) && ok; +} + +void DrawableComposite::recalculateCoordinates (Expression::Scope* scope) +{ + Point resolved[3]; + bounds.resolveThreePoints (resolved, scope); + + const Rectangle content (getContentArea().resolve (scope)); + + AffineTransform t (AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].x, resolved[0].y, + content.getRight(), content.getY(), resolved[1].x, resolved[1].y, + content.getX(), content.getBottom(), resolved[2].x, resolved[2].y)); + + if (t.isSingularity()) + t = AffineTransform::identity; + + setTransform (t); +} + +void DrawableComposite::parentHierarchyChanged() +{ + DrawableComposite* parent = getParent(); + if (parent != nullptr) + originRelativeToComponent = parent->originRelativeToComponent - getPosition(); +} + +void DrawableComposite::childBoundsChanged (Component*) +{ + updateBoundsToFitChildren(); +} + +void DrawableComposite::childrenChanged() +{ + updateBoundsToFitChildren(); +} + +void DrawableComposite::updateBoundsToFitChildren() +{ + if (! updateBoundsReentrant) + { + const ScopedValueSetter setter (updateBoundsReentrant, true, false); + + Rectangle childArea; + + for (int i = getNumChildComponents(); --i >= 0;) + childArea = childArea.getUnion (getChildComponent(i)->getBoundsInParent()); + + const Point delta (childArea.getPosition()); + childArea += getPosition(); + + if (childArea != getBounds()) + { + if (! delta.isOrigin()) + { + originRelativeToComponent -= delta; + + for (int i = getNumChildComponents(); --i >= 0;) + if (Component* const c = getChildComponent(i)) + c->setBounds (c->getBounds() - delta); + } + + setBounds (childArea); + } + } +} + +//============================================================================== +const char* const DrawableComposite::contentLeftMarkerName = "left"; +const char* const DrawableComposite::contentRightMarkerName = "right"; +const char* const DrawableComposite::contentTopMarkerName = "top"; +const char* const DrawableComposite::contentBottomMarkerName = "bottom"; + +//============================================================================== +const Identifier DrawableComposite::valueTreeType ("Group"); + +const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft"); +const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables"); +const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX"); +const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY"); + +//============================================================================== +DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} + +ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const +{ + return state.getChildWithName (childGroupTag); +} + +ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) +{ + return state.getOrCreateChildWithName (childGroupTag, undoManager); +} + +RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const +{ + return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), + state.getProperty (topRight, "100, 0"), + state.getProperty (bottomLeft, "0, 100")); +} + +void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +{ + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); +} + +void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager) +{ + const RelativeRectangle content (getContentArea()); + + setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), + RelativePoint (content.right, content.top), + RelativePoint (content.left, content.bottom)), undoManager); +} + +RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const +{ + MarkerList::ValueTreeWrapper marksX (getMarkerList (true)); + MarkerList::ValueTreeWrapper marksY (getMarkerList (false)); + + return RelativeRectangle (marksX.getMarker (marksX.getMarkerState (0)).position, + marksX.getMarker (marksX.getMarkerState (1)).position, + marksY.getMarker (marksY.getMarkerState (0)).position, + marksY.getMarker (marksY.getMarkerState (1)).position); +} + +void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager) +{ + MarkerList::ValueTreeWrapper marksX (getMarkerListCreating (true, nullptr)); + MarkerList::ValueTreeWrapper marksY (getMarkerListCreating (false, nullptr)); + + marksX.setMarker (MarkerList::Marker (contentLeftMarkerName, newArea.left), undoManager); + marksX.setMarker (MarkerList::Marker (contentRightMarkerName, newArea.right), undoManager); + marksY.setMarker (MarkerList::Marker (contentTopMarkerName, newArea.top), undoManager); + marksY.setMarker (MarkerList::Marker (contentBottomMarkerName, newArea.bottom), undoManager); +} + +MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const +{ + return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY); +} + +MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) +{ + return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); +} + +//============================================================================== +void DrawableComposite::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) +{ + const ValueTreeWrapper wrapper (tree); + setComponentID (wrapper.getID()); + + wrapper.getMarkerList (true).applyTo (markersX); + wrapper.getMarkerList (false).applyTo (markersY); + + setBoundingBox (wrapper.getBoundingBox()); + + builder.updateChildComponents (*this, wrapper.getChildList()); +} + +ValueTree DrawableComposite::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); + + v.setID (getComponentID()); + v.setBoundingBox (bounds, nullptr); + + ValueTree childList (v.getChildListCreating (nullptr)); + + for (int i = 0; i < getNumChildComponents(); ++i) + { + const Drawable* const d = dynamic_cast (getChildComponent(i)); + jassert (d != nullptr); // You can't save a mix of Drawables and normal components! + + childList.addChild (d->createValueTree (imageProvider), -1, nullptr); + } + + v.getMarkerListCreating (true, nullptr).readFrom (markersX, nullptr); + v.getMarkerListCreating (false, nullptr).readFrom (markersY, nullptr); + + return tree; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.h new file mode 100644 index 0000000..1de9111 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.h @@ -0,0 +1,156 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLECOMPOSITE_H_INCLUDED +#define JUCE_DRAWABLECOMPOSITE_H_INCLUDED + + +//============================================================================== +/** + A drawable object which acts as a container for a set of other Drawables. + + @see Drawable +*/ +class JUCE_API DrawableComposite : public Drawable +{ +public: + //============================================================================== + /** Creates a composite Drawable. */ + DrawableComposite(); + + /** Creates a copy of a DrawableComposite. */ + DrawableComposite (const DrawableComposite&); + + /** Destructor. */ + ~DrawableComposite(); + + //============================================================================== + /** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. + @see setContentArea + */ + void setBoundingBox (const RelativeParallelogram& newBoundingBox); + + /** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered. + @see setBoundingBox + */ + const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } + + /** Changes the bounding box transform to match the content area, so that any sub-items will + be drawn at their untransformed positions. + */ + void resetBoundingBoxToContentArea(); + + /** Returns the main content rectangle. + The content area is actually defined by the markers named "left", "right", "top" and + "bottom", but this method is a shortcut that returns them all at once. + @see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName + */ + RelativeRectangle getContentArea() const; + + /** Changes the main content area. + The content area is actually defined by the markers named "left", "right", "top" and + "bottom", but this method is a shortcut that sets them all at once. + @see setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName + */ + void setContentArea (const RelativeRectangle& newArea); + + /** Resets the content area and the bounding transform to fit around the area occupied + by the child components (ignoring any markers). + */ + void resetContentAreaAndBoundingBoxToFitChildren(); + + //============================================================================== + /** The name of the marker that defines the left edge of the content area. */ + static const char* const contentLeftMarkerName; + /** The name of the marker that defines the right edge of the content area. */ + static const char* const contentRightMarkerName; + /** The name of the marker that defines the top edge of the content area. */ + static const char* const contentTopMarkerName; + /** The name of the marker that defines the bottom edge of the content area. */ + static const char* const contentBottomMarkerName; + + //============================================================================== + /** @internal */ + Drawable* createCopy() const; + /** @internal */ + void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); + /** @internal */ + ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const; + /** @internal */ + static const Identifier valueTreeType; + /** @internal */ + Rectangle getDrawableBounds() const; + /** @internal */ + void childBoundsChanged (Component*) override; + /** @internal */ + void childrenChanged() override; + /** @internal */ + void parentHierarchyChanged() override; + /** @internal */ + MarkerList* getMarkers (bool xAxis) override; + + //============================================================================== + /** Internally-used class for wrapping a DrawableComposite's state into a ValueTree. */ + class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase + { + public: + ValueTreeWrapper (const ValueTree& state); + + ValueTree getChildList() const; + ValueTree getChildListCreating (UndoManager* undoManager); + + RelativeParallelogram getBoundingBox() const; + void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); + void resetBoundingBoxToContentArea (UndoManager* undoManager); + + RelativeRectangle getContentArea() const; + void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager); + + MarkerList::ValueTreeWrapper getMarkerList (bool xAxis) const; + MarkerList::ValueTreeWrapper getMarkerListCreating (bool xAxis, UndoManager* undoManager); + + static const Identifier topLeft, topRight, bottomLeft; + + private: + static const Identifier childGroupTag, markerGroupTagX, markerGroupTagY; + }; + +private: + //============================================================================== + RelativeParallelogram bounds; + MarkerList markersX, markersY; + bool updateBoundsReentrant; + + friend class Drawable::Positioner; + bool registerCoordinates (RelativeCoordinatePositionerBase&); + void recalculateCoordinates (Expression::Scope*); + + void updateBoundsToFitChildren(); + + DrawableComposite& operator= (const DrawableComposite&); + JUCE_LEAK_DETECTOR (DrawableComposite) +}; + + +#endif // JUCE_DRAWABLECOMPOSITE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp new file mode 100644 index 0000000..d609011 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.cpp @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableImage::DrawableImage() + : opacity (1.0f), + overlayColour (0x00000000) +{ + bounds.topRight = RelativePoint (Point (1.0f, 0.0f)); + bounds.bottomLeft = RelativePoint (Point (0.0f, 1.0f)); +} + +DrawableImage::DrawableImage (const DrawableImage& other) + : Drawable (other), + image (other.image), + opacity (other.opacity), + overlayColour (other.overlayColour), + bounds (other.bounds) +{ + setBounds (other.getBounds()); +} + +DrawableImage::~DrawableImage() +{ +} + +//============================================================================== +void DrawableImage::setImage (const Image& imageToUse) +{ + image = imageToUse; + setBounds (imageToUse.getBounds()); + + bounds.topLeft = RelativePoint (Point (0.0f, 0.0f)); + bounds.topRight = RelativePoint (Point ((float) image.getWidth(), 0.0f)); + bounds.bottomLeft = RelativePoint (Point (0.0f, (float) image.getHeight())); + recalculateCoordinates (nullptr); + + repaint(); +} + +void DrawableImage::setOpacity (const float newOpacity) +{ + opacity = newOpacity; +} + +void DrawableImage::setOverlayColour (Colour newOverlayColour) +{ + overlayColour = newOverlayColour; +} + +void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds) +{ + if (bounds != newBounds) + { + bounds = newBounds; + + if (bounds.isDynamic()) + { + Drawable::Positioner* const p = new Drawable::Positioner (*this); + setPositioner (p); + p->apply(); + } + else + { + setPositioner (nullptr); + recalculateCoordinates (nullptr); + } + } +} + +//============================================================================== +bool DrawableImage::registerCoordinates (RelativeCoordinatePositionerBase& pos) +{ + bool ok = pos.addPoint (bounds.topLeft); + ok = pos.addPoint (bounds.topRight) && ok; + return pos.addPoint (bounds.bottomLeft) && ok; +} + +void DrawableImage::recalculateCoordinates (Expression::Scope* scope) +{ + if (image.isValid()) + { + Point resolved[3]; + bounds.resolveThreePoints (resolved, scope); + + const Point tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth()); + const Point bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight()); + + AffineTransform t (AffineTransform::fromTargetPoints (resolved[0].x, resolved[0].y, + tr.x, tr.y, + bl.x, bl.y)); + + if (t.isSingularity()) + t = AffineTransform::identity; + + setTransform (t); + } +} + +//============================================================================== +void DrawableImage::paint (Graphics& g) +{ + if (image.isValid()) + { + if (opacity > 0.0f && ! overlayColour.isOpaque()) + { + g.setOpacity (opacity); + g.drawImageAt (image, 0, 0, false); + } + + if (! overlayColour.isTransparent()) + { + g.setColour (overlayColour.withMultipliedAlpha (opacity)); + g.drawImageAt (image, 0, 0, true); + } + } +} + +Rectangle DrawableImage::getDrawableBounds() const +{ + return image.getBounds().toFloat(); +} + +bool DrawableImage::hitTest (int x, int y) +{ + return Drawable::hitTest (x, y) && image.isValid() && image.getPixelAt (x, y).getAlpha() >= 127; +} + +Drawable* DrawableImage::createCopy() const +{ + return new DrawableImage (*this); +} + +//============================================================================== +const Identifier DrawableImage::valueTreeType ("Image"); + +const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity"); +const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay"); +const Identifier DrawableImage::ValueTreeWrapper::image ("image"); +const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft"); + +//============================================================================== +DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} + +var DrawableImage::ValueTreeWrapper::getImageIdentifier() const +{ + return state [image]; +} + +Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (image, undoManager); +} + +void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager) +{ + state.setProperty (image, newIdentifier, undoManager); +} + +float DrawableImage::ValueTreeWrapper::getOpacity() const +{ + return (float) state.getProperty (opacity, 1.0); +} + +Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager) +{ + if (! state.hasProperty (opacity)) + state.setProperty (opacity, 1.0, undoManager); + + return state.getPropertyAsValue (opacity, undoManager); +} + +void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager) +{ + state.setProperty (opacity, newOpacity, undoManager); +} + +Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const +{ + return Colour::fromString (state [overlay].toString()); +} + +void DrawableImage::ValueTreeWrapper::setOverlayColour (Colour newColour, UndoManager* undoManager) +{ + if (newColour.isTransparent()) + state.removeProperty (overlay, undoManager); + else + state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager); +} + +Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (overlay, undoManager); +} + +RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const +{ + return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), + state.getProperty (topRight, "100, 0"), + state.getProperty (bottomLeft, "0, 100")); +} + +void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +{ + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); +} + + +//============================================================================== +void DrawableImage::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) +{ + const ValueTreeWrapper controller (tree); + setComponentID (controller.getID()); + + const float newOpacity = controller.getOpacity(); + const Colour newOverlayColour (controller.getOverlayColour()); + + Image newImage; + const var imageIdentifier (controller.getImageIdentifier()); + + + jassert (builder.getImageProvider() != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them! + + if (builder.getImageProvider() != nullptr) + newImage = builder.getImageProvider()->getImageForIdentifier (imageIdentifier); + + const RelativeParallelogram newBounds (controller.getBoundingBox()); + + if (bounds != newBounds || newOpacity != opacity + || overlayColour != newOverlayColour || image != newImage) + { + repaint(); + opacity = newOpacity; + overlayColour = newOverlayColour; + + if (image != newImage) + setImage (newImage); + + setBoundingBox (newBounds); + } +} + +ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); + + v.setID (getComponentID()); + v.setOpacity (opacity, nullptr); + v.setOverlayColour (overlayColour, nullptr); + v.setBoundingBox (bounds, nullptr); + + if (image.isValid()) + { + jassert (imageProvider != nullptr); // if you're using images, you need to provide something that can load and save them! + + if (imageProvider != nullptr) + v.setImageIdentifier (imageProvider->getIdentifierForImage (image), nullptr); + } + + return tree; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h new file mode 100644 index 0000000..6089dee --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h @@ -0,0 +1,138 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLEIMAGE_H_INCLUDED +#define JUCE_DRAWABLEIMAGE_H_INCLUDED + + +//============================================================================== +/** + A drawable object which is a bitmap image. + + @see Drawable +*/ +class JUCE_API DrawableImage : public Drawable +{ +public: + //============================================================================== + DrawableImage(); + DrawableImage (const DrawableImage&); + + /** Destructor. */ + ~DrawableImage(); + + //============================================================================== + /** Sets the image that this drawable will render. */ + void setImage (const Image& imageToUse); + + /** Returns the current image. */ + const Image& getImage() const noexcept { return image; } + + /** Sets the opacity to use when drawing the image. */ + void setOpacity (float newOpacity); + + /** Returns the image's opacity. */ + float getOpacity() const noexcept { return opacity; } + + /** Sets a colour to draw over the image's alpha channel. + + By default this is transparent so isn't drawn, but if you set a non-transparent + colour here, then it will be overlaid on the image, using the image's alpha + channel as a mask. + + This is handy for doing things like darkening or lightening an image by overlaying + it with semi-transparent black or white. + */ + void setOverlayColour (Colour newOverlayColour); + + /** Returns the overlay colour. */ + Colour getOverlayColour() const noexcept { return overlayColour; } + + /** Sets the bounding box within which the image should be displayed. */ + void setBoundingBox (const RelativeParallelogram& newBounds); + + /** Returns the position to which the image's top-left corner should be remapped in the target + coordinate space when rendering this object. + @see setTransform + */ + const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } + + //============================================================================== + /** @internal */ + void paint (Graphics&) override; + /** @internal */ + bool hitTest (int x, int y) override; + /** @internal */ + Drawable* createCopy() const override; + /** @internal */ + Rectangle getDrawableBounds() const override; + /** @internal */ + void refreshFromValueTree (const ValueTree& tree, ComponentBuilder&); + /** @internal */ + ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override; + /** @internal */ + static const Identifier valueTreeType; + + //============================================================================== + /** Internally-used class for wrapping a DrawableImage's state into a ValueTree. */ + class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase + { + public: + ValueTreeWrapper (const ValueTree& state); + + var getImageIdentifier() const; + void setImageIdentifier (const var&, UndoManager*); + Value getImageIdentifierValue (UndoManager*); + + float getOpacity() const; + void setOpacity (float newOpacity, UndoManager*); + Value getOpacityValue (UndoManager*); + + Colour getOverlayColour() const; + void setOverlayColour (Colour newColour, UndoManager*); + Value getOverlayColourValue (UndoManager*); + + RelativeParallelogram getBoundingBox() const; + void setBoundingBox (const RelativeParallelogram&, UndoManager*); + + static const Identifier opacity, overlay, image, topLeft, topRight, bottomLeft; + }; + +private: + //============================================================================== + Image image; + float opacity; + Colour overlayColour; + RelativeParallelogram bounds; + + friend class Drawable::Positioner; + bool registerCoordinates (RelativeCoordinatePositionerBase&); + void recalculateCoordinates (Expression::Scope*); + + DrawableImage& operator= (const DrawableImage&); + JUCE_LEAK_DETECTOR (DrawableImage) +}; + + +#endif // JUCE_DRAWABLEIMAGE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.cpp new file mode 100644 index 0000000..6dc46ba --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.cpp @@ -0,0 +1,570 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawablePath::DrawablePath() +{ +} + +DrawablePath::DrawablePath (const DrawablePath& other) + : DrawableShape (other) +{ + if (other.relativePath != nullptr) + setPath (*other.relativePath); + else + setPath (other.path); +} + +DrawablePath::~DrawablePath() +{ +} + +Drawable* DrawablePath::createCopy() const +{ + return new DrawablePath (*this); +} + +//============================================================================== +void DrawablePath::setPath (const Path& newPath) +{ + path = newPath; + pathChanged(); +} + +const Path& DrawablePath::getPath() const +{ + return path; +} + +const Path& DrawablePath::getStrokePath() const +{ + return strokePath; +} + +void DrawablePath::applyRelativePath (const RelativePointPath& newRelativePath, Expression::Scope* scope) +{ + Path newPath; + newRelativePath.createPath (newPath, scope); + + if (path != newPath) + { + path.swapWithPath (newPath); + pathChanged(); + } +} + +//============================================================================== +class DrawablePath::RelativePositioner : public RelativeCoordinatePositionerBase +{ +public: + RelativePositioner (DrawablePath& comp) + : RelativeCoordinatePositionerBase (comp), + owner (comp) + { + } + + bool registerCoordinates() + { + bool ok = true; + + jassert (owner.relativePath != nullptr); + const RelativePointPath& path = *owner.relativePath; + + for (int i = 0; i < path.elements.size(); ++i) + { + RelativePointPath::ElementBase* const e = path.elements.getUnchecked(i); + + int numPoints; + RelativePoint* const points = e->getControlPoints (numPoints); + + for (int j = numPoints; --j >= 0;) + ok = addPoint (points[j]) && ok; + } + + return ok; + } + + void applyToComponentBounds() + { + jassert (owner.relativePath != nullptr); + + ComponentScope scope (getComponent()); + owner.applyRelativePath (*owner.relativePath, &scope); + } + + void applyNewBounds (const Rectangle&) + { + jassertfalse; // drawables can't be resized directly! + } + +private: + DrawablePath& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner) +}; + +void DrawablePath::setPath (const RelativePointPath& newRelativePath) +{ + if (newRelativePath.containsAnyDynamicPoints()) + { + if (relativePath == nullptr || newRelativePath != *relativePath) + { + relativePath = new RelativePointPath (newRelativePath); + + RelativePositioner* const p = new RelativePositioner (*this); + setPositioner (p); + p->apply(); + } + } + else + { + relativePath = nullptr; + applyRelativePath (newRelativePath, nullptr); + } +} + +//============================================================================== +const Identifier DrawablePath::valueTreeType ("Path"); + +const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); +const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); +const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); +const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); + +//============================================================================== +DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : FillAndStrokeState (state_) +{ + jassert (state.hasType (valueTreeType)); +} + +ValueTree DrawablePath::ValueTreeWrapper::getPathState() +{ + return state.getOrCreateChildWithName (path, nullptr); +} + +bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const +{ + return state [nonZeroWinding]; +} + +void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) +{ + state.setProperty (nonZeroWinding, b, undoManager); +} + +void DrawablePath::ValueTreeWrapper::readFrom (const RelativePointPath& p, UndoManager* undoManager) +{ + setUsesNonZeroWinding (p.usesNonZeroWinding, undoManager); + + ValueTree pathTree (getPathState()); + pathTree.removeAllChildren (undoManager); + + for (int i = 0; i < p.elements.size(); ++i) + pathTree.addChild (p.elements.getUnchecked(i)->createTree(), -1, undoManager); +} + +void DrawablePath::ValueTreeWrapper::writeTo (RelativePointPath& p) const +{ + p.usesNonZeroWinding = usesNonZeroWinding(); + RelativePoint points[3]; + + const ValueTree pathTree (state.getChildWithName (path)); + const int num = pathTree.getNumChildren(); + for (int i = 0; i < num; ++i) + { + const Element e (pathTree.getChild(i)); + + const int numCps = e.getNumControlPoints(); + for (int j = 0; j < numCps; ++j) + points[j] = e.getControlPoint (j); + + RelativePointPath::ElementBase* newElement = nullptr; + const Identifier t (e.getType()); + + if (t == Element::startSubPathElement) newElement = new RelativePointPath::StartSubPath (points[0]); + else if (t == Element::closeSubPathElement) newElement = new RelativePointPath::CloseSubPath(); + else if (t == Element::lineToElement) newElement = new RelativePointPath::LineTo (points[0]); + else if (t == Element::quadraticToElement) newElement = new RelativePointPath::QuadraticTo (points[0], points[1]); + else if (t == Element::cubicToElement) newElement = new RelativePointPath::CubicTo (points[0], points[1], points[2]); + else jassertfalse; + + p.addElement (newElement); + } +} + +//============================================================================== +const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode"); +const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); +const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); +const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); +const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); +const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); + +const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner"; +const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round"; +const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm"; + +DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) + : state (state_) +{ +} + +DrawablePath::ValueTreeWrapper::Element::~Element() +{ +} + +DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const +{ + return ValueTreeWrapper (state.getParent().getParent()); +} + +DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const +{ + return Element (state.getSibling (-1)); +} + +int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const noexcept +{ + const Identifier i (state.getType()); + if (i == startSubPathElement || i == lineToElement) return 1; + if (i == quadraticToElement) return 2; + if (i == cubicToElement) return 3; + return 0; +} + +RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const +{ + jassert (index >= 0 && index < getNumControlPoints()); + return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); +} + +Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager) +{ + jassert (index >= 0 && index < getNumControlPoints()); + return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager); +} + +void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) +{ + jassert (index >= 0 && index < getNumControlPoints()); + state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); +} + +RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const +{ + const Identifier i (state.getType()); + + if (i == startSubPathElement) + return getControlPoint (0); + + jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement); + + return getPreviousElement().getEndPoint(); +} + +RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const +{ + const Identifier i (state.getType()); + if (i == startSubPathElement || i == lineToElement) return getControlPoint (0); + if (i == quadraticToElement) return getControlPoint (1); + if (i == cubicToElement) return getControlPoint (2); + + jassert (i == closeSubPathElement); + return RelativePoint(); +} + +float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::Scope* scope) const +{ + const Identifier i (state.getType()); + + if (i == lineToElement || i == closeSubPathElement) + return getEndPoint().resolve (scope).getDistanceFrom (getStartPoint().resolve (scope)); + + if (i == cubicToElement) + { + Path p; + p.startNewSubPath (getStartPoint().resolve (scope)); + p.cubicTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope), getControlPoint (2).resolve (scope)); + return p.getLength(); + } + + if (i == quadraticToElement) + { + Path p; + p.startNewSubPath (getStartPoint().resolve (scope)); + p.quadraticTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope)); + return p.getLength(); + } + + jassert (i == startSubPathElement); + return 0; +} + +String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const +{ + return state [mode].toString(); +} + +void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager) +{ + if (state.hasType (cubicToElement)) + state.setProperty (mode, newMode, undoManager); +} + +void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager) +{ + const Identifier i (state.getType()); + + if (i == quadraticToElement || i == cubicToElement) + { + ValueTree newState (lineToElement); + Element e (newState); + e.setControlPoint (0, getEndPoint(), undoManager); + state = newState; + } +} + +void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::Scope* scope, UndoManager* undoManager) +{ + const Identifier i (state.getType()); + + if (i == lineToElement || i == quadraticToElement) + { + ValueTree newState (cubicToElement); + Element e (newState); + + const RelativePoint start (getStartPoint()); + const RelativePoint end (getEndPoint()); + const Point startResolved (start.resolve (scope)); + const Point endResolved (end.resolve (scope)); + e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager); + e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager); + e.setControlPoint (2, end, undoManager); + + state = newState; + } +} + +void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager) +{ + const Identifier i (state.getType()); + + if (i != startSubPathElement) + { + ValueTree newState (startSubPathElement); + Element e (newState); + e.setControlPoint (0, getEndPoint(), undoManager); + state = newState; + } +} + +namespace DrawablePathHelpers +{ + static Point findCubicSubdivisionPoint (float proportion, const Point points[4]) + { + const Point mid1 (points[0] + (points[1] - points[0]) * proportion), + mid2 (points[1] + (points[2] - points[1]) * proportion), + mid3 (points[2] + (points[3] - points[2]) * proportion); + + const Point newCp1 (mid1 + (mid2 - mid1) * proportion), + newCp2 (mid2 + (mid3 - mid2) * proportion); + + return newCp1 + (newCp2 - newCp1) * proportion; + } + + static Point findQuadraticSubdivisionPoint (float proportion, const Point points[3]) + { + const Point mid1 (points[0] + (points[1] - points[0]) * proportion), + mid2 (points[1] + (points[2] - points[1]) * proportion); + + return mid1 + (mid2 - mid1) * proportion; + } +} + +float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (Point targetPoint, Expression::Scope* scope) const +{ + using namespace DrawablePathHelpers; + const Identifier pointType (state.getType()); + float bestProp = 0; + + if (pointType == cubicToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); + + const Point points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) }; + + float bestDistance = std::numeric_limits::max(); + + for (int i = 110; --i >= 0;) + { + float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); + const Point centre (findCubicSubdivisionPoint (prop, points)); + const float distance = centre.getDistanceFrom (targetPoint); + + if (distance < bestDistance) + { + bestProp = prop; + bestDistance = distance; + } + } + } + else if (pointType == quadraticToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); + const Point points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) }; + + float bestDistance = std::numeric_limits::max(); + + for (int i = 110; --i >= 0;) + { + float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); + const Point centre (findQuadraticSubdivisionPoint ((float) prop, points)); + const float distance = centre.getDistanceFrom (targetPoint); + + if (distance < bestDistance) + { + bestProp = prop; + bestDistance = distance; + } + } + } + else if (pointType == lineToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); + const Line line (rp1.resolve (scope), rp2.resolve (scope)); + bestProp = line.findNearestProportionalPositionTo (targetPoint); + } + + return bestProp; +} + +ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (Point targetPoint, Expression::Scope* scope, UndoManager* undoManager) +{ + ValueTree newTree; + const Identifier pointType (state.getType()); + + if (pointType == cubicToElement) + { + float bestProp = findProportionAlongLine (targetPoint, scope); + + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); + const Point points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) }; + + const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), + mid2 (points[1] + (points[2] - points[1]) * bestProp), + mid3 (points[2] + (points[3] - points[2]) * bestProp); + + const Point newCp1 (mid1 + (mid2 - mid1) * bestProp), + newCp2 (mid2 + (mid3 - mid2) * bestProp); + + const Point newCentre (newCp1 + (newCp2 - newCp1) * bestProp); + + setControlPoint (0, mid1, undoManager); + setControlPoint (1, newCp1, undoManager); + setControlPoint (2, newCentre, undoManager); + setModeOfEndPoint (roundedMode, undoManager); + + Element newElement (newTree = ValueTree (cubicToElement)); + newElement.setControlPoint (0, newCp2, nullptr); + newElement.setControlPoint (1, mid3, nullptr); + newElement.setControlPoint (2, rp4, nullptr); + + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); + } + else if (pointType == quadraticToElement) + { + float bestProp = findProportionAlongLine (targetPoint, scope); + + RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); + const Point points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) }; + + const Point mid1 (points[0] + (points[1] - points[0]) * bestProp), + mid2 (points[1] + (points[2] - points[1]) * bestProp); + + const Point newCentre (mid1 + (mid2 - mid1) * bestProp); + + setControlPoint (0, mid1, undoManager); + setControlPoint (1, newCentre, undoManager); + setModeOfEndPoint (roundedMode, undoManager); + + Element newElement (newTree = ValueTree (quadraticToElement)); + newElement.setControlPoint (0, mid2, nullptr); + newElement.setControlPoint (1, rp3, nullptr); + + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); + } + else if (pointType == lineToElement) + { + RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); + const Line line (rp1.resolve (scope), rp2.resolve (scope)); + const Point newPoint (line.findNearestPointTo (targetPoint)); + + setControlPoint (0, newPoint, undoManager); + + Element newElement (newTree = ValueTree (lineToElement)); + newElement.setControlPoint (0, rp2, nullptr); + + state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); + } + else if (pointType == closeSubPathElement) + { + } + + return newTree; +} + +void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager) +{ + state.getParent().removeChild (state, undoManager); +} + +//============================================================================== +void DrawablePath::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) +{ + ValueTreeWrapper v (tree); + setComponentID (v.getID()); + + refreshFillTypes (v, builder.getImageProvider()); + setStrokeType (v.getStrokeType()); + + RelativePointPath newRelativePath; + v.writeTo (newRelativePath); + setPath (newRelativePath); +} + +ValueTree DrawablePath::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); + + v.setID (getComponentID()); + writeTo (v, imageProvider, nullptr); + + if (relativePath != nullptr) + v.readFrom (*relativePath, nullptr); + else + v.readFrom (RelativePointPath (path), nullptr); + + return tree; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h new file mode 100644 index 0000000..bed3895 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h @@ -0,0 +1,145 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLEPATH_H_INCLUDED +#define JUCE_DRAWABLEPATH_H_INCLUDED + + +//============================================================================== +/** + A drawable object which renders a filled or outlined shape. + + For details on how to change the fill and stroke, see the DrawableShape class. + + @see Drawable, DrawableShape +*/ +class JUCE_API DrawablePath : public DrawableShape +{ +public: + //============================================================================== + /** Creates a DrawablePath. */ + DrawablePath(); + DrawablePath (const DrawablePath&); + + /** Destructor. */ + ~DrawablePath(); + + //============================================================================== + /** Changes the path that will be drawn. + @see setFillColour, setStrokeType + */ + void setPath (const Path& newPath); + + /** Sets the path using a RelativePointPath. + Calling this will set up a Component::Positioner to automatically update the path + if any of the points in the source path are dynamic. + */ + void setPath (const RelativePointPath& newPath); + + /** Returns the current path. */ + const Path& getPath() const; + + /** Returns the current path for the outline. */ + const Path& getStrokePath() const; + + //============================================================================== + /** @internal */ + Drawable* createCopy() const; + /** @internal */ + void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); + /** @internal */ + ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const; + /** @internal */ + static const Identifier valueTreeType; + + //============================================================================== + /** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */ + class ValueTreeWrapper : public DrawableShape::FillAndStrokeState + { + public: + ValueTreeWrapper (const ValueTree& state); + + bool usesNonZeroWinding() const; + void setUsesNonZeroWinding (bool b, UndoManager* undoManager); + + class Element + { + public: + explicit Element (const ValueTree& state); + ~Element(); + + const Identifier getType() const noexcept { return state.getType(); } + int getNumControlPoints() const noexcept; + + RelativePoint getControlPoint (int index) const; + Value getControlPointValue (int index, UndoManager*); + RelativePoint getStartPoint() const; + RelativePoint getEndPoint() const; + void setControlPoint (int index, const RelativePoint& point, UndoManager*); + float getLength (Expression::Scope*) const; + + ValueTreeWrapper getParent() const; + Element getPreviousElement() const; + + String getModeOfEndPoint() const; + void setModeOfEndPoint (const String& newMode, UndoManager*); + + void convertToLine (UndoManager*); + void convertToCubic (Expression::Scope*, UndoManager*); + void convertToPathBreak (UndoManager* undoManager); + ValueTree insertPoint (Point targetPoint, Expression::Scope*, UndoManager*); + void removePoint (UndoManager* undoManager); + float findProportionAlongLine (Point targetPoint, Expression::Scope*) const; + + static const Identifier mode, startSubPathElement, closeSubPathElement, + lineToElement, quadraticToElement, cubicToElement; + static const char* cornerMode; + static const char* roundedMode; + static const char* symmetricMode; + + ValueTree state; + }; + + ValueTree getPathState(); + + void readFrom (const RelativePointPath& path, UndoManager* undoManager); + void writeTo (RelativePointPath& path) const; + + static const Identifier nonZeroWinding, point1, point2, point3; + }; + +private: + //============================================================================== + ScopedPointer relativePath; + + class RelativePositioner; + friend class RelativePositioner; + void applyRelativePath (const RelativePointPath&, Expression::Scope*); + + DrawablePath& operator= (const DrawablePath&); + JUCE_LEAK_DETECTOR (DrawablePath) +}; + + +#endif // JUCE_DRAWABLEPATH_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp new file mode 100644 index 0000000..5a8c1c8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.cpp @@ -0,0 +1,182 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableRectangle::DrawableRectangle() +{ +} + +DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) + : DrawableShape (other), + bounds (other.bounds), + cornerSize (other.cornerSize) +{ +} + +DrawableRectangle::~DrawableRectangle() +{ +} + +Drawable* DrawableRectangle::createCopy() const +{ + return new DrawableRectangle (*this); +} + +//============================================================================== +void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds) +{ + if (bounds != newBounds) + { + bounds = newBounds; + rebuildPath(); + } +} + +void DrawableRectangle::setCornerSize (const RelativePoint& newSize) +{ + if (cornerSize != newSize) + { + cornerSize = newSize; + rebuildPath(); + } +} + +void DrawableRectangle::rebuildPath() +{ + if (bounds.isDynamic() || cornerSize.isDynamic()) + { + Drawable::Positioner* const p = new Drawable::Positioner (*this); + setPositioner (p); + p->apply(); + } + else + { + setPositioner (nullptr); + recalculateCoordinates (nullptr); + } +} + +bool DrawableRectangle::registerCoordinates (RelativeCoordinatePositionerBase& pos) +{ + bool ok = pos.addPoint (bounds.topLeft); + ok = pos.addPoint (bounds.topRight) && ok; + ok = pos.addPoint (bounds.bottomLeft) && ok; + return pos.addPoint (cornerSize) && ok; +} + +void DrawableRectangle::recalculateCoordinates (Expression::Scope* scope) +{ + Point points[3]; + bounds.resolveThreePoints (points, scope); + + const float cornerSizeX = (float) cornerSize.x.resolve (scope); + const float cornerSizeY = (float) cornerSize.y.resolve (scope); + + const float w = Line (points[0], points[1]).getLength(); + const float h = Line (points[0], points[2]).getLength(); + + Path newPath; + + if (cornerSizeX > 0 && cornerSizeY > 0) + newPath.addRoundedRectangle (0, 0, w, h, cornerSizeX, cornerSizeY); + else + newPath.addRectangle (0, 0, w, h); + + newPath.applyTransform (AffineTransform::fromTargetPoints (0, 0, points[0].x, points[0].y, + w, 0, points[1].x, points[1].y, + 0, h, points[2].x, points[2].y)); + + if (path != newPath) + { + path.swapWithPath (newPath); + pathChanged(); + } +} + +//============================================================================== +const Identifier DrawableRectangle::valueTreeType ("Rectangle"); +const Identifier DrawableRectangle::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableRectangle::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableRectangle::ValueTreeWrapper::bottomLeft ("bottomLeft"); +const Identifier DrawableRectangle::ValueTreeWrapper::cornerSize ("cornerSize"); + +//============================================================================== +DrawableRectangle::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : FillAndStrokeState (state_) +{ + jassert (state.hasType (valueTreeType)); +} + +RelativeParallelogram DrawableRectangle::ValueTreeWrapper::getRectangle() const +{ + return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), + state.getProperty (topRight, "100, 0"), + state.getProperty (bottomLeft, "0, 100")); +} + +void DrawableRectangle::ValueTreeWrapper::setRectangle (const RelativeParallelogram& newBounds, UndoManager* undoManager) +{ + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); +} + +void DrawableRectangle::ValueTreeWrapper::setCornerSize (const RelativePoint& newSize, UndoManager* undoManager) +{ + state.setProperty (cornerSize, newSize.toString(), undoManager); +} + +RelativePoint DrawableRectangle::ValueTreeWrapper::getCornerSize() const +{ + return RelativePoint (state [cornerSize]); +} + +Value DrawableRectangle::ValueTreeWrapper::getCornerSizeValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (cornerSize, undoManager); +} + +//============================================================================== +void DrawableRectangle::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) +{ + ValueTreeWrapper v (tree); + setComponentID (v.getID()); + + refreshFillTypes (v, builder.getImageProvider()); + setStrokeType (v.getStrokeType()); + setRectangle (v.getRectangle()); + setCornerSize (v.getCornerSize()); +} + +ValueTree DrawableRectangle::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); + + v.setID (getComponentID()); + writeTo (v, imageProvider, nullptr); + v.setRectangle (bounds, nullptr); + v.setCornerSize (cornerSize, nullptr); + + return tree; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.h new file mode 100644 index 0000000..9bae02e --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableRectangle.h @@ -0,0 +1,103 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLERECTANGLE_H_INCLUDED +#define JUCE_DRAWABLERECTANGLE_H_INCLUDED + + +//============================================================================== +/** + A Drawable object which draws a rectangle. + + For details on how to change the fill and stroke, see the DrawableShape class. + + @see Drawable, DrawableShape +*/ +class JUCE_API DrawableRectangle : public DrawableShape +{ +public: + //============================================================================== + DrawableRectangle(); + DrawableRectangle (const DrawableRectangle&); + + /** Destructor. */ + ~DrawableRectangle(); + + //============================================================================== + /** Sets the rectangle's bounds. */ + void setRectangle (const RelativeParallelogram& newBounds); + + /** Returns the rectangle's bounds. */ + const RelativeParallelogram& getRectangle() const noexcept { return bounds; } + + /** Returns the corner size to be used. */ + const RelativePoint& getCornerSize() const noexcept { return cornerSize; } + + /** Sets a new corner size for the rectangle */ + void setCornerSize (const RelativePoint& newSize); + + //============================================================================== + /** @internal */ + Drawable* createCopy() const; + /** @internal */ + void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); + /** @internal */ + ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const; + /** @internal */ + static const Identifier valueTreeType; + + //============================================================================== + /** Internally-used class for wrapping a DrawableRectangle's state into a ValueTree. */ + class ValueTreeWrapper : public DrawableShape::FillAndStrokeState + { + public: + ValueTreeWrapper (const ValueTree& state); + + RelativeParallelogram getRectangle() const; + void setRectangle (const RelativeParallelogram& newBounds, UndoManager*); + + void setCornerSize (const RelativePoint& cornerSize, UndoManager*); + RelativePoint getCornerSize() const; + Value getCornerSizeValue (UndoManager*); + + static const Identifier topLeft, topRight, bottomLeft, cornerSize; + }; + + +private: + friend class Drawable::Positioner; + + RelativeParallelogram bounds; + RelativePoint cornerSize; + + void rebuildPath(); + bool registerCoordinates (RelativeCoordinatePositionerBase&); + void recalculateCoordinates (Expression::Scope*); + + DrawableRectangle& operator= (const DrawableRectangle&); + JUCE_LEAK_DETECTOR (DrawableRectangle) +}; + + +#endif // JUCE_DRAWABLERECTANGLE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp new file mode 100644 index 0000000..5813493 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.cpp @@ -0,0 +1,472 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableShape::DrawableShape() + : strokeType (0.0f), + mainFill (Colours::black), + strokeFill (Colours::black) +{ +} + +DrawableShape::DrawableShape (const DrawableShape& other) + : Drawable (other), + strokeType (other.strokeType), + mainFill (other.mainFill), + strokeFill (other.strokeFill) +{ +} + +DrawableShape::~DrawableShape() +{ +} + +//============================================================================== +class DrawableShape::RelativePositioner : public RelativeCoordinatePositionerBase +{ +public: + RelativePositioner (DrawableShape& comp, const DrawableShape::RelativeFillType& f, bool isMain) + : RelativeCoordinatePositionerBase (comp), + owner (comp), + fill (f), + isMainFill (isMain) + { + } + + bool registerCoordinates() + { + bool ok = addPoint (fill.gradientPoint1); + ok = addPoint (fill.gradientPoint2) && ok; + return addPoint (fill.gradientPoint3) && ok; + } + + void applyToComponentBounds() + { + ComponentScope scope (owner); + if (isMainFill ? owner.mainFill.recalculateCoords (&scope) + : owner.strokeFill.recalculateCoords (&scope)) + owner.repaint(); + } + + void applyNewBounds (const Rectangle&) + { + jassertfalse; // drawables can't be resized directly! + } + +private: + DrawableShape& owner; + const DrawableShape::RelativeFillType fill; + const bool isMainFill; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner) +}; + +void DrawableShape::setFill (const FillType& newFill) +{ + setFill (RelativeFillType (newFill)); +} + +void DrawableShape::setStrokeFill (const FillType& newFill) +{ + setStrokeFill (RelativeFillType (newFill)); +} + +void DrawableShape::setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill, + ScopedPointer& pos) +{ + if (fill != newFill) + { + fill = newFill; + pos = nullptr; + + if (fill.isDynamic()) + { + pos = new RelativePositioner (*this, fill, true); + pos->apply(); + } + else + { + fill.recalculateCoords (nullptr); + } + + repaint(); + } +} + +void DrawableShape::setFill (const RelativeFillType& newFill) +{ + setFillInternal (mainFill, newFill, mainFillPositioner); +} + +void DrawableShape::setStrokeFill (const RelativeFillType& newFill) +{ + setFillInternal (strokeFill, newFill, strokeFillPositioner); +} + +void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType) +{ + if (strokeType != newStrokeType) + { + strokeType = newStrokeType; + strokeChanged(); + } +} + +void DrawableShape::setStrokeThickness (const float newThickness) +{ + setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle())); +} + +bool DrawableShape::isStrokeVisible() const noexcept +{ + return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.fill.isInvisible(); +} + +void DrawableShape::refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider* imageProvider) +{ + setFill (newState.getFill (FillAndStrokeState::fill, imageProvider)); + setStrokeFill (newState.getFill (FillAndStrokeState::stroke, imageProvider)); +} + +void DrawableShape::writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const +{ + state.setFill (FillAndStrokeState::fill, mainFill, imageProvider, undoManager); + state.setFill (FillAndStrokeState::stroke, strokeFill, imageProvider, undoManager); + state.setStrokeType (strokeType, undoManager); +} + +//============================================================================== +void DrawableShape::paint (Graphics& g) +{ + transformContextToCorrectOrigin (g); + + g.setFillType (mainFill.fill); + g.fillPath (path); + + if (isStrokeVisible()) + { + g.setFillType (strokeFill.fill); + g.fillPath (strokePath); + } +} + +void DrawableShape::pathChanged() +{ + strokeChanged(); +} + +void DrawableShape::strokeChanged() +{ + strokePath.clear(); + strokeType.createStrokedPath (strokePath, path, AffineTransform::identity, 4.0f); + + setBoundsToEnclose (getDrawableBounds()); + repaint(); +} + +Rectangle DrawableShape::getDrawableBounds() const +{ + if (isStrokeVisible()) + return strokePath.getBounds(); + + return path.getBounds(); +} + +bool DrawableShape::hitTest (int x, int y) +{ + bool allowsClicksOnThisComponent, allowsClicksOnChildComponents; + getInterceptsMouseClicks (allowsClicksOnThisComponent, allowsClicksOnChildComponents); + + if (! allowsClicksOnThisComponent) + return false; + + const float globalX = (float) (x - originRelativeToComponent.x); + const float globalY = (float) (y - originRelativeToComponent.y); + + return path.contains (globalX, globalY) + || (isStrokeVisible() && strokePath.contains (globalX, globalY)); +} + +//============================================================================== +DrawableShape::RelativeFillType::RelativeFillType() +{ +} + +DrawableShape::RelativeFillType::RelativeFillType (const FillType& fill_) + : fill (fill_) +{ + if (fill.isGradient()) + { + const ColourGradient& g = *fill.gradient; + + gradientPoint1 = g.point1.transformedBy (fill.transform); + gradientPoint2 = g.point2.transformedBy (fill.transform); + gradientPoint3 = Point (g.point1.x + g.point2.y - g.point1.y, + g.point1.y + g.point1.x - g.point2.x) + .transformedBy (fill.transform); + fill.transform = AffineTransform::identity; + } +} + +DrawableShape::RelativeFillType::RelativeFillType (const RelativeFillType& other) + : fill (other.fill), + gradientPoint1 (other.gradientPoint1), + gradientPoint2 (other.gradientPoint2), + gradientPoint3 (other.gradientPoint3) +{ +} + +DrawableShape::RelativeFillType& DrawableShape::RelativeFillType::operator= (const RelativeFillType& other) +{ + fill = other.fill; + gradientPoint1 = other.gradientPoint1; + gradientPoint2 = other.gradientPoint2; + gradientPoint3 = other.gradientPoint3; + return *this; +} + +bool DrawableShape::RelativeFillType::operator== (const RelativeFillType& other) const +{ + return fill == other.fill + && ((! fill.isGradient()) + || (gradientPoint1 == other.gradientPoint1 + && gradientPoint2 == other.gradientPoint2 + && gradientPoint3 == other.gradientPoint3)); +} + +bool DrawableShape::RelativeFillType::operator!= (const RelativeFillType& other) const +{ + return ! operator== (other); +} + +bool DrawableShape::RelativeFillType::recalculateCoords (Expression::Scope* scope) +{ + if (fill.isGradient()) + { + const Point g1 (gradientPoint1.resolve (scope)); + const Point g2 (gradientPoint2.resolve (scope)); + AffineTransform t; + + ColourGradient& g = *fill.gradient; + + if (g.isRadial) + { + const Point g3 (gradientPoint3.resolve (scope)); + const Point g3Source (g1.x + g2.y - g1.y, + g1.y + g1.x - g2.x); + + t = AffineTransform::fromTargetPoints (g1.x, g1.y, g1.x, g1.y, + g2.x, g2.y, g2.x, g2.y, + g3Source.x, g3Source.y, g3.x, g3.y); + } + + if (g.point1 != g1 || g.point2 != g2 || fill.transform != t) + { + g.point1 = g1; + g.point2 = g2; + fill.transform = t; + return true; + } + } + + return false; +} + +bool DrawableShape::RelativeFillType::isDynamic() const +{ + return gradientPoint1.isDynamic() || gradientPoint2.isDynamic() || gradientPoint3.isDynamic(); +} + +void DrawableShape::RelativeFillType::writeTo (ValueTree& v, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const +{ + if (fill.isColour()) + { + v.setProperty (FillAndStrokeState::type, "solid", undoManager); + v.setProperty (FillAndStrokeState::colour, String::toHexString ((int) fill.colour.getARGB()), undoManager); + } + else if (fill.isGradient()) + { + v.setProperty (FillAndStrokeState::type, "gradient", undoManager); + v.setProperty (FillAndStrokeState::gradientPoint1, gradientPoint1.toString(), undoManager); + v.setProperty (FillAndStrokeState::gradientPoint2, gradientPoint2.toString(), undoManager); + v.setProperty (FillAndStrokeState::gradientPoint3, gradientPoint3.toString(), undoManager); + + const ColourGradient& cg = *fill.gradient; + v.setProperty (FillAndStrokeState::radial, cg.isRadial, undoManager); + + String s; + for (int i = 0; i < cg.getNumColours(); ++i) + s << ' ' << cg.getColourPosition (i) + << ' ' << String::toHexString ((int) cg.getColour(i).getARGB()); + + v.setProperty (FillAndStrokeState::colours, s.trimStart(), undoManager); + } + else if (fill.isTiledImage()) + { + v.setProperty (FillAndStrokeState::type, "image", undoManager); + + if (imageProvider != nullptr) + v.setProperty (FillAndStrokeState::imageId, imageProvider->getIdentifierForImage (fill.image), undoManager); + + if (fill.getOpacity() < 1.0f) + v.setProperty (FillAndStrokeState::imageOpacity, fill.getOpacity(), undoManager); + else + v.removeProperty (FillAndStrokeState::imageOpacity, undoManager); + } + else + { + jassertfalse; + } +} + +bool DrawableShape::RelativeFillType::readFrom (const ValueTree& v, ComponentBuilder::ImageProvider* imageProvider) +{ + const String newType (v [FillAndStrokeState::type].toString()); + + if (newType == "solid") + { + const String colourString (v [FillAndStrokeState::colour].toString()); + fill.setColour (colourString.isEmpty() ? Colours::black + : Colour::fromString (colourString)); + return true; + } + else if (newType == "gradient") + { + ColourGradient g; + g.isRadial = v [FillAndStrokeState::radial]; + + StringArray colourSteps; + colourSteps.addTokens (v [FillAndStrokeState::colours].toString(), false); + + for (int i = 0; i < colourSteps.size() / 2; ++i) + g.addColour (colourSteps[i * 2].getDoubleValue(), + Colour::fromString (colourSteps[i * 2 + 1])); + + fill.setGradient (g); + + gradientPoint1 = RelativePoint (v [FillAndStrokeState::gradientPoint1]); + gradientPoint2 = RelativePoint (v [FillAndStrokeState::gradientPoint2]); + gradientPoint3 = RelativePoint (v [FillAndStrokeState::gradientPoint3]); + return true; + } + else if (newType == "image") + { + Image im; + if (imageProvider != nullptr) + im = imageProvider->getImageForIdentifier (v [FillAndStrokeState::imageId]); + + fill.setTiledImage (im, AffineTransform::identity); + fill.setOpacity ((float) v.getProperty (FillAndStrokeState::imageOpacity, 1.0f)); + return true; + } + + jassertfalse; + return false; +} + +//============================================================================== +const Identifier DrawableShape::FillAndStrokeState::type ("type"); +const Identifier DrawableShape::FillAndStrokeState::colour ("colour"); +const Identifier DrawableShape::FillAndStrokeState::colours ("colours"); +const Identifier DrawableShape::FillAndStrokeState::fill ("Fill"); +const Identifier DrawableShape::FillAndStrokeState::stroke ("Stroke"); +const Identifier DrawableShape::FillAndStrokeState::path ("Path"); +const Identifier DrawableShape::FillAndStrokeState::jointStyle ("jointStyle"); +const Identifier DrawableShape::FillAndStrokeState::capStyle ("capStyle"); +const Identifier DrawableShape::FillAndStrokeState::strokeWidth ("strokeWidth"); +const Identifier DrawableShape::FillAndStrokeState::gradientPoint1 ("point1"); +const Identifier DrawableShape::FillAndStrokeState::gradientPoint2 ("point2"); +const Identifier DrawableShape::FillAndStrokeState::gradientPoint3 ("point3"); +const Identifier DrawableShape::FillAndStrokeState::radial ("radial"); +const Identifier DrawableShape::FillAndStrokeState::imageId ("imageId"); +const Identifier DrawableShape::FillAndStrokeState::imageOpacity ("imageOpacity"); + +DrawableShape::FillAndStrokeState::FillAndStrokeState (const ValueTree& state_) + : Drawable::ValueTreeWrapperBase (state_) +{ +} + +DrawableShape::RelativeFillType DrawableShape::FillAndStrokeState::getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider* imageProvider) const +{ + DrawableShape::RelativeFillType f; + f.readFrom (state.getChildWithName (fillOrStrokeType), imageProvider); + return f; +} + +ValueTree DrawableShape::FillAndStrokeState::getFillState (const Identifier& fillOrStrokeType) +{ + ValueTree v (state.getChildWithName (fillOrStrokeType)); + if (v.isValid()) + return v; + + setFill (fillOrStrokeType, FillType (Colours::black), nullptr, nullptr); + return getFillState (fillOrStrokeType); +} + +void DrawableShape::FillAndStrokeState::setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill, + ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) +{ + ValueTree v (state.getOrCreateChildWithName (fillOrStrokeType, undoManager)); + newFill.writeTo (v, imageProvider, undoManager); +} + +PathStrokeType DrawableShape::FillAndStrokeState::getStrokeType() const +{ + const String jointStyleString (state [jointStyle].toString()); + const String capStyleString (state [capStyle].toString()); + + return PathStrokeType (state [strokeWidth], + jointStyleString == "curved" ? PathStrokeType::curved + : (jointStyleString == "bevel" ? PathStrokeType::beveled + : PathStrokeType::mitered), + capStyleString == "square" ? PathStrokeType::square + : (capStyleString == "round" ? PathStrokeType::rounded + : PathStrokeType::butt)); +} + +void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager) +{ + state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager); + state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered + ? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager); + state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt + ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); +} + +static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement) +{ + if (fill.fill.colour == original && fill.fill.isColour()) + { + fill = FillType (replacement); + return true; + } + + return false; +} + +bool DrawableShape::replaceColour (Colour original, Colour replacement) +{ + bool changed1 = replaceColourInFill (mainFill, original, replacement); + bool changed2 = replaceColourInFill (strokeFill, original, replacement); + return changed1 || changed2; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.h new file mode 100644 index 0000000..ed893f1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableShape.h @@ -0,0 +1,182 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLESHAPE_H_INCLUDED +#define JUCE_DRAWABLESHAPE_H_INCLUDED + + +//============================================================================== +/** + A base class implementing common functionality for Drawable classes which + consist of some kind of filled and stroked outline. + + @see DrawablePath, DrawableRectangle +*/ +class JUCE_API DrawableShape : public Drawable +{ +protected: + //============================================================================== + DrawableShape(); + DrawableShape (const DrawableShape&); + +public: + /** Destructor. */ + ~DrawableShape(); + + //============================================================================== + /** A FillType wrapper that allows the gradient coordinates to be implemented using RelativePoint. + */ + class RelativeFillType + { + public: + RelativeFillType(); + RelativeFillType (const FillType& fill); + RelativeFillType (const RelativeFillType&); + RelativeFillType& operator= (const RelativeFillType&); + + bool operator== (const RelativeFillType&) const; + bool operator!= (const RelativeFillType&) const; + + bool isDynamic() const; + bool recalculateCoords (Expression::Scope* scope); + + void writeTo (ValueTree& v, ComponentBuilder::ImageProvider*, UndoManager*) const; + bool readFrom (const ValueTree& v, ComponentBuilder::ImageProvider*); + + //============================================================================== + FillType fill; + RelativePoint gradientPoint1, gradientPoint2, gradientPoint3; + }; + + //============================================================================== + /** Sets a fill type for the path. + This colour is used to fill the path - if you don't want the path to be + filled (e.g. if you're just drawing an outline), set this to a transparent + colour. + + @see setPath, setStrokeFill + */ + void setFill (const FillType& newFill); + + /** Sets a fill type for the path. + This colour is used to fill the path - if you don't want the path to be + filled (e.g. if you're just drawing an outline), set this to a transparent + colour. + + @see setPath, setStrokeFill + */ + void setFill (const RelativeFillType& newFill); + + /** Returns the current fill type. + @see setFill + */ + const RelativeFillType& getFill() const noexcept { return mainFill; } + + /** Sets the fill type with which the outline will be drawn. + @see setFill + */ + void setStrokeFill (const FillType& newStrokeFill); + + /** Sets the fill type with which the outline will be drawn. + @see setFill + */ + void setStrokeFill (const RelativeFillType& newStrokeFill); + + /** Returns the current stroke fill. + @see setStrokeFill + */ + const RelativeFillType& getStrokeFill() const noexcept { return strokeFill; } + + /** Changes the properties of the outline that will be drawn around the path. + If the stroke has 0 thickness, no stroke will be drawn. + @see setStrokeThickness, setStrokeColour + */ + void setStrokeType (const PathStrokeType& newStrokeType); + + /** Changes the stroke thickness. + This is a shortcut for calling setStrokeType. + */ + void setStrokeThickness (float newThickness); + + /** Returns the current outline style. */ + const PathStrokeType& getStrokeType() const noexcept { return strokeType; } + + //============================================================================== + /** @internal */ + class FillAndStrokeState : public Drawable::ValueTreeWrapperBase + { + public: + FillAndStrokeState (const ValueTree& state); + + ValueTree getFillState (const Identifier& fillOrStrokeType); + RelativeFillType getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider*) const; + void setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill, + ComponentBuilder::ImageProvider*, UndoManager*); + + PathStrokeType getStrokeType() const; + void setStrokeType (const PathStrokeType& newStrokeType, UndoManager*); + + static const Identifier type, colour, colours, fill, stroke, path, jointStyle, capStyle, strokeWidth, + gradientPoint1, gradientPoint2, gradientPoint3, radial, imageId, imageOpacity; + }; + + /** @internal */ + Rectangle getDrawableBounds() const override; + /** @internal */ + void paint (Graphics&) override; + /** @internal */ + bool hitTest (int x, int y) override; + /** @internal */ + bool replaceColour (Colour originalColour, Colour replacementColour) override; + +protected: + //============================================================================== + /** Called when the cached path should be updated. */ + void pathChanged(); + /** Called when the cached stroke should be updated. */ + void strokeChanged(); + /** True if there's a stroke with a non-zero thickness and non-transparent colour. */ + bool isStrokeVisible() const noexcept; + /** Updates the details from a FillAndStrokeState object, returning true if something changed. */ + void refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider*); + /** Writes the stroke and fill details to a FillAndStrokeState object. */ + void writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider*, UndoManager*) const; + + //============================================================================== + PathStrokeType strokeType; + Path path, strokePath; + +private: + class RelativePositioner; + RelativeFillType mainFill, strokeFill; + ScopedPointer mainFillPositioner, strokeFillPositioner; + + void setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill, + ScopedPointer& positioner); + + DrawableShape& operator= (const DrawableShape&); +}; + + +#endif // JUCE_DRAWABLESHAPE_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp new file mode 100644 index 0000000..e9c9802 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp @@ -0,0 +1,334 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DrawableText::DrawableText() + : colour (Colours::black), + justification (Justification::centredLeft) +{ + setBoundingBox (RelativeParallelogram (RelativePoint (0.0f, 0.0f), + RelativePoint (50.0f, 0.0f), + RelativePoint (0.0f, 20.0f))); + setFont (Font (15.0f), true); +} + +DrawableText::DrawableText (const DrawableText& other) + : Drawable (other), + bounds (other.bounds), + fontHeight (other.fontHeight), + fontHScale (other.fontHScale), + font (other.font), + text (other.text), + colour (other.colour), + justification (other.justification) +{ + refreshBounds(); +} + +DrawableText::~DrawableText() +{ +} + +//============================================================================== +void DrawableText::setText (const String& newText) +{ + if (text != newText) + { + text = newText; + refreshBounds(); + } +} + +void DrawableText::setColour (Colour newColour) +{ + if (colour != newColour) + { + colour = newColour; + repaint(); + } +} + +void DrawableText::setFont (const Font& newFont, bool applySizeAndScale) +{ + if (font != newFont) + { + font = newFont; + + if (applySizeAndScale) + { + fontHeight = font.getHeight(); + fontHScale = font.getHorizontalScale(); + } + + refreshBounds(); + } +} + +void DrawableText::setJustification (Justification newJustification) +{ + justification = newJustification; + repaint(); +} + +void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) +{ + if (bounds != newBounds) + { + bounds = newBounds; + refreshBounds(); + } +} + +void DrawableText::setFontHeight (const RelativeCoordinate& newHeight) +{ + if (fontHeight != newHeight) + { + fontHeight = newHeight; + refreshBounds(); + } +} + +void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale) +{ + if (fontHScale != newScale) + { + fontHScale = newScale; + refreshBounds(); + } +} + +void DrawableText::refreshBounds() +{ + if (bounds.isDynamic() || fontHeight.isDynamic() || fontHScale.isDynamic()) + { + Drawable::Positioner* const p = new Drawable::Positioner (*this); + setPositioner (p); + p->apply(); + } + else + { + setPositioner (0); + recalculateCoordinates (0); + } +} + +bool DrawableText::registerCoordinates (RelativeCoordinatePositionerBase& pos) +{ + bool ok = pos.addPoint (bounds.topLeft); + ok = pos.addPoint (bounds.topRight) && ok; + ok = pos.addPoint (bounds.bottomLeft) && ok; + ok = pos.addCoordinate (fontHeight) && ok; + return pos.addCoordinate (fontHScale) && ok; +} + +void DrawableText::recalculateCoordinates (Expression::Scope* scope) +{ + bounds.resolveThreePoints (resolvedPoints, scope); + + const float w = Line (resolvedPoints[0], resolvedPoints[1]).getLength(); + const float h = Line (resolvedPoints[0], resolvedPoints[2]).getLength(); + + const float height = jlimit (0.01f, jmax (0.01f, h), (float) fontHeight.resolve (scope)); + const float hscale = jlimit (0.01f, jmax (0.01f, w), (float) fontHScale.resolve (scope)); + + scaledFont = font; + scaledFont.setHeight (height); + scaledFont.setHorizontalScale (hscale); + + setBoundsToEnclose (getDrawableBounds()); + repaint(); +} + +//============================================================================== +void DrawableText::paint (Graphics& g) +{ + transformContextToCorrectOrigin (g); + + const float w = Line (resolvedPoints[0], resolvedPoints[1]).getLength(); + const float h = Line (resolvedPoints[0], resolvedPoints[2]).getLength(); + + g.addTransform (AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y, + w, 0, resolvedPoints[1].x, resolvedPoints[1].y, + 0, h, resolvedPoints[2].x, resolvedPoints[2].y)); + g.setFont (scaledFont); + g.setColour (colour); + + g.drawFittedText (text, Rectangle (w, h).getSmallestIntegerContainer(), justification, 0x100000); +} + +Rectangle DrawableText::getDrawableBounds() const +{ + return RelativeParallelogram::getBoundingBox (resolvedPoints); +} + +Drawable* DrawableText::createCopy() const +{ + return new DrawableText (*this); +} + +//============================================================================== +const Identifier DrawableText::valueTreeType ("Text"); + +const Identifier DrawableText::ValueTreeWrapper::text ("text"); +const Identifier DrawableText::ValueTreeWrapper::colour ("colour"); +const Identifier DrawableText::ValueTreeWrapper::font ("font"); +const Identifier DrawableText::ValueTreeWrapper::justification ("justification"); +const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft"); +const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight"); +const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft"); +const Identifier DrawableText::ValueTreeWrapper::fontHeight ("fontHeight"); +const Identifier DrawableText::ValueTreeWrapper::fontHScale ("fontHScale"); + +//============================================================================== +DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) + : ValueTreeWrapperBase (state_) +{ + jassert (state.hasType (valueTreeType)); +} + +String DrawableText::ValueTreeWrapper::getText() const +{ + return state [text].toString(); +} + +void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) +{ + state.setProperty (text, newText, undoManager); +} + +Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (text, undoManager); +} + +Colour DrawableText::ValueTreeWrapper::getColour() const +{ + return Colour::fromString (state [colour].toString()); +} + +void DrawableText::ValueTreeWrapper::setColour (Colour newColour, UndoManager* undoManager) +{ + state.setProperty (colour, newColour.toString(), undoManager); +} + +Justification DrawableText::ValueTreeWrapper::getJustification() const +{ + return Justification ((int) state [justification]); +} + +void DrawableText::ValueTreeWrapper::setJustification (Justification newJustification, UndoManager* undoManager) +{ + state.setProperty (justification, newJustification.getFlags(), undoManager); +} + +Font DrawableText::ValueTreeWrapper::getFont() const +{ + return Font::fromString (state [font]); +} + +void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager) +{ + state.setProperty (font, newFont.toString(), undoManager); +} + +Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager) +{ + return state.getPropertyAsValue (font, undoManager); +} + +RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const +{ + return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString()); +} + +void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) +{ + state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); + state.setProperty (topRight, newBounds.topRight.toString(), undoManager); + state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); +} + +RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHeight() const +{ + return state [fontHeight].toString(); +} + +void DrawableText::ValueTreeWrapper::setFontHeight (const RelativeCoordinate& coord, UndoManager* undoManager) +{ + state.setProperty (fontHeight, coord.toString(), undoManager); +} + +RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHorizontalScale() const +{ + return state [fontHScale].toString(); +} + +void DrawableText::ValueTreeWrapper::setFontHorizontalScale (const RelativeCoordinate& coord, UndoManager* undoManager) +{ + state.setProperty (fontHScale, coord.toString(), undoManager); +} + +//============================================================================== +void DrawableText::refreshFromValueTree (const ValueTree& tree, ComponentBuilder&) +{ + ValueTreeWrapper v (tree); + setComponentID (v.getID()); + + const RelativeParallelogram newBounds (v.getBoundingBox()); + const RelativeCoordinate newFontHeight (v.getFontHeight()); + const RelativeCoordinate newFontHScale (v.getFontHorizontalScale()); + const Colour newColour (v.getColour()); + const Justification newJustification (v.getJustification()); + const String newText (v.getText()); + const Font newFont (v.getFont()); + + if (text != newText || font != newFont || justification != newJustification + || colour != newColour || bounds != newBounds + || newFontHeight != fontHeight || newFontHScale != fontHScale) + { + setBoundingBox (newBounds); + setFontHeight (newFontHeight); + setFontHorizontalScale (newFontHScale); + setColour (newColour); + setFont (newFont, false); + setJustification (newJustification); + setText (newText); + } +} + +ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const +{ + ValueTree tree (valueTreeType); + ValueTreeWrapper v (tree); + + v.setID (getComponentID()); + v.setText (text, nullptr); + v.setFont (font, nullptr); + v.setJustification (justification, nullptr); + v.setColour (colour, nullptr); + v.setBoundingBox (bounds, nullptr); + v.setFontHeight (fontHeight, nullptr); + v.setFontHorizontalScale (fontHScale, nullptr); + + return tree; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h new file mode 100644 index 0000000..ac44fb4 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h @@ -0,0 +1,155 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DRAWABLETEXT_H_INCLUDED +#define JUCE_DRAWABLETEXT_H_INCLUDED + + +//============================================================================== +/** + A drawable object which renders a line of text. + + @see Drawable +*/ +class JUCE_API DrawableText : public Drawable +{ +public: + //============================================================================== + /** Creates a DrawableText object. */ + DrawableText(); + DrawableText (const DrawableText&); + + /** Destructor. */ + ~DrawableText(); + + //============================================================================== + /** Sets the text to display.*/ + void setText (const String& newText); + + /** Returns the currently displayed text */ + const String& getText() const noexcept { return text;} + + /** Sets the colour of the text. */ + void setColour (Colour newColour); + + /** Returns the current text colour. */ + Colour getColour() const noexcept { return colour; } + + /** Sets the font to use. + Note that the font height and horizontal scale are set as RelativeCoordinates using + setFontHeight and setFontHorizontalScale. If applySizeAndScale is true, then these height + and scale values will be changed to match the dimensions of the font supplied; + if it is false, then the new font object's height and scale are ignored. + */ + void setFont (const Font& newFont, bool applySizeAndScale); + + /** Returns the current font. */ + const Font& getFont() const noexcept { return font; } + + /** Changes the justification of the text within the bounding box. */ + void setJustification (Justification newJustification); + + /** Returns the current justification. */ + Justification getJustification() const noexcept { return justification; } + + /** Returns the parallelogram that defines the text bounding box. */ + const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } + + /** Sets the bounding box that contains the text. */ + void setBoundingBox (const RelativeParallelogram& newBounds); + + const RelativeCoordinate& getFontHeight() const { return fontHeight; } + void setFontHeight (const RelativeCoordinate& newHeight); + + const RelativeCoordinate& getFontHorizontalScale() const { return fontHScale; } + void setFontHorizontalScale (const RelativeCoordinate& newScale); + + //============================================================================== + /** @internal */ + void paint (Graphics&) override; + /** @internal */ + Drawable* createCopy() const override; + /** @internal */ + void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); + /** @internal */ + ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const override; + /** @internal */ + static const Identifier valueTreeType; + /** @internal */ + Rectangle getDrawableBounds() const override; + + //============================================================================== + /** Internally-used class for wrapping a DrawableText's state into a ValueTree. */ + class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase + { + public: + ValueTreeWrapper (const ValueTree& state); + + String getText() const; + void setText (const String& newText, UndoManager* undoManager); + Value getTextValue (UndoManager* undoManager); + + Colour getColour() const; + void setColour (Colour newColour, UndoManager* undoManager); + + Justification getJustification() const; + void setJustification (Justification newJustification, UndoManager* undoManager); + + Font getFont() const; + void setFont (const Font& newFont, UndoManager* undoManager); + Value getFontValue (UndoManager* undoManager); + + RelativeParallelogram getBoundingBox() const; + void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); + + RelativeCoordinate getFontHeight() const; + void setFontHeight (const RelativeCoordinate& newHeight, UndoManager* undoManager); + + RelativeCoordinate getFontHorizontalScale() const; + void setFontHorizontalScale (const RelativeCoordinate& newScale, UndoManager* undoManager); + + static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontHeight, fontHScale; + }; + +private: + //============================================================================== + RelativeParallelogram bounds; + RelativeCoordinate fontHeight, fontHScale; + Point resolvedPoints[3]; + Font font, scaledFont; + String text; + Colour colour; + Justification justification; + + friend class Drawable::Positioner; + bool registerCoordinates (RelativeCoordinatePositionerBase&); + void recalculateCoordinates (Expression::Scope*); + void refreshBounds(); + + DrawableText& operator= (const DrawableText&); + JUCE_LEAK_DETECTOR (DrawableText) +}; + + +#endif // JUCE_DRAWABLETEXT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp new file mode 100644 index 0000000..35253a2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -0,0 +1,1315 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +class SVGState +{ +public: + //============================================================================== + explicit SVGState (const XmlElement* const topLevel) + : topLevelXml (topLevel, nullptr), + elementX (0), elementY (0), + width (512), height (512), + viewBoxW (0), viewBoxH (0) + { + } + + struct XmlPath + { + XmlPath (const XmlElement* e, const XmlPath* p) noexcept : xml (e), parent (p) {} + + const XmlElement& operator*() const noexcept { jassert (xml != nullptr); return *xml; } + const XmlElement* operator->() const noexcept { return xml; } + XmlPath getChild (const XmlElement* e) const noexcept { return XmlPath (e, this); } + + const XmlElement* xml; + const XmlPath* parent; + }; + + //============================================================================== + Drawable* parseSVGElement (const XmlPath& xml) + { + if (! xml->hasTagNameIgnoringNamespace ("svg")) + return nullptr; + + DrawableComposite* const drawable = new DrawableComposite(); + + setDrawableID (*drawable, xml); + + SVGState newState (*this); + + if (xml->hasAttribute ("transform")) + newState.addTransform (xml); + + newState.elementX = getCoordLength (xml->getStringAttribute ("x", String (newState.elementX)), viewBoxW); + newState.elementY = getCoordLength (xml->getStringAttribute ("y", String (newState.elementY)), viewBoxH); + newState.width = getCoordLength (xml->getStringAttribute ("width", String (newState.width)), viewBoxW); + newState.height = getCoordLength (xml->getStringAttribute ("height", String (newState.height)), viewBoxH); + + if (newState.width <= 0) newState.width = 100; + if (newState.height <= 0) newState.height = 100; + + Point viewboxXY; + + if (xml->hasAttribute ("viewBox")) + { + const String viewBoxAtt (xml->getStringAttribute ("viewBox")); + String::CharPointerType viewParams (viewBoxAtt.getCharPointer()); + Point vwh; + + if (parseCoords (viewParams, viewboxXY, true) + && parseCoords (viewParams, vwh, true) + && vwh.x > 0 + && vwh.y > 0) + { + newState.viewBoxW = vwh.x; + newState.viewBoxH = vwh.y; + + const int placementFlags = parsePlacementFlags (xml->getStringAttribute ("preserveAspectRatio").trim()); + + if (placementFlags != 0) + newState.transform = RectanglePlacement (placementFlags) + .getTransformToFit (Rectangle (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y), + Rectangle (newState.width, newState.height)) + .followedBy (newState.transform); + } + } + else + { + if (viewBoxW == 0) newState.viewBoxW = newState.width; + if (viewBoxH == 0) newState.viewBoxH = newState.height; + } + + newState.parseSubElements (xml, *drawable); + + drawable->setContentArea (RelativeRectangle (RelativeCoordinate (viewboxXY.x), + RelativeCoordinate (viewboxXY.x + newState.viewBoxW), + RelativeCoordinate (viewboxXY.y), + RelativeCoordinate (viewboxXY.y + newState.viewBoxH))); + drawable->resetBoundingBoxToContentArea(); + + return drawable; + } + + //============================================================================== + void parsePathString (Path& path, const String& pathString) const + { + String::CharPointerType d (pathString.getCharPointer().findEndOfWhitespace()); + + Point subpathStart, last, last2, p1, p2, p3; + juce_wchar lastCommandChar = 0; + bool isRelative = true; + bool carryOn = true; + + const CharPointer_ASCII validCommandChars ("MmLlHhVvCcSsQqTtAaZz"); + + while (! d.isEmpty()) + { + if (validCommandChars.indexOf (*d) >= 0) + { + lastCommandChar = d.getAndAdvance(); + isRelative = (lastCommandChar >= 'a' && lastCommandChar <= 'z'); + } + + switch (lastCommandChar) + { + case 'M': + case 'm': + case 'L': + case 'l': + if (parseCoordsOrSkip (d, p1, false)) + { + if (isRelative) + p1 += last; + + if (lastCommandChar == 'M' || lastCommandChar == 'm') + { + subpathStart = p1; + path.startNewSubPath (p1); + lastCommandChar = 'l'; + } + else + path.lineTo (p1); + + last2 = last; + last = p1; + } + break; + + case 'H': + case 'h': + if (parseCoord (d, p1.x, false, true)) + { + if (isRelative) + p1.x += last.x; + + path.lineTo (p1.x, last.y); + + last2.x = last.x; + last.x = p1.x; + } + else + { + ++d; + } + break; + + case 'V': + case 'v': + if (parseCoord (d, p1.y, false, false)) + { + if (isRelative) + p1.y += last.y; + + path.lineTo (last.x, p1.y); + + last2.y = last.y; + last.y = p1.y; + } + else + { + ++d; + } + break; + + case 'C': + case 'c': + if (parseCoordsOrSkip (d, p1, false) + && parseCoordsOrSkip (d, p2, false) + && parseCoordsOrSkip (d, p3, false)) + { + if (isRelative) + { + p1 += last; + p2 += last; + p3 += last; + } + + path.cubicTo (p1, p2, p3); + + last2 = p2; + last = p3; + } + break; + + case 'S': + case 's': + if (parseCoordsOrSkip (d, p1, false) + && parseCoordsOrSkip (d, p3, false)) + { + if (isRelative) + { + p1 += last; + p3 += last; + } + + p2 = last + (last - last2); + path.cubicTo (p2, p1, p3); + + last2 = p1; + last = p3; + } + break; + + case 'Q': + case 'q': + if (parseCoordsOrSkip (d, p1, false) + && parseCoordsOrSkip (d, p2, false)) + { + if (isRelative) + { + p1 += last; + p2 += last; + } + + path.quadraticTo (p1, p2); + + last2 = p1; + last = p2; + } + break; + + case 'T': + case 't': + if (parseCoordsOrSkip (d, p1, false)) + { + if (isRelative) + p1 += last; + + p2 = last + (last - last2); + path.quadraticTo (p2, p1); + + last2 = p2; + last = p1; + } + break; + + case 'A': + case 'a': + if (parseCoordsOrSkip (d, p1, false)) + { + String num; + + if (parseNextNumber (d, num, false)) + { + const float angle = num.getFloatValue() * (180.0f / float_Pi); + + if (parseNextNumber (d, num, false)) + { + const bool largeArc = num.getIntValue() != 0; + + if (parseNextNumber (d, num, false)) + { + const bool sweep = num.getIntValue() != 0; + + if (parseCoordsOrSkip (d, p2, false)) + { + if (isRelative) + p2 += last; + + if (last != p2) + { + double centreX, centreY, startAngle, deltaAngle; + double rx = p1.x, ry = p1.y; + + endpointToCentreParameters (last.x, last.y, p2.x, p2.y, + angle, largeArc, sweep, + rx, ry, centreX, centreY, + startAngle, deltaAngle); + + path.addCentredArc ((float) centreX, (float) centreY, + (float) rx, (float) ry, + angle, (float) startAngle, (float) (startAngle + deltaAngle), + false); + + path.lineTo (p2); + } + + last2 = last; + last = p2; + } + } + } + } + } + + break; + + case 'Z': + case 'z': + path.closeSubPath(); + last = last2 = subpathStart; + d = d.findEndOfWhitespace(); + lastCommandChar = 'M'; + break; + + default: + carryOn = false; + break; + } + + if (! carryOn) + break; + } + + // paths that finish back at their start position often seem to be + // left without a 'z', so need to be closed explicitly.. + if (path.getCurrentPosition() == subpathStart) + path.closeSubPath(); + } + +private: + //============================================================================== + const XmlPath topLevelXml; + float elementX, elementY, width, height, viewBoxW, viewBoxH; + AffineTransform transform; + String cssStyleText; + + static void setDrawableID (Drawable& d, const XmlPath& xml) + { + String compID (xml->getStringAttribute ("id")); + d.setName (compID); + d.setComponentID (compID); + } + + //============================================================================== + void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable) + { + forEachXmlChildElement (*xml, e) + parentDrawable.addAndMakeVisible (parseSubElement (xml.getChild (e))); + } + + Drawable* parseSubElement (const XmlPath& xml) + { + const String tag (xml->getTagNameWithoutNamespace()); + + if (tag == "g") return parseGroupElement (xml); + if (tag == "svg") return parseSVGElement (xml); + if (tag == "path") return parsePath (xml); + if (tag == "rect") return parseRect (xml); + if (tag == "circle") return parseCircle (xml); + if (tag == "ellipse") return parseEllipse (xml); + if (tag == "line") return parseLine (xml); + if (tag == "polyline") return parsePolygon (xml, true); + if (tag == "polygon") return parsePolygon (xml, false); + if (tag == "text") return parseText (xml); + if (tag == "switch") return parseSwitch (xml); + if (tag == "style") parseCSSStyle (xml); + + return nullptr; + } + + DrawableComposite* parseSwitch (const XmlPath& xml) + { + if (const XmlElement* const group = xml->getChildByName ("g")) + return parseGroupElement (xml.getChild (group)); + + return nullptr; + } + + DrawableComposite* parseGroupElement (const XmlPath& xml) + { + DrawableComposite* const drawable = new DrawableComposite(); + + setDrawableID (*drawable, xml); + + if (xml->hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); + + newState.parseSubElements (xml, *drawable); + } + else + { + parseSubElements (xml, *drawable); + } + + drawable->resetContentAreaAndBoundingBoxToFitChildren(); + return drawable; + } + + //============================================================================== + Drawable* parsePath (const XmlPath& xml) const + { + Path path; + parsePathString (path, xml->getStringAttribute ("d")); + + if (getStyleAttribute (xml, "fill-rule").trim().equalsIgnoreCase ("evenodd")) + path.setUsingNonZeroWinding (false); + + return parseShape (xml, path); + } + + Drawable* parseRect (const XmlPath& xml) const + { + Path rect; + + const bool hasRX = xml->hasAttribute ("rx"); + const bool hasRY = xml->hasAttribute ("ry"); + + if (hasRX || hasRY) + { + float rx = getCoordLength (xml, "rx", viewBoxW); + float ry = getCoordLength (xml, "ry", viewBoxH); + + if (! hasRX) + rx = ry; + else if (! hasRY) + ry = rx; + + rect.addRoundedRectangle (getCoordLength (xml, "x", viewBoxW), + getCoordLength (xml, "y", viewBoxH), + getCoordLength (xml, "width", viewBoxW), + getCoordLength (xml, "height", viewBoxH), + rx, ry); + } + else + { + rect.addRectangle (getCoordLength (xml, "x", viewBoxW), + getCoordLength (xml, "y", viewBoxH), + getCoordLength (xml, "width", viewBoxW), + getCoordLength (xml, "height", viewBoxH)); + } + + return parseShape (xml, rect); + } + + Drawable* parseCircle (const XmlPath& xml) const + { + Path circle; + + const float cx = getCoordLength (xml, "cx", viewBoxW); + const float cy = getCoordLength (xml, "cy", viewBoxH); + const float radius = getCoordLength (xml, "r", viewBoxW); + + circle.addEllipse (cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); + + return parseShape (xml, circle); + } + + Drawable* parseEllipse (const XmlPath& xml) const + { + Path ellipse; + + const float cx = getCoordLength (xml, "cx", viewBoxW); + const float cy = getCoordLength (xml, "cy", viewBoxH); + const float radiusX = getCoordLength (xml, "rx", viewBoxW); + const float radiusY = getCoordLength (xml, "ry", viewBoxH); + + ellipse.addEllipse (cx - radiusX, cy - radiusY, radiusX * 2.0f, radiusY * 2.0f); + + return parseShape (xml, ellipse); + } + + Drawable* parseLine (const XmlPath& xml) const + { + Path line; + + const float x1 = getCoordLength (xml, "x1", viewBoxW); + const float y1 = getCoordLength (xml, "y1", viewBoxH); + const float x2 = getCoordLength (xml, "x2", viewBoxW); + const float y2 = getCoordLength (xml, "y2", viewBoxH); + + line.startNewSubPath (x1, y1); + line.lineTo (x2, y2); + + return parseShape (xml, line); + } + + Drawable* parsePolygon (const XmlPath& xml, const bool isPolyline) const + { + const String pointsAtt (xml->getStringAttribute ("points")); + String::CharPointerType points (pointsAtt.getCharPointer()); + Path path; + Point p; + + if (parseCoords (points, p, true)) + { + Point first (p), last; + + path.startNewSubPath (first); + + while (parseCoords (points, p, true)) + { + last = p; + path.lineTo (p); + } + + if ((! isPolyline) || first == last) + path.closeSubPath(); + } + + return parseShape (xml, path); + } + + //============================================================================== + Drawable* parseShape (const XmlPath& xml, Path& path, + const bool shouldParseTransform = true) const + { + if (shouldParseTransform && xml->hasAttribute ("transform")) + { + SVGState newState (*this); + newState.addTransform (xml); + + return newState.parseShape (xml, path, false); + } + + DrawablePath* dp = new DrawablePath(); + setDrawableID (*dp, xml); + dp->setFill (Colours::transparentBlack); + + path.applyTransform (transform); + dp->setPath (path); + + dp->setFill (getPathFillType (path, + getStyleAttribute (xml, "fill"), + getStyleAttribute (xml, "fill-opacity"), + getStyleAttribute (xml, "opacity"), + pathContainsClosedSubPath (path) ? Colours::black + : Colours::transparentBlack)); + + const String strokeType (getStyleAttribute (xml, "stroke")); + + if (strokeType.isNotEmpty() && ! strokeType.equalsIgnoreCase ("none")) + { + dp->setStrokeFill (getPathFillType (path, strokeType, + getStyleAttribute (xml, "stroke-opacity"), + getStyleAttribute (xml, "opacity"), + Colours::transparentBlack)); + + dp->setStrokeType (getStrokeFor (xml)); + } + + return dp; + } + + static bool pathContainsClosedSubPath (const Path& path) noexcept + { + for (Path::Iterator iter (path); iter.next();) + if (iter.elementType == Path::Iterator::closePath) + return true; + + return false; + } + + struct SetGradientStopsOp + { + const SVGState* state; + ColourGradient* gradient; + + void operator() (const XmlPath& xml) + { + state->addGradientStopsIn (*gradient, xml); + } + }; + + void addGradientStopsIn (ColourGradient& cg, const XmlPath& fillXml) const + { + if (fillXml.xml != nullptr) + { + forEachXmlChildElementWithTagName (*fillXml, e, "stop") + { + int index = 0; + Colour col (parseColour (getStyleAttribute (fillXml.getChild (e), "stop-color"), index, Colours::black)); + + const String opacity (getStyleAttribute (fillXml.getChild (e), "stop-opacity", "1")); + col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue())); + + double offset = e->getDoubleAttribute ("offset"); + + if (e->getStringAttribute ("offset").containsChar ('%')) + offset *= 0.01; + + cg.addColour (jlimit (0.0, 1.0, offset), col); + } + } + } + + FillType getGradientFillType (const XmlPath& fillXml, + const Path& path, + const float opacity) const + { + ColourGradient gradient; + + { + const String id (fillXml->getStringAttribute ("xlink:href")); + + if (id.startsWithChar ('#')) + { + SetGradientStopsOp op = { this, &gradient, }; + findElementForId (topLevelXml, id.substring (1), op); + } + } + + addGradientStopsIn (gradient, fillXml); + + if (gradient.getNumColours() > 0) + { + gradient.addColour (0.0, gradient.getColour (0)); + gradient.addColour (1.0, gradient.getColour (gradient.getNumColours() - 1)); + } + else + { + gradient.addColour (0.0, Colours::black); + gradient.addColour (1.0, Colours::black); + } + + if (opacity < 1.0f) + gradient.multiplyOpacity (opacity); + + jassert (gradient.getNumColours() > 0); + + gradient.isRadial = fillXml->hasTagNameIgnoringNamespace ("radialGradient"); + + float gradientWidth = viewBoxW; + float gradientHeight = viewBoxH; + float dx = 0.0f; + float dy = 0.0f; + + const bool userSpace = fillXml->getStringAttribute ("gradientUnits").equalsIgnoreCase ("userSpaceOnUse"); + + if (! userSpace) + { + const Rectangle bounds (path.getBounds()); + dx = bounds.getX(); + dy = bounds.getY(); + gradientWidth = bounds.getWidth(); + gradientHeight = bounds.getHeight(); + } + + if (gradient.isRadial) + { + if (userSpace) + gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("cx", "50%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("cy", "50%"), gradientHeight)); + else + gradient.point1.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("cx", "50%"), 1.0f), + dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("cy", "50%"), 1.0f)); + + const float radius = getCoordLength (fillXml->getStringAttribute ("r", "50%"), gradientWidth); + gradient.point2 = gradient.point1 + Point (radius, 0.0f); + + //xxx (the fx, fy focal point isn't handled properly here..) + } + else + { + if (userSpace) + { + gradient.point1.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x1", "0%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("y1", "0%"), gradientHeight)); + + gradient.point2.setXY (dx + getCoordLength (fillXml->getStringAttribute ("x2", "100%"), gradientWidth), + dy + getCoordLength (fillXml->getStringAttribute ("y2", "0%"), gradientHeight)); + } + else + { + gradient.point1.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("x1", "0%"), 1.0f), + dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("y1", "0%"), 1.0f)); + + gradient.point2.setXY (dx + gradientWidth * getCoordLength (fillXml->getStringAttribute ("x2", "100%"), 1.0f), + dy + gradientHeight * getCoordLength (fillXml->getStringAttribute ("y2", "0%"), 1.0f)); + } + + if (gradient.point1 == gradient.point2) + return Colour (gradient.getColour (gradient.getNumColours() - 1)); + } + + FillType type (gradient); + + const AffineTransform gradientTransform (parseTransform (fillXml->getStringAttribute ("gradientTransform")) + .followedBy (transform)); + + if (gradient.isRadial) + { + type.transform = gradientTransform; + } + else + { + // Transform the perpendicular vector into the new coordinate space for the gradient. + // This vector is now the slope of the linear gradient as it should appear in the new coord space + const Point perpendicular (Point (gradient.point2.y - gradient.point1.y, + gradient.point1.x - gradient.point2.x) + .transformedBy (gradientTransform.withAbsoluteTranslation (0, 0))); + + const Point newGradPoint1 (gradient.point1.transformedBy (gradientTransform)); + const Point newGradPoint2 (gradient.point2.transformedBy (gradientTransform)); + + // Project the transformed gradient vector onto the transformed slope of the linear + // gradient as it should appear in the new coordinate space + const float scale = perpendicular.getDotProduct (newGradPoint2 - newGradPoint1) + / perpendicular.getDotProduct (perpendicular); + + type.gradient->point1 = newGradPoint1; + type.gradient->point2 = newGradPoint2 - perpendicular * scale; + } + + return type; + } + + struct GetFillTypeOp + { + const SVGState* state; + FillType* dest; + const Path* path; + float opacity; + + void operator() (const XmlPath& xml) + { + if (xml->hasTagNameIgnoringNamespace ("linearGradient") + || xml->hasTagNameIgnoringNamespace ("radialGradient")) + *dest = state->getGradientFillType (xml, *path, opacity); + } + }; + + FillType getPathFillType (const Path& path, + const String& fill, + const String& fillOpacity, + const String& overallOpacity, + const Colour defaultColour) const + { + float opacity = 1.0f; + + if (overallOpacity.isNotEmpty()) + opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue()); + + if (fillOpacity.isNotEmpty()) + opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); + + if (fill.startsWithIgnoreCase ("url")) + { + const String id (fill.fromFirstOccurrenceOf ("#", false, false) + .upToLastOccurrenceOf (")", false, false).trim()); + + FillType result; + GetFillTypeOp op = { this, &result, &path, opacity }; + + if (findElementForId (topLevelXml, id, op)) + return result; + } + + if (fill.equalsIgnoreCase ("none")) + return Colours::transparentBlack; + + int i = 0; + return parseColour (fill, i, defaultColour).withMultipliedAlpha (opacity); + } + + static PathStrokeType::JointStyle getJointStyle (const String& join) noexcept + { + if (join.equalsIgnoreCase ("round")) return PathStrokeType::curved; + if (join.equalsIgnoreCase ("bevel")) return PathStrokeType::beveled; + + return PathStrokeType::mitered; + } + + static PathStrokeType::EndCapStyle getEndCapStyle (const String& cap) noexcept + { + if (cap.equalsIgnoreCase ("round")) return PathStrokeType::rounded; + if (cap.equalsIgnoreCase ("square")) return PathStrokeType::square; + + return PathStrokeType::butt; + } + + float getStrokeWidth (const String& strokeWidth) const noexcept + { + return transform.getScaleFactor() * getCoordLength (strokeWidth, viewBoxW); + } + + PathStrokeType getStrokeFor (const XmlPath& xml) const + { + return PathStrokeType (getStrokeWidth (getStyleAttribute (xml, "stroke-width", "1")), + getJointStyle (getStyleAttribute (xml, "stroke-linejoin")), + getEndCapStyle (getStyleAttribute (xml, "stroke-linecap"))); + } + + //============================================================================== + Drawable* parseText (const XmlPath& xml) + { + Array xCoords, yCoords, dxCoords, dyCoords; + + getCoordList (xCoords, getInheritedAttribute (xml, "x"), true, true); + getCoordList (yCoords, getInheritedAttribute (xml, "y"), true, false); + getCoordList (dxCoords, getInheritedAttribute (xml, "dx"), true, true); + getCoordList (dyCoords, getInheritedAttribute (xml, "dy"), true, false); + + + //xxx not done text yet! + + + forEachXmlChildElement (*xml, e) + { + if (e->isTextElement()) + { + const String text (e->getText()); + + Path path; + Drawable* s = parseShape (xml.getChild (e), path); + delete s; // xxx not finished! + } + else if (e->hasTagNameIgnoringNamespace ("tspan")) + { + Drawable* s = parseText (xml.getChild (e)); + delete s; // xxx not finished! + } + } + + return nullptr; + } + + //============================================================================== + void addTransform (const XmlPath& xml) + { + transform = parseTransform (xml->getStringAttribute ("transform")) + .followedBy (transform); + } + + //============================================================================== + bool parseCoord (String::CharPointerType& s, float& value, const bool allowUnits, const bool isX) const + { + String number; + + if (! parseNextNumber (s, number, allowUnits)) + { + value = 0; + return false; + } + + value = getCoordLength (number, isX ? viewBoxW : viewBoxH); + return true; + } + + bool parseCoords (String::CharPointerType& s, Point& p, const bool allowUnits) const + { + return parseCoord (s, p.x, allowUnits, true) + && parseCoord (s, p.y, allowUnits, false); + } + + bool parseCoordsOrSkip (String::CharPointerType& s, Point& p, const bool allowUnits) const + { + if (parseCoords (s, p, allowUnits)) + return true; + + if (! s.isEmpty()) ++s; + return false; + } + + float getCoordLength (const String& s, const float sizeForProportions) const noexcept + { + float n = s.getFloatValue(); + const int len = s.length(); + + if (len > 2) + { + const float dpi = 96.0f; + + const juce_wchar n1 = s [len - 2]; + const juce_wchar n2 = s [len - 1]; + + if (n1 == 'i' && n2 == 'n') n *= dpi; + else if (n1 == 'm' && n2 == 'm') n *= dpi / 25.4f; + else if (n1 == 'c' && n2 == 'm') n *= dpi / 2.54f; + else if (n1 == 'p' && n2 == 'c') n *= 15.0f; + else if (n2 == '%') n *= 0.01f * sizeForProportions; + } + + return n; + } + + float getCoordLength (const XmlPath& xml, const char* attName, const float sizeForProportions) const noexcept + { + return getCoordLength (xml->getStringAttribute (attName), sizeForProportions); + } + + void getCoordList (Array& coords, const String& list, bool allowUnits, const bool isX) const + { + String::CharPointerType text (list.getCharPointer()); + float value; + + while (parseCoord (text, value, allowUnits, isX)) + coords.add (value); + } + + //============================================================================== + void parseCSSStyle (const XmlPath& xml) + { + cssStyleText = xml->getAllSubText() + "\n" + cssStyleText; + } + + static String::CharPointerType findStyleItem (String::CharPointerType source, String::CharPointerType name) + { + const int nameLength = (int) name.length(); + + while (! source.isEmpty()) + { + if (source.getAndAdvance() == '.' + && CharacterFunctions::compareIgnoreCaseUpTo (source, name, nameLength) == 0) + { + String::CharPointerType endOfName ((source + nameLength).findEndOfWhitespace()); + + if (*endOfName == '{') + return endOfName; + } + } + + return source; + } + + String getStyleAttribute (const XmlPath& xml, StringRef attributeName, + const String& defaultValue = String()) const + { + if (xml->hasAttribute (attributeName)) + return xml->getStringAttribute (attributeName, defaultValue); + + const String styleAtt (xml->getStringAttribute ("style")); + + if (styleAtt.isNotEmpty()) + { + const String value (getAttributeFromStyleList (styleAtt, attributeName, String())); + + if (value.isNotEmpty()) + return value; + } + else if (xml->hasAttribute ("class")) + { + String::CharPointerType openBrace = findStyleItem (cssStyleText.getCharPointer(), + xml->getStringAttribute ("class").getCharPointer()); + + if (! openBrace.isEmpty()) + { + String::CharPointerType closeBrace = CharacterFunctions::find (openBrace, (juce_wchar) '}'); + + if (closeBrace != openBrace) + { + const String value (getAttributeFromStyleList (String (openBrace + 1, closeBrace), + attributeName, defaultValue)); + if (value.isNotEmpty()) + return value; + } + } + } + + if (xml.parent != nullptr) + return getStyleAttribute (*xml.parent, attributeName, defaultValue); + + return defaultValue; + } + + String getInheritedAttribute (const XmlPath& xml, StringRef attributeName) const + { + if (xml->hasAttribute (attributeName)) + return xml->getStringAttribute (attributeName); + + if (xml.parent != nullptr) + return getInheritedAttribute (*xml.parent, attributeName); + + return String(); + } + + static int parsePlacementFlags (const String& align) noexcept + { + if (align.isEmpty()) + return 0; + + if (align.containsIgnoreCase ("none")) + return RectanglePlacement::stretchToFit; + + return (align.containsIgnoreCase ("slice") ? RectanglePlacement::fillDestination : 0) + | (align.containsIgnoreCase ("xMin") ? RectanglePlacement::xLeft + : (align.containsIgnoreCase ("xMax") ? RectanglePlacement::xRight + : RectanglePlacement::xMid)) + | (align.containsIgnoreCase ("yMin") ? RectanglePlacement::yTop + : (align.containsIgnoreCase ("yMax") ? RectanglePlacement::yBottom + : RectanglePlacement::yMid)); + } + + //============================================================================== + static bool isIdentifierChar (const juce_wchar c) + { + return CharacterFunctions::isLetter (c) || c == '-'; + } + + static String getAttributeFromStyleList (const String& list, StringRef attributeName, const String& defaultValue) + { + int i = 0; + + for (;;) + { + i = list.indexOf (i, attributeName); + + if (i < 0) + break; + + if ((i == 0 || (i > 0 && ! isIdentifierChar (list [i - 1]))) + && ! isIdentifierChar (list [i + attributeName.length()])) + { + i = list.indexOfChar (i, ':'); + + if (i < 0) + break; + + int end = list.indexOfChar (i, ';'); + + if (end < 0) + end = 0x7ffff; + + return list.substring (i + 1, end).trim(); + } + + ++i; + } + + return defaultValue; + } + + //============================================================================== + static bool parseNextNumber (String::CharPointerType& text, String& value, const bool allowUnits) + { + String::CharPointerType s (text); + + while (s.isWhitespace() || *s == ',') + ++s; + + String::CharPointerType start (s); + + if (s.isDigit() || *s == '.' || *s == '-') + ++s; + + while (s.isDigit() || *s == '.') + ++s; + + if ((*s == 'e' || *s == 'E') + && ((s + 1).isDigit() || s[1] == '-' || s[1] == '+')) + { + s += 2; + + while (s.isDigit()) + ++s; + } + + if (allowUnits) + while (s.isLetter()) + ++s; + + if (s == start) + { + text = s; + return false; + } + + value = String (start, s); + + while (s.isWhitespace() || *s == ',') + ++s; + + text = s; + return true; + } + + //============================================================================== + static Colour parseColour (const String& s, int& index, const Colour defaultColour) + { + if (s [index] == '#') + { + uint32 hex[6] = { 0 }; + int numChars = 0; + + for (int i = 6; --i >= 0;) + { + const int hexValue = CharacterFunctions::getHexDigitValue (s [++index]); + + if (hexValue >= 0) + hex [numChars++] = (uint32) hexValue; + else + break; + } + + if (numChars <= 3) + return Colour ((uint8) (hex [0] * 0x11), + (uint8) (hex [1] * 0x11), + (uint8) (hex [2] * 0x11)); + + return Colour ((uint8) ((hex [0] << 4) + hex [1]), + (uint8) ((hex [2] << 4) + hex [3]), + (uint8) ((hex [4] << 4) + hex [5])); + } + + if (s [index] == 'r' + && s [index + 1] == 'g' + && s [index + 2] == 'b') + { + const int openBracket = s.indexOfChar (index, '('); + const int closeBracket = s.indexOfChar (openBracket, ')'); + + if (openBracket >= 3 && closeBracket > openBracket) + { + index = closeBracket; + + StringArray tokens; + tokens.addTokens (s.substring (openBracket + 1, closeBracket), ",", ""); + tokens.trim(); + tokens.removeEmptyStrings(); + + if (tokens[0].containsChar ('%')) + return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()), + (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()), + (uint8) roundToInt (2.55 * tokens[2].getDoubleValue())); + else + return Colour ((uint8) tokens[0].getIntValue(), + (uint8) tokens[1].getIntValue(), + (uint8) tokens[2].getIntValue()); + } + } + + return Colours::findColourForName (s, defaultColour); + } + + static AffineTransform parseTransform (String t) + { + AffineTransform result; + + while (t.isNotEmpty()) + { + StringArray tokens; + tokens.addTokens (t.fromFirstOccurrenceOf ("(", false, false) + .upToFirstOccurrenceOf (")", false, false), + ", ", ""); + + tokens.removeEmptyStrings (true); + + float numbers[6]; + + for (int i = 0; i < numElementsInArray (numbers); ++i) + numbers[i] = tokens[i].getFloatValue(); + + AffineTransform trans; + + if (t.startsWithIgnoreCase ("matrix")) + { + trans = AffineTransform (numbers[0], numbers[2], numbers[4], + numbers[1], numbers[3], numbers[5]); + } + else if (t.startsWithIgnoreCase ("translate")) + { + trans = AffineTransform::translation (numbers[0], numbers[1]); + } + else if (t.startsWithIgnoreCase ("scale")) + { + trans = AffineTransform::scale (numbers[0], numbers[tokens.size() > 1 ? 1 : 0]); + } + else if (t.startsWithIgnoreCase ("rotate")) + { + trans = AffineTransform::rotation (numbers[0] / (180.0f / float_Pi), numbers[1], numbers[2]); + } + else if (t.startsWithIgnoreCase ("skewX")) + { + trans = AffineTransform::shear (std::tan (numbers[0] * (float_Pi / 180.0f)), 0.0f); + } + else if (t.startsWithIgnoreCase ("skewY")) + { + trans = AffineTransform::shear (0.0f, std::tan (numbers[0] * (float_Pi / 180.0f))); + } + + result = trans.followedBy (result); + t = t.fromFirstOccurrenceOf (")", false, false).trimStart(); + } + + return result; + } + + static void endpointToCentreParameters (const double x1, const double y1, + const double x2, const double y2, + const double angle, + const bool largeArc, const bool sweep, + double& rx, double& ry, + double& centreX, double& centreY, + double& startAngle, double& deltaAngle) noexcept + { + const double midX = (x1 - x2) * 0.5; + const double midY = (y1 - y2) * 0.5; + + const double cosAngle = std::cos (angle); + const double sinAngle = std::sin (angle); + const double xp = cosAngle * midX + sinAngle * midY; + const double yp = cosAngle * midY - sinAngle * midX; + const double xp2 = xp * xp; + const double yp2 = yp * yp; + + double rx2 = rx * rx; + double ry2 = ry * ry; + + const double s = (xp2 / rx2) + (yp2 / ry2); + double c; + + if (s <= 1.0) + { + c = std::sqrt (jmax (0.0, ((rx2 * ry2) - (rx2 * yp2) - (ry2 * xp2)) + / (( rx2 * yp2) + (ry2 * xp2)))); + + if (largeArc == sweep) + c = -c; + } + else + { + const double s2 = std::sqrt (s); + rx *= s2; + ry *= s2; + c = 0; + } + + const double cpx = ((rx * yp) / ry) * c; + const double cpy = ((-ry * xp) / rx) * c; + + centreX = ((x1 + x2) * 0.5) + (cosAngle * cpx) - (sinAngle * cpy); + centreY = ((y1 + y2) * 0.5) + (sinAngle * cpx) + (cosAngle * cpy); + + const double ux = (xp - cpx) / rx; + const double uy = (yp - cpy) / ry; + const double vx = (-xp - cpx) / rx; + const double vy = (-yp - cpy) / ry; + + const double length = juce_hypot (ux, uy); + + startAngle = acos (jlimit (-1.0, 1.0, ux / length)); + + if (uy < 0) + startAngle = -startAngle; + + startAngle += double_Pi * 0.5; + + deltaAngle = acos (jlimit (-1.0, 1.0, ((ux * vx) + (uy * vy)) + / (length * juce_hypot (vx, vy)))); + + if ((ux * vy) - (uy * vx) < 0) + deltaAngle = -deltaAngle; + + if (sweep) + { + if (deltaAngle < 0) + deltaAngle += double_Pi * 2.0; + } + else + { + if (deltaAngle > 0) + deltaAngle -= double_Pi * 2.0; + } + + deltaAngle = fmod (deltaAngle, double_Pi * 2.0); + } + + template + static bool findElementForId (const XmlPath& parent, const String& id, OperationType& op) + { + forEachXmlChildElement (*parent, e) + { + if (e->compareAttribute ("id", id)) + { + op (parent.getChild (e)); + return true; + } + + if (findElementForId (parent.getChild (e), id, op)) + return true; + } + + return false; + } + + SVGState& operator= (const SVGState&) JUCE_DELETED_FUNCTION; +}; + + +//============================================================================== +Drawable* Drawable::createFromSVG (const XmlElement& svgDocument) +{ + SVGState state (&svgDocument); + return state.parseSVGElement (SVGState::XmlPath (&svgDocument, nullptr)); +} + +Path Drawable::parseSVGPath (const String& svgPath) +{ + SVGState state (nullptr); + Path p; + state.parsePathString (p, svgPath); + return p; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.cpp new file mode 100644 index 0000000..265df83 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.cpp @@ -0,0 +1,71 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow) + : fileList (listToShow) +{ +} + +DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent() +{ +} + +//============================================================================== +FileBrowserListener::~FileBrowserListener() +{ +} + +void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* const listener) +{ + listeners.add (listener); +} + +void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* const listener) +{ + listeners.remove (listener); +} + +void DirectoryContentsDisplayComponent::sendSelectionChangeMessage() +{ + Component::BailOutChecker checker (dynamic_cast (this)); + listeners.callChecked (checker, &FileBrowserListener::selectionChanged); +} + +void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e) +{ + if (fileList.getDirectory().exists()) + { + Component::BailOutChecker checker (dynamic_cast (this)); + listeners.callChecked (checker, &FileBrowserListener::fileClicked, file, e); + } +} + +void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file) +{ + if (fileList.getDirectory().exists()) + { + Component::BailOutChecker checker (dynamic_cast (this)); + listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, file); + } +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h new file mode 100644 index 0000000..ebe1680 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED +#define JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED + + +//============================================================================== +/** + A base class for components that display a list of the files in a directory. + + @see DirectoryContentsList +*/ +class JUCE_API DirectoryContentsDisplayComponent +{ +public: + //============================================================================== + /** Creates a DirectoryContentsDisplayComponent for a given list of files. */ + DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow); + + /** Destructor. */ + virtual ~DirectoryContentsDisplayComponent(); + + //============================================================================== + /** Returns the number of files the user has got selected. + @see getSelectedFile + */ + virtual int getNumSelectedFiles() const = 0; + + /** Returns one of the files that the user has currently selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles + */ + virtual File getSelectedFile (int index) const = 0; + + /** Deselects any selected files. */ + virtual void deselectAllFiles() = 0; + + /** Scrolls this view to the top. */ + virtual void scrollToTop() = 0; + + /** If the specified file is in the list, it will become the only selected item + (and if the file isn't in the list, all other items will be deselected). */ + virtual void setSelectedFile (const File&) = 0; + + //============================================================================== + /** Adds a listener to be told when files are selected or clicked. + @see removeListener + */ + void addListener (FileBrowserListener* listener); + + /** Removes a listener. + @see addListener + */ + void removeListener (FileBrowserListener* listener); + + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the list. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + highlightColourId = 0x1000540, /**< The colour to use to fill a highlighted row of the list. */ + textColourId = 0x1000541, /**< The colour for the text. */ + }; + + //============================================================================== + /** @internal */ + void sendSelectionChangeMessage(); + /** @internal */ + void sendDoubleClickMessage (const File& file); + /** @internal */ + void sendMouseClickMessage (const File& file, const MouseEvent& e); + +protected: + //============================================================================== + DirectoryContentsList& fileList; + ListenerList listeners; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent) +}; + + +#endif // JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp new file mode 100644 index 0000000..db98eb2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp @@ -0,0 +1,258 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t) + : fileFilter (f), thread (t), + fileTypeFlags (File::ignoreHiddenFiles | File::findFiles), + shouldStop (true) +{ +} + +DirectoryContentsList::~DirectoryContentsList() +{ + stopSearching(); +} + +void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles) +{ + setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles) + : (fileTypeFlags & ~File::ignoreHiddenFiles)); +} + +bool DirectoryContentsList::ignoresHiddenFiles() const +{ + return (fileTypeFlags & File::ignoreHiddenFiles) != 0; +} + +//============================================================================== +void DirectoryContentsList::setDirectory (const File& directory, + const bool includeDirectories, + const bool includeFiles) +{ + jassert (includeDirectories || includeFiles); // you have to speciify at least one of these! + + if (directory != root) + { + clear(); + root = directory; + changed(); + + // (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes) + fileTypeFlags &= ~(File::findDirectories | File::findFiles); + } + + int newFlags = fileTypeFlags; + if (includeDirectories) newFlags |= File::findDirectories; else newFlags &= ~File::findDirectories; + if (includeFiles) newFlags |= File::findFiles; else newFlags &= ~File::findFiles; + + setTypeFlags (newFlags); +} + +void DirectoryContentsList::setTypeFlags (const int newFlags) +{ + if (fileTypeFlags != newFlags) + { + fileTypeFlags = newFlags; + refresh(); + } +} + +void DirectoryContentsList::stopSearching() +{ + shouldStop = true; + thread.removeTimeSliceClient (this); + fileFindHandle = nullptr; +} + +void DirectoryContentsList::clear() +{ + stopSearching(); + + if (files.size() > 0) + { + files.clear(); + changed(); + } +} + +void DirectoryContentsList::refresh() +{ + clear(); + + if (root.isDirectory()) + { + fileFindHandle = new DirectoryIterator (root, false, "*", fileTypeFlags); + shouldStop = false; + thread.addTimeSliceClient (this); + } +} + +void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter) +{ + const ScopedLock sl (fileListLock); + fileFilter = newFileFilter; +} + +//============================================================================== +bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const +{ + const ScopedLock sl (fileListLock); + + if (const FileInfo* const info = files [index]) + { + result = *info; + return true; + } + + return false; +} + +File DirectoryContentsList::getFile (const int index) const +{ + const ScopedLock sl (fileListLock); + + if (const FileInfo* const info = files [index]) + return root.getChildFile (info->filename); + + return File(); +} + +bool DirectoryContentsList::contains (const File& targetFile) const +{ + const ScopedLock sl (fileListLock); + + for (int i = files.size(); --i >= 0;) + if (root.getChildFile (files.getUnchecked(i)->filename) == targetFile) + return true; + + return false; +} + +bool DirectoryContentsList::isStillLoading() const +{ + return fileFindHandle != nullptr; +} + +void DirectoryContentsList::changed() +{ + sendChangeMessage(); +} + +//============================================================================== +int DirectoryContentsList::useTimeSlice() +{ + const uint32 startTime = Time::getApproximateMillisecondCounter(); + bool hasChanged = false; + + for (int i = 100; --i >= 0;) + { + if (! checkNextFile (hasChanged)) + { + if (hasChanged) + changed(); + + return 500; + } + + if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150)) + break; + } + + if (hasChanged) + changed(); + + return 0; +} + +bool DirectoryContentsList::checkNextFile (bool& hasChanged) +{ + if (fileFindHandle != nullptr) + { + bool fileFoundIsDir, isHidden, isReadOnly; + int64 fileSize; + Time modTime, creationTime; + + if (fileFindHandle->next (&fileFoundIsDir, &isHidden, &fileSize, + &modTime, &creationTime, &isReadOnly)) + { + if (addFile (fileFindHandle->getFile(), fileFoundIsDir, + fileSize, modTime, creationTime, isReadOnly)) + { + hasChanged = true; + } + + return true; + } + + fileFindHandle = nullptr; + } + + return false; +} + +struct FileInfoComparator +{ + static int compareElements (const DirectoryContentsList::FileInfo* const first, + const DirectoryContentsList::FileInfo* const second) + { + #if JUCE_WINDOWS + if (first->isDirectory != second->isDirectory) + return first->isDirectory ? -1 : 1; + #endif + + return first->filename.compareNatural (second->filename); + } +}; + +bool DirectoryContentsList::addFile (const File& file, const bool isDir, + const int64 fileSize, + Time modTime, Time creationTime, + const bool isReadOnly) +{ + const ScopedLock sl (fileListLock); + + if (fileFilter == nullptr + || ((! isDir) && fileFilter->isFileSuitable (file)) + || (isDir && fileFilter->isDirectorySuitable (file))) + { + ScopedPointer info (new FileInfo()); + + info->filename = file.getFileName(); + info->fileSize = fileSize; + info->modificationTime = modTime; + info->creationTime = creationTime; + info->isDirectory = isDir; + info->isReadOnly = isReadOnly; + + for (int i = files.size(); --i >= 0;) + if (files.getUnchecked(i)->filename == info->filename) + return false; + + FileInfoComparator comp; + files.addSorted (comp, info.release()); + return true; + } + + return false; +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h new file mode 100644 index 0000000..b2e6a21 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h @@ -0,0 +1,223 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED +#define JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED + + +//============================================================================== +/** + A class to asynchronously scan for details about the files in a directory. + + This keeps a list of files and some information about them, using a background + thread to scan for more files. As files are found, it broadcasts change messages + to tell any listeners. + + @see FileListComponent, FileBrowserComponent +*/ +class JUCE_API DirectoryContentsList : public ChangeBroadcaster, + private TimeSliceClient +{ +public: + //============================================================================== + /** Creates a directory list. + + To set the directory it should point to, use setDirectory(), which will + also start it scanning for files on the background thread. + + When the background thread finds and adds new files to this list, the + ChangeBroadcaster class will send a change message, so you can register + listeners and update them when the list changes. + + @param fileFilter an optional filter to select which files are + included in the list. If this is nullptr, then all files + and directories are included. Make sure that the filter + doesn't get deleted during the lifetime of this object + @param threadToUse a thread object that this list can use + to scan for files as a background task. Make sure + that the thread you give it has been started, or you + won't get any files! + */ + DirectoryContentsList (const FileFilter* fileFilter, + TimeSliceThread& threadToUse); + + /** Destructor. */ + ~DirectoryContentsList(); + + + //============================================================================== + /** Returns the directory that's currently being used. */ + const File& getDirectory() const noexcept { return root; } + + /** Sets the directory to look in for files. + + If the directory that's passed in is different to the current one, this will + also start the background thread scanning it for files. + */ + void setDirectory (const File& directory, + bool includeDirectories, + bool includeFiles); + + /** Returns true if this list contains directories. + @see setDirectory + */ + bool isFindingDirectories() const noexcept { return (fileTypeFlags & File::findDirectories) != 0; } + + /** Returns true if this list contains files. + @see setDirectory + */ + bool isFindingFiles() const noexcept { return (fileTypeFlags & File::findFiles) != 0; } + + /** Clears the list, and stops the thread scanning for files. */ + void clear(); + + /** Clears the list and restarts scanning the directory for files. */ + void refresh(); + + /** True if the background thread hasn't yet finished scanning for files. */ + bool isStillLoading() const; + + /** Tells the list whether or not to ignore hidden files. + By default these are ignored. + */ + void setIgnoresHiddenFiles (bool shouldIgnoreHiddenFiles); + + /** Returns true if hidden files are ignored. + @see setIgnoresHiddenFiles + */ + bool ignoresHiddenFiles() const; + + /** Replaces the current FileFilter. + This can be nullptr to have no filter. The DirectoryContentList does not take + ownership of this object - it just keeps a pointer to it, so you must manage its + lifetime. + Note that this only replaces the filter, it doesn't refresh the list - you'll + probably want to call refresh() after calling this. + */ + void setFileFilter (const FileFilter* newFileFilter); + + //============================================================================== + /** Contains cached information about one of the files in a DirectoryContentsList. + */ + struct FileInfo + { + //============================================================================== + /** The filename. + + This isn't a full pathname, it's just the last part of the path, same as you'd + get from File::getFileName(). + + To get the full pathname, use DirectoryContentsList::getDirectory().getChildFile (filename). + */ + String filename; + + /** File size in bytes. */ + int64 fileSize; + + /** File modification time. + As supplied by File::getLastModificationTime(). + */ + Time modificationTime; + + /** File creation time. + As supplied by File::getCreationTime(). + */ + Time creationTime; + + /** True if the file is a directory. */ + bool isDirectory; + + /** True if the file is read-only. */ + bool isReadOnly; + }; + + //============================================================================== + /** Returns the number of files currently available in the list. + + The info about one of these files can be retrieved with getFileInfo() or getFile(). + + Obviously as the background thread runs and scans the directory for files, this + number will change. + + @see getFileInfo, getFile + */ + int getNumFiles() const noexcept { return files.size(); } + + /** Returns the cached information about one of the files in the list. + + If the index is in-range, this will return true and will copy the file's details + to the structure that is passed-in. + + If it returns false, then the index wasn't in range, and the structure won't + be affected. + + @see getNumFiles, getFile + */ + bool getFileInfo (int index, FileInfo& resultInfo) const; + + /** Returns one of the files in the list. + + @param index should be less than getNumFiles(). If this is out-of-range, the + return value will be File::nonexistent + @see getNumFiles, getFileInfo + */ + File getFile (int index) const; + + /** Returns the file filter being used. + The filter is specified in the constructor. + */ + const FileFilter* getFilter() const noexcept { return fileFilter; } + + /** Returns true if the list contains the specified file. */ + bool contains (const File&) const; + + //============================================================================== + /** @internal */ + TimeSliceThread& getTimeSliceThread() const noexcept { return thread; } + +private: + File root; + const FileFilter* fileFilter; + TimeSliceThread& thread; + int fileTypeFlags; + + CriticalSection fileListLock; + OwnedArray files; + + ScopedPointer fileFindHandle; + bool volatile shouldStop; + + int useTimeSlice() override; + void stopSearching(); + void changed(); + bool checkNextFile (bool& hasChanged); + bool addFile (const File&, bool isDir, int64 fileSize, Time modTime, + Time creationTime, bool isReadOnly); + void setTypeFlags (int); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsList) +}; + + +#endif // JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp new file mode 100644 index 0000000..4a2f023 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -0,0 +1,586 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +FileBrowserComponent::FileBrowserComponent (int flags_, + const File& initialFileOrDirectory, + const FileFilter* fileFilter_, + FilePreviewComponent* previewComp_) + : FileFilter (String::empty), + fileFilter (fileFilter_), + flags (flags_), + previewComp (previewComp_), + currentPathBox ("path"), + fileLabel ("f", TRANS ("file:")), + thread ("Juce FileBrowser") +{ + // You need to specify one or other of the open/save flags.. + jassert ((flags & (saveMode | openMode)) != 0); + jassert ((flags & (saveMode | openMode)) != (saveMode | openMode)); + + // You need to specify at least one of these flags.. + jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0); + + String filename; + + if (initialFileOrDirectory == File::nonexistent) + { + currentRoot = File::getCurrentWorkingDirectory(); + } + else if (initialFileOrDirectory.isDirectory()) + { + currentRoot = initialFileOrDirectory; + } + else + { + chosenFiles.add (initialFileOrDirectory); + currentRoot = initialFileOrDirectory.getParentDirectory(); + filename = initialFileOrDirectory.getFileName(); + } + + fileList = new DirectoryContentsList (this, thread); + + if ((flags & useTreeView) != 0) + { + FileTreeComponent* const tree = new FileTreeComponent (*fileList); + fileListComponent = tree; + + if ((flags & canSelectMultipleItems) != 0) + tree->setMultiSelectEnabled (true); + + addAndMakeVisible (tree); + } + else + { + FileListComponent* const list = new FileListComponent (*fileList); + fileListComponent = list; + list->setOutlineThickness (1); + + if ((flags & canSelectMultipleItems) != 0) + list->setMultipleSelectionEnabled (true); + + addAndMakeVisible (list); + } + + fileListComponent->addListener (this); + + addAndMakeVisible (currentPathBox); + currentPathBox.setEditableText (true); + resetRecentPaths(); + currentPathBox.addListener (this); + + addAndMakeVisible (filenameBox); + filenameBox.setMultiLine (false); + filenameBox.setSelectAllWhenFocused (true); + filenameBox.setText (filename, false); + filenameBox.addListener (this); + filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0); + + addAndMakeVisible (fileLabel); + fileLabel.attachToComponent (&filenameBox, true); + + addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton()); + goUpButton->addListener (this); + goUpButton->setTooltip (TRANS ("Go up to parent directory")); + + if (previewComp != nullptr) + addAndMakeVisible (previewComp); + + setRoot (currentRoot); + + thread.startThread (4); +} + +FileBrowserComponent::~FileBrowserComponent() +{ + fileListComponent = nullptr; + fileList = nullptr; + thread.stopThread (10000); +} + +//============================================================================== +void FileBrowserComponent::addListener (FileBrowserListener* const newListener) +{ + listeners.add (newListener); +} + +void FileBrowserComponent::removeListener (FileBrowserListener* const listener) +{ + listeners.remove (listener); +} + +//============================================================================== +bool FileBrowserComponent::isSaveMode() const noexcept +{ + return (flags & saveMode) != 0; +} + +int FileBrowserComponent::getNumSelectedFiles() const noexcept +{ + if (chosenFiles.size() == 0 && currentFileIsValid()) + return 1; + + return chosenFiles.size(); +} + +File FileBrowserComponent::getSelectedFile (int index) const noexcept +{ + if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty()) + return currentRoot; + + if (! filenameBox.isReadOnly()) + return currentRoot.getChildFile (filenameBox.getText()); + + return chosenFiles[index]; +} + +bool FileBrowserComponent::currentFileIsValid() const +{ + const File f (getSelectedFile (0)); + + if (isSaveMode()) + return (flags & canSelectDirectories) != 0 || ! f.isDirectory(); + + return f.exists(); +} + +File FileBrowserComponent::getHighlightedFile() const noexcept +{ + return fileListComponent->getSelectedFile (0); +} + +void FileBrowserComponent::deselectAllFiles() +{ + fileListComponent->deselectAllFiles(); +} + +//============================================================================== +bool FileBrowserComponent::isFileSuitable (const File& file) const +{ + return (flags & canSelectFiles) != 0 + && (fileFilter == nullptr || fileFilter->isFileSuitable (file)); +} + +bool FileBrowserComponent::isDirectorySuitable (const File&) const +{ + return true; +} + +bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const +{ + if (f.isDirectory()) + return (flags & canSelectDirectories) != 0 + && (fileFilter == nullptr || fileFilter->isDirectorySuitable (f)); + + return (flags & canSelectFiles) != 0 && f.exists() + && (fileFilter == nullptr || fileFilter->isFileSuitable (f)); +} + +//============================================================================== +const File& FileBrowserComponent::getRoot() const +{ + return currentRoot; +} + +void FileBrowserComponent::setRoot (const File& newRootDirectory) +{ + bool callListeners = false; + + if (currentRoot != newRootDirectory) + { + callListeners = true; + fileListComponent->scrollToTop(); + + String path (newRootDirectory.getFullPathName()); + + if (path.isEmpty()) + path = File::separatorString; + + StringArray rootNames, rootPaths; + getRoots (rootNames, rootPaths); + + if (! rootPaths.contains (path, true)) + { + bool alreadyListed = false; + + for (int i = currentPathBox.getNumItems(); --i >= 0;) + { + if (currentPathBox.getItemText (i).equalsIgnoreCase (path)) + { + alreadyListed = true; + break; + } + } + + if (! alreadyListed) + currentPathBox.addItem (path, currentPathBox.getNumItems() + 2); + } + } + + currentRoot = newRootDirectory; + fileList->setDirectory (currentRoot, true, true); + + String currentRootName (currentRoot.getFullPathName()); + if (currentRootName.isEmpty()) + currentRootName = File::separatorString; + + currentPathBox.setText (currentRootName, dontSendNotification); + + goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory() + && currentRoot.getParentDirectory() != currentRoot); + + if (callListeners) + { + Component::BailOutChecker checker (this); + listeners.callChecked (checker, &FileBrowserListener::browserRootChanged, currentRoot); + } +} + +void FileBrowserComponent::setFileName (const String& newName) +{ + filenameBox.setText (newName, true); + + fileListComponent->setSelectedFile (currentRoot.getChildFile (newName)); +} + +void FileBrowserComponent::resetRecentPaths() +{ + currentPathBox.clear(); + + StringArray rootNames, rootPaths; + getRoots (rootNames, rootPaths); + + for (int i = 0; i < rootNames.size(); ++i) + { + if (rootNames[i].isEmpty()) + currentPathBox.addSeparator(); + else + currentPathBox.addItem (rootNames[i], i + 1); + } + + currentPathBox.addSeparator(); +} + +void FileBrowserComponent::goUp() +{ + setRoot (getRoot().getParentDirectory()); +} + +void FileBrowserComponent::refresh() +{ + fileList->refresh(); +} + +void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter) +{ + if (fileFilter != newFileFilter) + { + fileFilter = newFileFilter; + refresh(); + } +} + +String FileBrowserComponent::getActionVerb() const +{ + return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS("Choose") + : TRANS("Save")) + : TRANS("Open"); +} + +void FileBrowserComponent::setFilenameBoxLabel (const String& name) +{ + fileLabel.setText (name, dontSendNotification); +} + +FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept +{ + return previewComp; +} + +DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept +{ + return fileListComponent; +} + +//============================================================================== +void FileBrowserComponent::resized() +{ + getLookAndFeel() + .layoutFileBrowserComponent (*this, fileListComponent, previewComp, + ¤tPathBox, &filenameBox, goUpButton); +} + +//============================================================================== +void FileBrowserComponent::sendListenerChangeMessage() +{ + Component::BailOutChecker checker (this); + + if (previewComp != nullptr) + previewComp->selectedFileChanged (getSelectedFile (0)); + + // You shouldn't delete the browser when the file gets changed! + jassert (! checker.shouldBailOut()); + + listeners.callChecked (checker, &FileBrowserListener::selectionChanged); +} + +void FileBrowserComponent::selectionChanged() +{ + StringArray newFilenames; + bool resetChosenFiles = true; + + for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i) + { + const File f (fileListComponent->getSelectedFile (i)); + + if (isFileOrDirSuitable (f)) + { + if (resetChosenFiles) + { + chosenFiles.clear(); + resetChosenFiles = false; + } + + chosenFiles.add (f); + newFilenames.add (f.getRelativePathFrom (getRoot())); + } + } + + if (newFilenames.size() > 0) + filenameBox.setText (newFilenames.joinIntoString (", "), false); + + sendListenerChangeMessage(); +} + +void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e) +{ + Component::BailOutChecker checker (this); + listeners.callChecked (checker, &FileBrowserListener::fileClicked, f, e); +} + +void FileBrowserComponent::fileDoubleClicked (const File& f) +{ + if (f.isDirectory()) + { + setRoot (f); + + if ((flags & canSelectDirectories) != 0) + filenameBox.setText (String::empty); + } + else + { + Component::BailOutChecker checker (this); + listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, f); + } +} + +void FileBrowserComponent::browserRootChanged (const File&) {} + +bool FileBrowserComponent::keyPressed (const KeyPress& key) +{ + (void) key; + + #if JUCE_LINUX || JUCE_WINDOWS + if (key.getModifiers().isCommandDown() + && (key.getKeyCode() == 'H' || key.getKeyCode() == 'h')) + { + fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles()); + fileList->refresh(); + return true; + } + #endif + + return false; +} + +//============================================================================== +void FileBrowserComponent::textEditorTextChanged (TextEditor&) +{ + sendListenerChangeMessage(); +} + +void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&) +{ + if (filenameBox.getText().containsChar (File::separator)) + { + const File f (currentRoot.getChildFile (filenameBox.getText())); + + if (f.isDirectory()) + { + setRoot (f); + chosenFiles.clear(); + filenameBox.setText (String::empty); + } + else + { + setRoot (f.getParentDirectory()); + chosenFiles.clear(); + chosenFiles.add (f); + filenameBox.setText (f.getFileName()); + } + } + else + { + fileDoubleClicked (getSelectedFile (0)); + } +} + +void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&) +{ +} + +void FileBrowserComponent::textEditorFocusLost (TextEditor&) +{ + if (! isSaveMode()) + selectionChanged(); +} + +//============================================================================== +void FileBrowserComponent::buttonClicked (Button*) +{ + goUp(); +} + +void FileBrowserComponent::comboBoxChanged (ComboBox*) +{ + const String newText (currentPathBox.getText().trim().unquoted()); + + if (newText.isNotEmpty()) + { + const int index = currentPathBox.getSelectedId() - 1; + + StringArray rootNames, rootPaths; + getRoots (rootNames, rootPaths); + + if (rootPaths [index].isNotEmpty()) + { + setRoot (File (rootPaths [index])); + } + else + { + File f (newText); + + for (;;) + { + if (f.isDirectory()) + { + setRoot (f); + break; + } + + if (f.getParentDirectory() == f) + break; + + f = f.getParentDirectory(); + } + } + } +} + +void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths) +{ + #if JUCE_WINDOWS + Array roots; + File::findFileSystemRoots (roots); + rootPaths.clear(); + + for (int i = 0; i < roots.size(); ++i) + { + const File& drive = roots.getReference(i); + + String name (drive.getFullPathName()); + rootPaths.add (name); + + if (drive.isOnHardDisk()) + { + String volume (drive.getVolumeLabel()); + + if (volume.isEmpty()) + volume = TRANS("Hard Drive"); + + name << " [" << volume << ']'; + } + else if (drive.isOnCDRomDrive()) + { + name << " [" << TRANS("CD/DVD drive") << ']'; + } + + rootNames.add (name); + } + + rootPaths.add (String::empty); + rootNames.add (String::empty); + + rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName()); + rootNames.add (TRANS("Documents")); + rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName()); + rootNames.add (TRANS("Music")); + rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName()); + rootNames.add (TRANS("Pictures")); + rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); + rootNames.add (TRANS("Desktop")); + + #elif JUCE_MAC + rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); + rootNames.add (TRANS("Home folder")); + rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName()); + rootNames.add (TRANS("Documents")); + rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName()); + rootNames.add (TRANS("Music")); + rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName()); + rootNames.add (TRANS("Pictures")); + rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); + rootNames.add (TRANS("Desktop")); + + rootPaths.add (String::empty); + rootNames.add (String::empty); + + Array volumes; + File vol ("/Volumes"); + vol.findChildFiles (volumes, File::findDirectories, false); + + for (int i = 0; i < volumes.size(); ++i) + { + const File& volume = volumes.getReference(i); + + if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.')) + { + rootPaths.add (volume.getFullPathName()); + rootNames.add (volume.getFileName()); + } + } + + #else + rootPaths.add ("/"); + rootNames.add ("/"); + rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); + rootNames.add (TRANS("Home folder")); + rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName()); + rootNames.add (TRANS("Desktop")); + #endif +} + +void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths) +{ + getDefaultRoots (rootNames, rootPaths); +} diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h new file mode 100644 index 0000000..a1151ef --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h @@ -0,0 +1,290 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_FILEBROWSERCOMPONENT_H_INCLUDED +#define JUCE_FILEBROWSERCOMPONENT_H_INCLUDED + + +//============================================================================== +/** + A component for browsing and selecting a file or directory to open or save. + + This contains a FileListComponent and adds various boxes and controls for + navigating and selecting a file. It can work in different modes so that it can + be used for loading or saving a file, or for choosing a directory. + + @see FileChooserDialogBox, FileChooser, FileListComponent +*/ +class JUCE_API FileBrowserComponent : public Component, + private FileBrowserListener, + private TextEditorListener, + private ButtonListener, + private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) + private FileFilter +{ +public: + //============================================================================== + /** Various options for the browser. + + A combination of these is passed into the FileBrowserComponent constructor. + */ + enum FileChooserFlags + { + openMode = 1, /**< specifies that the component should allow the user to + choose an existing file with the intention of opening it. */ + saveMode = 2, /**< specifies that the component should allow the user to specify + the name of a file that will be used to save something. */ + canSelectFiles = 4, /**< specifies that the user can select files (can be used in + conjunction with canSelectDirectories). */ + canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in + conjuction with canSelectFiles). */ + canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */ + useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */ + filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */ + warnAboutOverwriting = 128 /**< specifies that the dialog should warn about overwriting existing files (if possible). */ + }; + + //============================================================================== + /** Creates a FileBrowserComponent. + + @param flags A combination of flags from the FileChooserFlags enumeration, used to + specify the component's behaviour. The flags must contain either openMode + or saveMode, and canSelectFiles and/or canSelectDirectories. + @param initialFileOrDirectory The file or directory that should be selected when the component begins. + If this is File::nonexistent, a default directory will be chosen. + @param fileFilter an optional filter to use to determine which files are shown. + If this is nullptr then all files are displayed. Note that a pointer + is kept internally to this object, so make sure that it is not deleted + before the FileBrowserComponent object is deleted. + @param previewComp an optional preview component that will be used to show previews of + files that the user selects + */ + FileBrowserComponent (int flags, + const File& initialFileOrDirectory, + const FileFilter* fileFilter, + FilePreviewComponent* previewComp); + + /** Destructor. */ + ~FileBrowserComponent(); + + //============================================================================== + /** Returns the number of files that the user has got selected. + If multiple select isn't active, this will only be 0 or 1. To get the complete + list of files they've chosen, pass an index to getCurrentFile(). + */ + int getNumSelectedFiles() const noexcept; + + /** Returns one of the files that the user has chosen. + If the box has multi-select enabled, the index lets you specify which of the files + to get - see getNumSelectedFiles() to find out how many files were chosen. + @see getHighlightedFile + */ + File getSelectedFile (int index) const noexcept; + + /** Deselects any files that are currently selected. + */ + void deselectAllFiles(); + + /** Returns true if the currently selected file(s) are usable. + + This can be used to decide whether the user can press "ok" for the + current file. What it does depends on the mode, so for example in an "open" + mode, this only returns true if a file has been selected and if it exists. + In a "save" mode, a non-existent file would also be valid. + */ + bool currentFileIsValid() const; + + /** This returns the last item in the view that the user has highlighted. + This may be different from getCurrentFile(), which returns the value + that is shown in the filename box, and if there are multiple selections, + this will only return one of them. + @see getSelectedFile + */ + File getHighlightedFile() const noexcept; + + //============================================================================== + /** Returns the directory whose contents are currently being shown in the listbox. */ + const File& getRoot() const; + + /** Changes the directory that's being shown in the listbox. */ + void setRoot (const File& newRootDirectory); + + /** Changes the name that is currently shown in the filename box. */ + void setFileName (const String& newName); + + /** Equivalent to pressing the "up" button to browse the parent directory. */ + void goUp(); + + /** Refreshes the directory that's currently being listed. */ + void refresh(); + + /** Changes the filter that's being used to sift the files. */ + void setFileFilter (const FileFilter* newFileFilter); + + /** Returns a verb to describe what should happen when the file is accepted. + + E.g. if browsing in "load file" mode, this will be "Open", if in "save file" + mode, it'll be "Save", etc. + */ + virtual String getActionVerb() const; + + /** Returns true if the saveMode flag was set when this component was created. + */ + bool isSaveMode() const noexcept; + + /** Sets the label that will be displayed next to the filename entry box. + By default this is just "file", but you might want to change it to something more + appropriate for your app. + */ + void setFilenameBoxLabel (const String& name); + + //============================================================================== + /** Adds a listener to be told when the user selects and clicks on files. + @see removeListener + */ + void addListener (FileBrowserListener* listener); + + /** Removes a listener. + @see addListener + */ + void removeListener (FileBrowserListener* listener); + + /** Returns a platform-specific list of names and paths for some suggested places the user + might want to use as root folders. + The list returned contains empty strings to indicate section breaks. + @see getRoots() + */ + static void getDefaultRoots (StringArray& rootNames, StringArray& rootPaths); + + //============================================================================== + /** This abstract base class is implemented by LookAndFeel classes to provide + various file-browser layout and drawing methods. + */ + struct JUCE_API LookAndFeelMethods + { + virtual ~LookAndFeelMethods() {} + + // These return a pointer to an internally cached drawable - make sure you don't keep + // a copy of this pointer anywhere, as it may become invalid in the future. + virtual const Drawable* getDefaultFolderImage() = 0; + virtual const Drawable* getDefaultDocumentFileImage() = 0; + + virtual AttributedString createFileChooserHeaderText (const String& title, + const String& instructions) = 0; + + virtual void drawFileBrowserRow (Graphics&, int width, int height, + const String& filename, + Image* optionalIcon, + const String& fileSizeDescription, + const String& fileTimeDescription, + bool isDirectory, + bool isItemSelected, + int itemIndex, + DirectoryContentsDisplayComponent&) = 0; + + virtual Button* createFileBrowserGoUpButton() = 0; + + virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp, + DirectoryContentsDisplayComponent* fileListComponent, + FilePreviewComponent* previewComp, + ComboBox* currentPathBox, + TextEditor* filenameBox, + Button* goUpButton) = 0; + }; + + //============================================================================== + /** @internal */ + void resized() override; + /** @internal */ + void buttonClicked (Button*) override; + /** @internal */ + void comboBoxChanged (ComboBox*) override; + /** @internal */ + void textEditorTextChanged (TextEditor&) override; + /** @internal */ + void textEditorReturnKeyPressed (TextEditor&) override; + /** @internal */ + void textEditorEscapeKeyPressed (TextEditor&) override; + /** @internal */ + void textEditorFocusLost (TextEditor&) override; + /** @internal */ + bool keyPressed (const KeyPress&) override; + /** @internal */ + void selectionChanged() override; + /** @internal */ + void fileClicked (const File&, const MouseEvent&) override; + /** @internal */ + void fileDoubleClicked (const File&) override; + /** @internal */ + void browserRootChanged (const File&) override; + /** @internal */ + bool isFileSuitable (const File&) const override; + /** @internal */ + bool isDirectorySuitable (const File&) const override; + + /** @internal */ + FilePreviewComponent* getPreviewComponent() const noexcept; + + /** @internal */ + DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept; + +protected: + /** Returns a list of names and paths for the default places the user might want to look. + + By default this just calls getDefaultRoots(), but you may want to override it to + return a custom list. + */ + virtual void getRoots (StringArray& rootNames, StringArray& rootPaths); + + /** Updates the items in the dropdown list of recent paths with the values from getRoots(). */ + void resetRecentPaths(); + +private: + //============================================================================== + ScopedPointer fileList; + const FileFilter* fileFilter; + + int flags; + File currentRoot; + Array chosenFiles; + ListenerList listeners; + + ScopedPointer fileListComponent; + FilePreviewComponent* previewComp; + ComboBox currentPathBox; + TextEditor filenameBox; + Label fileLabel; + ScopedPointer