From f1384c11ee7279f83428c98bd15e117199b9b757 Mon Sep 17 00:00:00 2001 From: Tony Bark <35226681+tonytins@users.noreply.github.com> Date: Fri, 3 Oct 2025 02:19:59 -0400 Subject: [PATCH] Initial source commit --- .gitignore | 54 + gamma256/build/macOSX/iconColor.png | Bin 0 -> 402 bytes gamma256/build/macOSX/iconMask.png | Bin 0 -> 319 bytes gamma256/build/makeDistributionMacOSX | 55 + gamma256/build/makeDistributions | 56 + gamma256/build/source/cleanSrc | 14 + gamma256/build/source/exportSrc | 2 + gamma256/build/source/runToBuild | 39 + gamma256/build/win32/Passage.ico | Bin 0 -> 766 bytes gamma256/build/win32/SDL.dll | Bin 0 -> 320512 bytes gamma256/build/win32/iconSource.png | Bin 0 -> 213 bytes gamma256/documentation/Readme.txt | 1 + gamma256/documentation/changeLog.txt | 56 + gamma256/gameSource/Envelope.cpp | 92 + gamma256/gameSource/Envelope.h | 45 + gamma256/gameSource/HashTable.h | 133 + gamma256/gameSource/Makefile.GnuLinux | 10 + gamma256/gameSource/Makefile.MacOSX | 12 + gamma256/gameSource/Makefile.MinGW | 10 + gamma256/gameSource/Makefile.all | 96 + gamma256/gameSource/Timbre.cpp | 158 + gamma256/gameSource/Timbre.h | 35 + gamma256/gameSource/World.cpp | 963 ++++ gamma256/gameSource/World.h | 60 + gamma256/gameSource/blowUp.cpp | 72 + gamma256/gameSource/blowUp.h | 11 + gamma256/gameSource/characterSprite.png | Bin 0 -> 1151 bytes gamma256/gameSource/characterSpriteSad.png | Bin 0 -> 446 bytes gamma256/gameSource/chest.png | Bin 0 -> 160 bytes gamma256/gameSource/chestDust.png | Bin 0 -> 248 bytes gamma256/gameSource/chestPrize.png | Bin 0 -> 463 bytes gamma256/gameSource/common.cpp | 24 + gamma256/gameSource/common.h | 17 + gamma256/gameSource/configure | 89 + gamma256/gameSource/game.cpp | 1300 +++++ .../gameSource/graphics/characterSprite.tga | Bin 0 -> 8418 bytes .../graphics/characterSpriteSad.tga | Bin 0 -> 1938 bytes gamma256/gameSource/graphics/chest.tga | Bin 0 -> 426 bytes gamma256/gameSource/graphics/chestDust.tga | Bin 0 -> 4050 bytes gamma256/gameSource/graphics/chestPrize.tga | Bin 0 -> 4866 bytes gamma256/gameSource/graphics/heart.tga | Bin 0 -> 4050 bytes gamma256/gameSource/graphics/numerals.tga | Bin 0 -> 459 bytes gamma256/gameSource/graphics/spouseSprite.tga | Bin 0 -> 6258 bytes gamma256/gameSource/graphics/tileSet.tga | Bin 0 -> 77538 bytes gamma256/gameSource/graphics/title.tga | Bin 0 -> 4818 bytes gamma256/gameSource/heart.png | Bin 0 -> 265 bytes gamma256/gameSource/iPhone/Icon.png | Bin 0 -> 506 bytes gamma256/gameSource/iPhone/Info.plist | 32 + gamma256/gameSource/iPhone/LargeIcon.png | Bin 0 -> 2114 bytes gamma256/gameSource/iPhone/MainWindow.xib | 237 + gamma256/gameSource/iPhone/arrows.png | Bin 0 -> 1109 bytes gamma256/gameSource/iPhone/arrows.tga | Bin 0 -> 3573 bytes gamma256/gameSource/iPhone/blowUp.cpp | 84 + gamma256/gameSource/iPhone/blowUp.h | 10 + gamma256/gameSource/iPhone/common.cpp | 25 + gamma256/gameSource/iPhone/drawIntoScreen.h | 22 + gamma256/gameSource/iPhone/game.cpp | 1568 ++++++ .../jasonrohrer.mode1v3 | 1459 ++++++ .../jasonrohrer.pbxuser | 1277 +++++ .../gameWindowApp.xcodeproj/project.pbxproj | 468 ++ .../gameSource/iPhone/gameWindowAppDelegate.h | 74 + .../iPhone/gameWindowAppDelegate.mm | 434 ++ .../iPhone/gameWindowApp_Prefix.pch | 8 + gamma256/gameSource/iPhone/main.m | 41 + gamma256/gameSource/iPhone/musicPlayer.cpp | 799 +++ ...5145_999998_20081223_20081229154951691.txt | 3 + ...5145_999998_20081224_20081229155013664.txt | 14 + ...5145_999998_20081225_20081229155019662.txt | 16 + ...75145_999998_20081226_2008122915502587.txt | 20 + ...5145_999998_20081227_20081229155031472.txt | 24 + ...5145_999998_20081228_20081229155036881.txt | 18 + ...5145_999998_20081229_20090102170456173.txt | 14 + ...75145_999998_20081230_2009010217055919.txt | 15 + ...75145_999998_20081231_2009010217062259.txt | 9 + ...5145_999998_20090101_20090102170648169.txt | 11 + ...75145_999998_20090102_2009010615085482.txt | 9 + ...5145_999998_20090103_20090106150905023.txt | 11 + ...5145_999998_20090104_20090106150910384.txt | 35 + ...5145_999998_20090105_20090106150917837.txt | 26 + ...5145_999998_20090107_20090114175227209.txt | 19 + ...5145_999998_20090108_20090114175235627.txt | 17 + ...5145_999998_20090109_20090114175240828.txt | 17 + ...5145_999998_20090110_20090114175246221.txt | 15 + ...5145_999998_20090111_20090114175250853.txt | 14 + ...075145_999998_20090112_200901141752552.txt | 12 + ...5145_999998_20090113_20090114175300581.txt | 16 + gamma256/gameSource/iPhone/screenShot.png | Bin 0 -> 13981 bytes .../gameSource/iPhone/storeDescription.txt | 9 + .../gameSource/iPhone/testScreenDrawer.cpp | 21 + gamma256/gameSource/landscape.cpp | 454 ++ gamma256/gameSource/landscape.h | 113 + gamma256/gameSource/mac/SDLMain.h | 11 + gamma256/gameSource/mac/SDLMain.m | 384 ++ gamma256/gameSource/map.cpp | 297 ++ gamma256/gameSource/map.h | 30 + gamma256/gameSource/music.png | Bin 0 -> 1277 bytes gamma256/gameSource/music/music.tga | Bin 0 -> 142002 bytes gamma256/gameSource/musicPlayer.cpp | 645 +++ gamma256/gameSource/musicPlayer.h | 20 + gamma256/gameSource/numerals.png | Bin 0 -> 196 bytes gamma256/gameSource/numerals3.png | Bin 0 -> 144 bytes gamma256/gameSource/score.cpp | 110 + gamma256/gameSource/score.h | 19 + gamma256/gameSource/settings/fullscreen.ini | 1 + gamma256/gameSource/settings/screenHeight.ini | 1 + gamma256/gameSource/settings/screenWidth.ini | 1 + gamma256/gameSource/spouseSprite.png | Bin 0 -> 1089 bytes gamma256/gameSource/testMusicPlayer.cpp | 32 + gamma256/gameSource/testNoise.cpp | 66 + gamma256/gameSource/tileSet.png | Bin 0 -> 13840 bytes gamma256/gameSource/tileTemplate.png | Bin 0 -> 207 bytes gamma256/gameSource/title.png | Bin 0 -> 247 bytes gamma256/prototypes/screenCompress/Makefile | 53 + .../screenCompress/characterSprite.png | Bin 0 -> 311 bytes .../screenCompress/characterSprite.tga | Bin 0 -> 1722 bytes gamma256/prototypes/screenCompress/compile | 1 + .../prototypes/screenCompress/landscape.cpp | 442 ++ .../prototypes/screenCompress/landscape.h | 97 + .../prototypes/screenCompress/mapTile.tga | Bin 0 -> 236 bytes .../screenCompress/screenCompress.cpp | 798 +++ .../prototypes/screenCompress/sdlTest.cpp | 110 + .../prototypes/screenCompress/tileSet.png | Bin 0 -> 831 bytes .../prototypes/screenCompress/tileSet.tga | Bin 0 -> 7770 bytes .../screenCompress/tileTemplate.png | Bin 0 -> 207 bytes .../screenCompress/tileTemplate.tga | Bin 0 -> 3692 bytes minorGems/.emacs | 242 + minorGems/build/Makefile.minorGems | 378 ++ minorGems/build/Makefile.minorGems_targets | 211 + minorGems/common.h | 19 + minorGems/crypto/hashes/sha1.cpp | 265 + minorGems/crypto/hashes/sha1.h | 141 + minorGems/crypto/hashes/sha1Test.cpp | 109 + minorGems/crypto/hashes/sha1TestCompile | 1 + minorGems/crypto/hashes/sha1sum.cpp | 105 + minorGems/crypto/hashes/sha1sumCompile | 1 + minorGems/formats/encodingUtils.cpp | 453 ++ minorGems/formats/encodingUtils.h | 92 + minorGems/formats/encodingUtilsTest.cpp | 57 + minorGems/formats/encodingUtilsTestCompile | 1 + minorGems/formats/html/HTMLUtils.cpp | 57 + minorGems/formats/html/HTMLUtils.h | 46 + minorGems/formats/xml/XMLUtils.cpp | 83 + minorGems/formats/xml/XMLUtils.h | 46 + minorGems/graphics/3d/LandscapePrimitive3D.h | 138 + minorGems/graphics/3d/LathePrimitive3D.h | 129 + minorGems/graphics/3d/Object3D.h | 349 ++ minorGems/graphics/3d/Object3DFactory.h | 129 + minorGems/graphics/3d/Primitive3D.h | 676 +++ minorGems/graphics/3d/Primitive3DFactory.h | 129 + minorGems/graphics/ChannelFilter.h | 33 + minorGems/graphics/Color.h | 557 +++ minorGems/graphics/GraphicBuffer.h | 258 + minorGems/graphics/IconMap.h | 206 + minorGems/graphics/Image.h | 639 +++ minorGems/graphics/ImageColorConverter.h | 573 +++ minorGems/graphics/ImageConverter.h | 67 + minorGems/graphics/RGBAImage.h | 257 + minorGems/graphics/ScreenGraphics.h | 127 + .../graphics/converters/BMPImageConverter.h | 251 + .../converters/BigEndianImageConverter.h | 142 + .../graphics/converters/JPEGImageConverter.h | 95 + .../converters/LittleEndianImageConverter.h | 137 + .../graphics/converters/PNGImageConverter.cpp | 643 +++ .../graphics/converters/PNGImageConverter.h | 100 + .../graphics/converters/TGAImageConverter.h | 376 ++ .../graphics/converters/bmpConverterTest.cpp | 90 + minorGems/graphics/converters/bmpformat.txt | 87 + minorGems/graphics/converters/compileTestPNG | 1 + .../graphics/converters/jpegConverterTest.cpp | 90 + .../converters/jpegConverterTestCompile | 1 + .../graphics/converters/libpngSample.cpp | 108 + minorGems/graphics/converters/lodepng.cpp | 4319 +++++++++++++++++ minorGems/graphics/converters/lodepng.h | 1708 +++++++ minorGems/graphics/converters/testPNG.cpp | 44 + .../unix/JPEGImageConverterUnix.cpp | 563 +++ minorGems/graphics/filters/BoxBlurFilter.h | 234 + minorGems/graphics/filters/InvertFilter.h | 38 + minorGems/graphics/filters/MedianFilter.h | 159 + minorGems/graphics/filters/MultiFilter.h | 87 + minorGems/graphics/filters/SeamlessFilter.h | 155 + minorGems/graphics/filters/ThresholdFilter.h | 79 + minorGems/graphics/filters/quickselect.h | 41 + minorGems/graphics/getKey.h | 71 + minorGems/graphics/getMouse.h | 16 + minorGems/graphics/keyCodes.h | 52 + minorGems/graphics/linux/SDLTest.cpp | 221 + .../graphics/linux/ScreenGraphicsLinux.cpp | 153 + minorGems/graphics/linux/graphixCommonDefs.h | 8 + minorGems/graphics/loadFile.cpp | 86 + minorGems/graphics/loadfile.h | 3 + minorGems/graphics/mac/graphixCommonDefs.h | 8 + minorGems/graphics/mac/graphixFramework.cpp | 304 ++ minorGems/graphics/swapBuffers.h | 24 + minorGems/graphics/test/rgb2yiq.cpp | 149 + minorGems/graphics/test/rgb2yiqCompile | 1 + minorGems/graphics/test/tgaConverter.cpp | 181 + minorGems/graphics/test/tgaConverterCompile | 1 + minorGems/graphics/test/yiq2rgb.cpp | 147 + minorGems/graphics/test/yiq2rgbCompile | 1 + minorGems/graphics/win32/graphixCommonDefs.h | 8 + minorGems/graphics/win32/graphixFramework.cpp | 471 ++ minorGems/io/InputStream.h | 179 + minorGems/io/OutputStream.h | 185 + minorGems/io/PipedStream.h | 176 + minorGems/io/Serializable.h | 81 + minorGems/io/Stream.h | 150 + minorGems/io/TypeIO.h | 141 + minorGems/io/file/Directory.h | 73 + minorGems/io/file/File.h | 994 ++++ minorGems/io/file/FileInputStream.h | 183 + minorGems/io/file/FileOutputStream.h | 175 + minorGems/io/file/Path.h | 615 +++ minorGems/io/file/UniversalFileIO.h | 138 + minorGems/io/file/linux/PathLinux.cpp | 77 + minorGems/io/file/test/testChildFiles.cpp | 73 + minorGems/io/file/test/testPath.cpp | 39 + minorGems/io/file/testPath.cpp | 39 + minorGems/io/file/unix/DirectoryUnix.cpp | 51 + minorGems/io/file/win32/DirectoryWin32.cpp | 53 + minorGems/io/file/win32/PathWin32.cpp | 95 + minorGems/io/file/win32/dirent.cpp | 159 + minorGems/io/file/win32/dirent.h | 77 + minorGems/io/linux/TypeIOLinux.cpp | 132 + minorGems/io/pipedStreamTest.cpp | 78 + minorGems/io/serialPort/SerialPort.h | 108 + .../io/serialPort/SerialPortFromFile.cpp | 88 + .../io/serialPort/linux/SerialPortLinux.cpp | 277 ++ minorGems/io/serialPort/testSerialPort.cpp | 27 + minorGems/io/serialPort/testSerialPortCompile | 1 + minorGems/io/serialPort/win32/COMPort.cpp | 441 ++ minorGems/io/serialPort/win32/COMPort.h | 150 + .../io/serialPort/win32/SerialPortWin32.cpp | 199 + minorGems/io/win32/TypeIOWin32.cpp | 60 + minorGems/numtest.cpp | 49 + .../protocol/p2p/anonymousPublishing.txt | 196 + minorGems/protocol/p2p/genericProtocol.txt | 88 + minorGems/protocol/p2p/motivationEmail.txt | 34 + minorGems/protocol/p2p/notes.tex | 50 + .../protocol/p2p/resourceSpecifications.txt | 74 + minorGems/system/BinarySemaphore.h | 79 + minorGems/system/FinishedSignalThread.cpp | 59 + minorGems/system/FinishedSignalThread.h | 100 + .../system/FinishedSignalThreadManager.cpp | 116 + .../system/FinishedSignalThreadManager.h | 82 + minorGems/system/Launcher.h | 51 + minorGems/system/MutexLock.h | 71 + minorGems/system/Semaphore.h | 201 + minorGems/system/StopSignalThread.cpp | 65 + minorGems/system/StopSignalThread.h | 104 + minorGems/system/TestThread.cpp | 54 + minorGems/system/TestThread.h | 77 + minorGems/system/Thread.h | 136 + minorGems/system/ThreadSafePrinter.h | 71 + minorGems/system/Time.h | 112 + minorGems/system/endian.h | 108 + .../system/linux/BinarySemaphoreLinux.cpp | 192 + minorGems/system/linux/MutexLockLinux.cpp | 76 + minorGems/system/linux/ThreadLinux.cpp | 247 + minorGems/system/semaphoreTest.cpp | 115 + minorGems/system/unix/LauncherUnix.cpp | 33 + minorGems/system/unix/TimeUnix.cpp | 40 + .../system/win32/BinarySemaphoreWin32.cpp | 94 + minorGems/system/win32/LauncherWin32.cpp | 29 + minorGems/system/win32/MutexLockWin32.cpp | 81 + minorGems/system/win32/ThreadWin32.cpp | 108 + minorGems/system/win32/TimeWin32.cpp | 77 + minorGems/ui/GUIComponent.h | 31 + minorGems/ui/Keyboard.h | 77 + minorGems/ui/Mouse.h | 97 + minorGems/ui/Plot.cpp | 131 + minorGems/ui/Plot.h | 83 + minorGems/ui/ProgressBar.cpp | 122 + minorGems/ui/ProgressBar.h | 75 + minorGems/ui/SetMouse.h | 15 + minorGems/ui/SetMouseMac.cpp | 40 + minorGems/ui/SetMouseWin32.cpp | 50 + minorGems/ui/event/ActionListener.h | 54 + minorGems/ui/event/ActionListenerList.h | 143 + minorGems/ui/linux/KeyboardLinux.cpp | 180 + minorGems/ui/linux/MouseLinux.cpp | 73 + minorGems/util/CircularBuffer.h | 167 + minorGems/util/SettingsManager.cpp | 393 ++ minorGems/util/SettingsManager.h | 308 ++ minorGems/util/SimpleVector.h | 464 ++ minorGems/util/StringBufferOutputStream.cpp | 64 + minorGems/util/StringBufferOutputStream.h | 86 + minorGems/util/TranslationManager.cpp | 360 ++ minorGems/util/TranslationManager.h | 203 + .../util/development/fortify/FORTIFY.DOC | 746 +++ minorGems/util/development/fortify/Makefile | 73 + .../util/development/fortify/fortify.cpp | 2339 +++++++++ minorGems/util/development/fortify/fortify.h | 273 ++ minorGems/util/development/fortify/test.c | 61 + minorGems/util/development/fortify/test2.cpp | 66 + minorGems/util/development/fortify/test3.cpp | 57 + minorGems/util/development/fortify/ufortify.h | 79 + .../util/development/leakTracer/LeakCheck | 22 + .../development/leakTracer/LeakCheckAnalyze | 13 + .../util/development/leakTracer/LeakTracer.cc | 578 +++ .../util/development/leakTracer/Makefile | 64 + minorGems/util/development/leakTracer/README | 230 + .../util/development/leakTracer/README.html | 210 + minorGems/util/development/leakTracer/VERSION | 1 + .../util/development/leakTracer/leak-analyze | 116 + minorGems/util/development/leakTracer/test.cc | 27 + .../util/development/memory/MemoryTrack.cpp | 277 ++ .../util/development/memory/MemoryTrack.h | 230 + .../development/memory/compileTestDebugMemory | 1 + .../util/development/memory/debugMemory.cpp | 95 + .../util/development/memory/debugMemory.h | 127 + .../development/memory/testDebugMemory.cpp | 85 + minorGems/util/log/AppLog.cpp | 156 + minorGems/util/log/AppLog.h | 167 + minorGems/util/log/FileLog.cpp | 146 + minorGems/util/log/FileLog.h | 95 + minorGems/util/log/Log.cpp | 32 + minorGems/util/log/Log.h | 126 + minorGems/util/log/Makefile | 61 + minorGems/util/log/PrintLog.cpp | 209 + minorGems/util/log/PrintLog.h | 107 + minorGems/util/log/testLog.cpp | 42 + minorGems/util/printUtils.cpp | 42 + minorGems/util/printUtils.h | 31 + minorGems/util/random/CustomRandomSource.h | 244 + minorGems/util/random/Noise.cpp | 312 ++ minorGems/util/random/Noise.h | 89 + minorGems/util/random/RandomSource.h | 86 + minorGems/util/random/StdRandomSource.h | 152 + minorGems/util/random/testRandom.cpp | 215 + minorGems/util/stringUtils.cpp | 450 ++ minorGems/util/stringUtils.h | 321 ++ minorGems/util/test/testSnprintf.cpp | 58 + minorGems/util/test/testSnprintf.mingw.out | 12 + minorGems/util/vectorTest.cpp | 135 + runToBuild | 39 + 335 files changed, 52715 insertions(+) create mode 100644 .gitignore create mode 100644 gamma256/build/macOSX/iconColor.png create mode 100644 gamma256/build/macOSX/iconMask.png create mode 100755 gamma256/build/makeDistributionMacOSX create mode 100755 gamma256/build/makeDistributions create mode 100755 gamma256/build/source/cleanSrc create mode 100755 gamma256/build/source/exportSrc create mode 100755 gamma256/build/source/runToBuild create mode 100644 gamma256/build/win32/Passage.ico create mode 100755 gamma256/build/win32/SDL.dll create mode 100644 gamma256/build/win32/iconSource.png create mode 100644 gamma256/documentation/Readme.txt create mode 100644 gamma256/documentation/changeLog.txt create mode 100644 gamma256/gameSource/Envelope.cpp create mode 100644 gamma256/gameSource/Envelope.h create mode 100644 gamma256/gameSource/HashTable.h create mode 100644 gamma256/gameSource/Makefile.GnuLinux create mode 100644 gamma256/gameSource/Makefile.MacOSX create mode 100644 gamma256/gameSource/Makefile.MinGW create mode 100644 gamma256/gameSource/Makefile.all create mode 100644 gamma256/gameSource/Timbre.cpp create mode 100644 gamma256/gameSource/Timbre.h create mode 100644 gamma256/gameSource/World.cpp create mode 100644 gamma256/gameSource/World.h create mode 100644 gamma256/gameSource/blowUp.cpp create mode 100644 gamma256/gameSource/blowUp.h create mode 100644 gamma256/gameSource/characterSprite.png create mode 100644 gamma256/gameSource/characterSpriteSad.png create mode 100644 gamma256/gameSource/chest.png create mode 100644 gamma256/gameSource/chestDust.png create mode 100644 gamma256/gameSource/chestPrize.png create mode 100644 gamma256/gameSource/common.cpp create mode 100644 gamma256/gameSource/common.h create mode 100755 gamma256/gameSource/configure create mode 100644 gamma256/gameSource/game.cpp create mode 100644 gamma256/gameSource/graphics/characterSprite.tga create mode 100644 gamma256/gameSource/graphics/characterSpriteSad.tga create mode 100644 gamma256/gameSource/graphics/chest.tga create mode 100644 gamma256/gameSource/graphics/chestDust.tga create mode 100644 gamma256/gameSource/graphics/chestPrize.tga create mode 100644 gamma256/gameSource/graphics/heart.tga create mode 100644 gamma256/gameSource/graphics/numerals.tga create mode 100644 gamma256/gameSource/graphics/spouseSprite.tga create mode 100644 gamma256/gameSource/graphics/tileSet.tga create mode 100644 gamma256/gameSource/graphics/title.tga create mode 100644 gamma256/gameSource/heart.png create mode 100644 gamma256/gameSource/iPhone/Icon.png create mode 100644 gamma256/gameSource/iPhone/Info.plist create mode 100644 gamma256/gameSource/iPhone/LargeIcon.png create mode 100644 gamma256/gameSource/iPhone/MainWindow.xib create mode 100644 gamma256/gameSource/iPhone/arrows.png create mode 100644 gamma256/gameSource/iPhone/arrows.tga create mode 100644 gamma256/gameSource/iPhone/blowUp.cpp create mode 100644 gamma256/gameSource/iPhone/blowUp.h create mode 100644 gamma256/gameSource/iPhone/common.cpp create mode 100644 gamma256/gameSource/iPhone/drawIntoScreen.h create mode 100644 gamma256/gameSource/iPhone/game.cpp create mode 100644 gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.mode1v3 create mode 100644 gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.pbxuser create mode 100755 gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/project.pbxproj create mode 100644 gamma256/gameSource/iPhone/gameWindowAppDelegate.h create mode 100644 gamma256/gameSource/iPhone/gameWindowAppDelegate.mm create mode 100644 gamma256/gameSource/iPhone/gameWindowApp_Prefix.pch create mode 100644 gamma256/gameSource/iPhone/main.m create mode 100644 gamma256/gameSource/iPhone/musicPlayer.cpp create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081223_20081229154951691.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081224_20081229155013664.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081225_20081229155019662.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081226_2008122915502587.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081227_20081229155031472.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081228_20081229155036881.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081229_20090102170456173.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081230_2009010217055919.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20081231_2009010217062259.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090101_20090102170648169.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090102_2009010615085482.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090103_20090106150905023.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090104_20090106150910384.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090105_20090106150917837.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090107_20090114175227209.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090108_20090114175235627.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090109_20090114175240828.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090110_20090114175246221.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090111_20090114175250853.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090112_200901141752552.txt create mode 100644 gamma256/gameSource/iPhone/reports/S_D_80075145_999998_20090113_20090114175300581.txt create mode 100644 gamma256/gameSource/iPhone/screenShot.png create mode 100644 gamma256/gameSource/iPhone/storeDescription.txt create mode 100644 gamma256/gameSource/iPhone/testScreenDrawer.cpp create mode 100644 gamma256/gameSource/landscape.cpp create mode 100644 gamma256/gameSource/landscape.h create mode 100644 gamma256/gameSource/mac/SDLMain.h create mode 100644 gamma256/gameSource/mac/SDLMain.m create mode 100644 gamma256/gameSource/map.cpp create mode 100644 gamma256/gameSource/map.h create mode 100644 gamma256/gameSource/music.png create mode 100644 gamma256/gameSource/music/music.tga create mode 100644 gamma256/gameSource/musicPlayer.cpp create mode 100644 gamma256/gameSource/musicPlayer.h create mode 100644 gamma256/gameSource/numerals.png create mode 100644 gamma256/gameSource/numerals3.png create mode 100644 gamma256/gameSource/score.cpp create mode 100644 gamma256/gameSource/score.h create mode 100644 gamma256/gameSource/settings/fullscreen.ini create mode 100644 gamma256/gameSource/settings/screenHeight.ini create mode 100644 gamma256/gameSource/settings/screenWidth.ini create mode 100644 gamma256/gameSource/spouseSprite.png create mode 100644 gamma256/gameSource/testMusicPlayer.cpp create mode 100644 gamma256/gameSource/testNoise.cpp create mode 100644 gamma256/gameSource/tileSet.png create mode 100644 gamma256/gameSource/tileTemplate.png create mode 100644 gamma256/gameSource/title.png create mode 100644 gamma256/prototypes/screenCompress/Makefile create mode 100644 gamma256/prototypes/screenCompress/characterSprite.png create mode 100644 gamma256/prototypes/screenCompress/characterSprite.tga create mode 100755 gamma256/prototypes/screenCompress/compile create mode 100644 gamma256/prototypes/screenCompress/landscape.cpp create mode 100644 gamma256/prototypes/screenCompress/landscape.h create mode 100644 gamma256/prototypes/screenCompress/mapTile.tga create mode 100644 gamma256/prototypes/screenCompress/screenCompress.cpp create mode 100644 gamma256/prototypes/screenCompress/sdlTest.cpp create mode 100644 gamma256/prototypes/screenCompress/tileSet.png create mode 100644 gamma256/prototypes/screenCompress/tileSet.tga create mode 100644 gamma256/prototypes/screenCompress/tileTemplate.png create mode 100644 gamma256/prototypes/screenCompress/tileTemplate.tga create mode 100644 minorGems/.emacs create mode 100644 minorGems/build/Makefile.minorGems create mode 100644 minorGems/build/Makefile.minorGems_targets create mode 100644 minorGems/common.h create mode 100644 minorGems/crypto/hashes/sha1.cpp create mode 100644 minorGems/crypto/hashes/sha1.h create mode 100644 minorGems/crypto/hashes/sha1Test.cpp create mode 100755 minorGems/crypto/hashes/sha1TestCompile create mode 100644 minorGems/crypto/hashes/sha1sum.cpp create mode 100755 minorGems/crypto/hashes/sha1sumCompile create mode 100644 minorGems/formats/encodingUtils.cpp create mode 100644 minorGems/formats/encodingUtils.h create mode 100644 minorGems/formats/encodingUtilsTest.cpp create mode 100755 minorGems/formats/encodingUtilsTestCompile create mode 100644 minorGems/formats/html/HTMLUtils.cpp create mode 100644 minorGems/formats/html/HTMLUtils.h create mode 100644 minorGems/formats/xml/XMLUtils.cpp create mode 100644 minorGems/formats/xml/XMLUtils.h create mode 100644 minorGems/graphics/3d/LandscapePrimitive3D.h create mode 100644 minorGems/graphics/3d/LathePrimitive3D.h create mode 100644 minorGems/graphics/3d/Object3D.h create mode 100644 minorGems/graphics/3d/Object3DFactory.h create mode 100644 minorGems/graphics/3d/Primitive3D.h create mode 100644 minorGems/graphics/3d/Primitive3DFactory.h create mode 100644 minorGems/graphics/ChannelFilter.h create mode 100644 minorGems/graphics/Color.h create mode 100644 minorGems/graphics/GraphicBuffer.h create mode 100644 minorGems/graphics/IconMap.h create mode 100644 minorGems/graphics/Image.h create mode 100644 minorGems/graphics/ImageColorConverter.h create mode 100644 minorGems/graphics/ImageConverter.h create mode 100644 minorGems/graphics/RGBAImage.h create mode 100644 minorGems/graphics/ScreenGraphics.h create mode 100644 minorGems/graphics/converters/BMPImageConverter.h create mode 100644 minorGems/graphics/converters/BigEndianImageConverter.h create mode 100644 minorGems/graphics/converters/JPEGImageConverter.h create mode 100644 minorGems/graphics/converters/LittleEndianImageConverter.h create mode 100644 minorGems/graphics/converters/PNGImageConverter.cpp create mode 100644 minorGems/graphics/converters/PNGImageConverter.h create mode 100644 minorGems/graphics/converters/TGAImageConverter.h create mode 100644 minorGems/graphics/converters/bmpConverterTest.cpp create mode 100644 minorGems/graphics/converters/bmpformat.txt create mode 100755 minorGems/graphics/converters/compileTestPNG create mode 100644 minorGems/graphics/converters/jpegConverterTest.cpp create mode 100755 minorGems/graphics/converters/jpegConverterTestCompile create mode 100644 minorGems/graphics/converters/libpngSample.cpp create mode 100644 minorGems/graphics/converters/lodepng.cpp create mode 100644 minorGems/graphics/converters/lodepng.h create mode 100644 minorGems/graphics/converters/testPNG.cpp create mode 100644 minorGems/graphics/converters/unix/JPEGImageConverterUnix.cpp create mode 100644 minorGems/graphics/filters/BoxBlurFilter.h create mode 100644 minorGems/graphics/filters/InvertFilter.h create mode 100644 minorGems/graphics/filters/MedianFilter.h create mode 100644 minorGems/graphics/filters/MultiFilter.h create mode 100644 minorGems/graphics/filters/SeamlessFilter.h create mode 100644 minorGems/graphics/filters/ThresholdFilter.h create mode 100644 minorGems/graphics/filters/quickselect.h create mode 100644 minorGems/graphics/getKey.h create mode 100644 minorGems/graphics/getMouse.h create mode 100644 minorGems/graphics/keyCodes.h create mode 100644 minorGems/graphics/linux/SDLTest.cpp create mode 100644 minorGems/graphics/linux/ScreenGraphicsLinux.cpp create mode 100644 minorGems/graphics/linux/graphixCommonDefs.h create mode 100644 minorGems/graphics/loadFile.cpp create mode 100644 minorGems/graphics/loadfile.h create mode 100644 minorGems/graphics/mac/graphixCommonDefs.h create mode 100644 minorGems/graphics/mac/graphixFramework.cpp create mode 100644 minorGems/graphics/swapBuffers.h create mode 100644 minorGems/graphics/test/rgb2yiq.cpp create mode 100755 minorGems/graphics/test/rgb2yiqCompile create mode 100644 minorGems/graphics/test/tgaConverter.cpp create mode 100755 minorGems/graphics/test/tgaConverterCompile create mode 100644 minorGems/graphics/test/yiq2rgb.cpp create mode 100755 minorGems/graphics/test/yiq2rgbCompile create mode 100644 minorGems/graphics/win32/graphixCommonDefs.h create mode 100644 minorGems/graphics/win32/graphixFramework.cpp create mode 100644 minorGems/io/InputStream.h create mode 100644 minorGems/io/OutputStream.h create mode 100644 minorGems/io/PipedStream.h create mode 100644 minorGems/io/Serializable.h create mode 100644 minorGems/io/Stream.h create mode 100644 minorGems/io/TypeIO.h create mode 100644 minorGems/io/file/Directory.h create mode 100644 minorGems/io/file/File.h create mode 100644 minorGems/io/file/FileInputStream.h create mode 100644 minorGems/io/file/FileOutputStream.h create mode 100644 minorGems/io/file/Path.h create mode 100644 minorGems/io/file/UniversalFileIO.h create mode 100644 minorGems/io/file/linux/PathLinux.cpp create mode 100644 minorGems/io/file/test/testChildFiles.cpp create mode 100644 minorGems/io/file/test/testPath.cpp create mode 100644 minorGems/io/file/testPath.cpp create mode 100644 minorGems/io/file/unix/DirectoryUnix.cpp create mode 100644 minorGems/io/file/win32/DirectoryWin32.cpp create mode 100644 minorGems/io/file/win32/PathWin32.cpp create mode 100644 minorGems/io/file/win32/dirent.cpp create mode 100644 minorGems/io/file/win32/dirent.h create mode 100644 minorGems/io/linux/TypeIOLinux.cpp create mode 100644 minorGems/io/pipedStreamTest.cpp create mode 100644 minorGems/io/serialPort/SerialPort.h create mode 100644 minorGems/io/serialPort/SerialPortFromFile.cpp create mode 100644 minorGems/io/serialPort/linux/SerialPortLinux.cpp create mode 100644 minorGems/io/serialPort/testSerialPort.cpp create mode 100755 minorGems/io/serialPort/testSerialPortCompile create mode 100644 minorGems/io/serialPort/win32/COMPort.cpp create mode 100644 minorGems/io/serialPort/win32/COMPort.h create mode 100644 minorGems/io/serialPort/win32/SerialPortWin32.cpp create mode 100644 minorGems/io/win32/TypeIOWin32.cpp create mode 100644 minorGems/numtest.cpp create mode 100644 minorGems/protocol/p2p/anonymousPublishing.txt create mode 100644 minorGems/protocol/p2p/genericProtocol.txt create mode 100644 minorGems/protocol/p2p/motivationEmail.txt create mode 100644 minorGems/protocol/p2p/notes.tex create mode 100644 minorGems/protocol/p2p/resourceSpecifications.txt create mode 100644 minorGems/system/BinarySemaphore.h create mode 100644 minorGems/system/FinishedSignalThread.cpp create mode 100644 minorGems/system/FinishedSignalThread.h create mode 100644 minorGems/system/FinishedSignalThreadManager.cpp create mode 100644 minorGems/system/FinishedSignalThreadManager.h create mode 100644 minorGems/system/Launcher.h create mode 100644 minorGems/system/MutexLock.h create mode 100644 minorGems/system/Semaphore.h create mode 100644 minorGems/system/StopSignalThread.cpp create mode 100644 minorGems/system/StopSignalThread.h create mode 100644 minorGems/system/TestThread.cpp create mode 100644 minorGems/system/TestThread.h create mode 100644 minorGems/system/Thread.h create mode 100644 minorGems/system/ThreadSafePrinter.h create mode 100644 minorGems/system/Time.h create mode 100644 minorGems/system/endian.h create mode 100644 minorGems/system/linux/BinarySemaphoreLinux.cpp create mode 100644 minorGems/system/linux/MutexLockLinux.cpp create mode 100644 minorGems/system/linux/ThreadLinux.cpp create mode 100644 minorGems/system/semaphoreTest.cpp create mode 100644 minorGems/system/unix/LauncherUnix.cpp create mode 100644 minorGems/system/unix/TimeUnix.cpp create mode 100644 minorGems/system/win32/BinarySemaphoreWin32.cpp create mode 100644 minorGems/system/win32/LauncherWin32.cpp create mode 100644 minorGems/system/win32/MutexLockWin32.cpp create mode 100644 minorGems/system/win32/ThreadWin32.cpp create mode 100644 minorGems/system/win32/TimeWin32.cpp create mode 100644 minorGems/ui/GUIComponent.h create mode 100644 minorGems/ui/Keyboard.h create mode 100644 minorGems/ui/Mouse.h create mode 100644 minorGems/ui/Plot.cpp create mode 100644 minorGems/ui/Plot.h create mode 100644 minorGems/ui/ProgressBar.cpp create mode 100644 minorGems/ui/ProgressBar.h create mode 100644 minorGems/ui/SetMouse.h create mode 100644 minorGems/ui/SetMouseMac.cpp create mode 100644 minorGems/ui/SetMouseWin32.cpp create mode 100644 minorGems/ui/event/ActionListener.h create mode 100644 minorGems/ui/event/ActionListenerList.h create mode 100644 minorGems/ui/linux/KeyboardLinux.cpp create mode 100644 minorGems/ui/linux/MouseLinux.cpp create mode 100644 minorGems/util/CircularBuffer.h create mode 100644 minorGems/util/SettingsManager.cpp create mode 100644 minorGems/util/SettingsManager.h create mode 100644 minorGems/util/SimpleVector.h create mode 100644 minorGems/util/StringBufferOutputStream.cpp create mode 100644 minorGems/util/StringBufferOutputStream.h create mode 100644 minorGems/util/TranslationManager.cpp create mode 100644 minorGems/util/TranslationManager.h create mode 100644 minorGems/util/development/fortify/FORTIFY.DOC create mode 100644 minorGems/util/development/fortify/Makefile create mode 100644 minorGems/util/development/fortify/fortify.cpp create mode 100644 minorGems/util/development/fortify/fortify.h create mode 100644 minorGems/util/development/fortify/test.c create mode 100644 minorGems/util/development/fortify/test2.cpp create mode 100644 minorGems/util/development/fortify/test3.cpp create mode 100644 minorGems/util/development/fortify/ufortify.h create mode 100755 minorGems/util/development/leakTracer/LeakCheck create mode 100755 minorGems/util/development/leakTracer/LeakCheckAnalyze create mode 100644 minorGems/util/development/leakTracer/LeakTracer.cc create mode 100644 minorGems/util/development/leakTracer/Makefile create mode 100644 minorGems/util/development/leakTracer/README create mode 100644 minorGems/util/development/leakTracer/README.html create mode 100644 minorGems/util/development/leakTracer/VERSION create mode 100755 minorGems/util/development/leakTracer/leak-analyze create mode 100644 minorGems/util/development/leakTracer/test.cc create mode 100644 minorGems/util/development/memory/MemoryTrack.cpp create mode 100644 minorGems/util/development/memory/MemoryTrack.h create mode 100755 minorGems/util/development/memory/compileTestDebugMemory create mode 100644 minorGems/util/development/memory/debugMemory.cpp create mode 100644 minorGems/util/development/memory/debugMemory.h create mode 100644 minorGems/util/development/memory/testDebugMemory.cpp create mode 100644 minorGems/util/log/AppLog.cpp create mode 100644 minorGems/util/log/AppLog.h create mode 100644 minorGems/util/log/FileLog.cpp create mode 100644 minorGems/util/log/FileLog.h create mode 100644 minorGems/util/log/Log.cpp create mode 100644 minorGems/util/log/Log.h create mode 100644 minorGems/util/log/Makefile create mode 100644 minorGems/util/log/PrintLog.cpp create mode 100644 minorGems/util/log/PrintLog.h create mode 100644 minorGems/util/log/testLog.cpp create mode 100644 minorGems/util/printUtils.cpp create mode 100644 minorGems/util/printUtils.h create mode 100644 minorGems/util/random/CustomRandomSource.h create mode 100644 minorGems/util/random/Noise.cpp create mode 100644 minorGems/util/random/Noise.h create mode 100644 minorGems/util/random/RandomSource.h create mode 100644 minorGems/util/random/StdRandomSource.h create mode 100644 minorGems/util/random/testRandom.cpp create mode 100644 minorGems/util/stringUtils.cpp create mode 100644 minorGems/util/stringUtils.h create mode 100644 minorGems/util/test/testSnprintf.cpp create mode 100644 minorGems/util/test/testSnprintf.mingw.out create mode 100644 minorGems/util/vectorTest.cpp create mode 100755 runToBuild diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac6be85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Created by https://www.toptal.com/developers/gitignore/api/cmake,visualstudiocode,executable +# Edit at https://www.toptal.com/developers/gitignore?templates=cmake,visualstudiocode,executable + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +CMakeUserPresets.json + +# External projects +*-prefix/ + +### Executable ### +*.app +*.bat +*.cgi +*.com +*.exe +*.gadget +*.jar +*.pif +*.vb +*.wsf + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/cmake,visualstudiocode,executable diff --git a/gamma256/build/macOSX/iconColor.png b/gamma256/build/macOSX/iconColor.png new file mode 100644 index 0000000000000000000000000000000000000000..94cf8608f4fb8f6e6af208a2886058999dde7723 GIT binary patch literal 402 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 A#sB~S literal 0 HcmV?d00001 diff --git a/gamma256/build/win32/iconSource.png b/gamma256/build/win32/iconSource.png new file mode 100644 index 0000000000000000000000000000000000000000..f55dac0e776b3995944402fa37b5ed5393ee617a GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W=6Jd|hIkxLK6CbLssf9e?<}7H z-#EFl;u#VdJZ<|}b(XYQD?hHc`2F83#dN#-%YPj^4|FGk Mr>mdKI;Vst0NzhjrvLx| literal 0 HcmV?d00001 diff --git a/gamma256/documentation/Readme.txt b/gamma256/documentation/Readme.txt new file mode 100644 index 0000000..7c600bb --- /dev/null +++ b/gamma256/documentation/Readme.txt @@ -0,0 +1 @@ +For instructions, please see: http://hcsoftware.sf.net/passage \ No newline at end of file diff --git a/gamma256/documentation/changeLog.txt b/gamma256/documentation/changeLog.txt new file mode 100644 index 0000000..b8727e4 --- /dev/null +++ b/gamma256/documentation/changeLog.txt @@ -0,0 +1,56 @@ +Version 4 2010-May-24 + +--Fixed error in application of patch for older SDL versions. + +--Fixed random number generation so that it works on 64-bit platforms (thanks + to Kevin Fan). + +--Fixed chest gem selection behavior on certain platforms (like iPhone). + +--Fixed unsafe use of pointers into vector (triggered a bug with new shorter + default vector size). + +--Fixed string warnings that are caught by certain GCC versions. + + + + +Version 3 2007-December-13 + +--Switched to analog stick on 360 controller for much smoother play. + +--Added patch for older SDL versions provided by Jarno van der Kolk. + +--Added ESC in addition to Q to quit. + +--Added settings files for controlling screen size and fullscreen mode. + + + +Version 2 2007-November-13 + +--Added chiptune music soundtrack. + +--Improved star sprite. + +--Fixed bug with window sizing (manifested by title being set too low). + +--Improved behavior of screen blow-up/blow-down keys. + +--Fixed bug in game reset that can result in artifacts during subsequent plays. + +--Fixed artifacts after screen blow-up factor change on double-buffered + platforms. + +--Fixed problem with screen pitch. + +--Now builds for MacOSX 10.2 and higher (using MinGW and SDL). + +--Unix source distribution now available (compiles against SDL developer + library). + + + +Version 1 2007-November-1 + +--Initial release (Gamma256 submission) diff --git a/gamma256/gameSource/Envelope.cpp b/gamma256/gameSource/Envelope.cpp new file mode 100644 index 0000000..af00a05 --- /dev/null +++ b/gamma256/gameSource/Envelope.cpp @@ -0,0 +1,92 @@ +#include "Envelope.h" + +#include + +// #include + + +Envelope::Envelope( double inAttackTime, double inDecayTime, + double inSustainLevel, double inReleaseTime, + int inMaxNoteLengthInGridSteps, + int inGridStepDurationInSamples ) + : mNumComputedEnvelopes( inMaxNoteLengthInGridSteps ), + mEvelopeLengths( new int[ inMaxNoteLengthInGridSteps ] ), + mComputedEnvelopes( new double*[ inMaxNoteLengthInGridSteps ] ) { + + for( int i=0; i 0 ); + + mComputedEnvelopes[i][s] = t / inAttackTime; + } + else if( t < inAttackTime + inDecayTime ) { + // assert( inDecayTime > 0 ); + + // decay down to sustain level + mComputedEnvelopes[i][s] = + ( 1.0 - inSustainLevel ) * + ( inAttackTime + inDecayTime - t ) / + ( inDecayTime ) + + inSustainLevel; + } + else if( 1.0 - t > inReleaseTime ) { + mComputedEnvelopes[i][s] = inSustainLevel; + } + else { + if( inReleaseTime > 0 ) { + + mComputedEnvelopes[i][s] = + inSustainLevel - + inSustainLevel * + ( inReleaseTime - ( 1.0 - t ) ) / inReleaseTime; + } + else { + // release time 0 + // hold sustain until end + mComputedEnvelopes[i][s] = inSustainLevel; + } + } + + } + + // test code to output evelopes for plotting in gnuplot + if( false && i == 0 ) { + FILE *file = fopen( "env0.txt", "w" ); + for( int s=0; s +class HashTable { + + public: + + HashTable( int inSize ); + + ~HashTable(); + + Type lookup( int inKeyA, int inKeyB, char *outFound ); + + void insert( int inKeyA, int inKeyB, Type inItem ); + + // flush all entries from table + void clear(); + + + private: + int mSize; + + Type *mTable; + char *mSetFlags; + + int *mKeysA; + int *mKeysB; + + int mHitCount; + int mMissCount; + + int computeHash( int inKeyA, int inKeyB ); + + }; + + + +// Wow... never new that template function implementations must be in the +// same file as the declaration + + +template +HashTable::HashTable( int inSize ) + : mSize( inSize ), + mTable( new Type[ inSize ] ), + mSetFlags( new char[ inSize ] ), + mKeysA( new int[ inSize ] ), + mKeysB( new int[ inSize ] ), + mHitCount( 0 ), + mMissCount( 0 ) { + + + clear(); + + } + + + +#include + + +template +HashTable::~HashTable() { + delete [] mTable; + delete [] mSetFlags; + delete [] mKeysA; + delete [] mKeysB; + + printf( "%d hits, %d misses, %f hit ratio\n", + mHitCount, mMissCount, + mHitCount / (double)( mHitCount + mMissCount ) ); + } + + + +template +inline int HashTable::computeHash( int inKeyA, int inKeyB ) { + + int hashKey = ( inKeyA * 734727 + inKeyB * 263474 ) % mSize; + if( hashKey < 0 ) { + hashKey += mSize; + } + return hashKey; + } + + + +template +Type HashTable::lookup( int inKeyA, int inKeyB, char *outFound ) { + + int hashKey = computeHash( inKeyA, inKeyB ); + + if( mSetFlags[ hashKey ] + && + mKeysA[ hashKey ] == inKeyA + && + mKeysB[ hashKey ] == inKeyB ) { + + // hit + mHitCount ++; + + *outFound = true; + } + else { + // miss + mMissCount ++; + + *outFound = false; + } + + return mTable[ hashKey ]; + } + + + +template +void HashTable::insert( int inKeyA, int inKeyB, Type inItem ) { + + int hashKey = computeHash( inKeyA, inKeyB ); + + mSetFlags[ hashKey ] = true; + mKeysA[ hashKey ] = inKeyA; + mKeysB[ hashKey ] = inKeyB; + mTable[ hashKey ] = inItem; + } + + + +template +void HashTable::clear() { + + for( int i=0; i +#include + + +double twelthRootOfTwo = pow( 2, 1.0/12 ); + +// for major scale +// W, W, H, W, W, W, H +int halfstepMap[ 7 ] = { 0, 2, 4, 5, 7, 9, 11 }; + +// minor scale +// W,H,W,W,H,W,W +//int halfstepMap[ 7 ] = { 0, 2, 3, 5, 7, 8, 10 }; + + + +// gets frequency of note in our scale +double getFrequency( double inBaseFrequency, int inScaleNoteNumber ) { + int octavesUp = inScaleNoteNumber / 7; + + int numHalfsteps = halfstepMap[ inScaleNoteNumber % 7 ] + octavesUp * 12; + + return inBaseFrequency * pow( twelthRootOfTwo, numHalfsteps ); + } + + + + +/* + Was used during testing + +#include "minorGems/sound/formats/aiff.h" + +int outputFileNumber = 0; + + +// outputs a wave table as an AIFF +void outputWaveTable( Sint16 *inTable, int inLength, int inSampleRate ) { + // generate the header + int headerSize; + unsigned char *aiffHeader = + getAIFFHeader( 1, + 16, + inSampleRate, + inLength, + &headerSize ); + + char *fileName = autoSprintf( "waveTable%d.aiff", outputFileNumber ); + outputFileNumber++; + + + FILE *aiffFile = fopen( fileName, "wb" ); + + delete [] fileName; + + + //printf( "Header size = %d\n", headerSize ); + + fwrite( aiffHeader, 1, headerSize, aiffFile ); + + delete [] aiffHeader; + + for( int i=0; i> 8 & 0xFF; + unsigned char lsb = val && 0xFF; + + fwrite( &msb, 1, 1, aiffFile ); + fwrite( &lsb, 1, 1, aiffFile ); + } + fclose( aiffFile ); + + } +*/ + + + + +Timbre::Timbre( int inSampleRate, + double inLoudness, + double inBaseFrequency, + int inNumWaveTableEntries, + double( *inWaveFunction )( double ) ) + : mNumWaveTableEntries( inNumWaveTableEntries ), + mWaveTable( new Sint16*[ inNumWaveTableEntries ] ), + mWaveTableLengths( new int[ inNumWaveTableEntries ] ) { + + // build wave table for each possible pitch in image + + for( int i=0; i maxValue ) { + maxValue = waveValue; + } + else if( -waveValue > maxValue ) { + maxValue = -waveValue; + } + } + + // now normalize and convert to int + for( s=0; s +typedef int16_t Sint16; + + + +class Timbre { + public: + + /** + * Constructs a timbre and fills its wavetables. + * + * @param inSampleRate number of samples per second. + * @param inLoudness a scale factor in [0,1]. + * @param inBaseFrequency the lowest note in the wave table, in Hz. + * This is also the key for the major scale held in the wave table. + * @param inNumWaveTableEntries the number of wavetable entries. + * @param inWaveFunction a function mapping a double parameter t + * to a wave height in [-1,1]. Must have a period of 2pi. + */ + Timbre( int inSampleRate, + double inLoudness, + double inBaseFrequency, + int inNumWaveTableEntries, + double( *inWaveFunction )( double ) ); + + ~Timbre(); + + + + int mNumWaveTableEntries; + // mWaveTable[x] corresponds to a wave with frequency of + // getFrequency(x) + Sint16 **mWaveTable; + int *mWaveTableLengths; + }; diff --git a/gamma256/gameSource/World.cpp b/gamma256/gameSource/World.cpp new file mode 100644 index 0000000..d8fc11e --- /dev/null +++ b/gamma256/gameSource/World.cpp @@ -0,0 +1,963 @@ +#include "World.h" + + +#include "common.h" +#include "map.h" + + +#include "minorGems/graphics/Image.h" +#include "minorGems/util/SimpleVector.h" + + + + + +class GraphicContainer { + + public: + + GraphicContainer( const char *inTGAFileName ) { + + Image *image = readTGA( inTGAFileName ); + + mW = image->getWidth(); + mH = image->getHeight(); + int imagePixelCount = mW * mH; + + mRed = new double[ imagePixelCount ]; + mGreen = new double[ imagePixelCount ]; + mBlue = new double[ imagePixelCount ]; + + for( int i=0; igetChannel(0)[ i ]; + mGreen[i] = 255 * image->getChannel(1)[ i ]; + mBlue[i] = 255 * image->getChannel(2)[ i ]; + } + delete image; + } + + + ~GraphicContainer() { + delete [] mRed; + delete [] mGreen; + delete [] mBlue; + } + + + double *mRed; + double *mGreen; + double *mBlue; + + int mW; + int mH; + + + }; + + + + + + + + +GraphicContainer *tileContainer; +GraphicContainer *chestContainer; + +GraphicContainer *spriteContainer; +GraphicContainer *spriteSadContainer; +GraphicContainer *spouseContainer; + +GraphicContainer *prizeAnimationContainer; +GraphicContainer *dustAnimationContainer; +GraphicContainer *heartAnimationContainer; + + +// dimensions of one tile. TileImage contains 13 tiles, stacked vertically, +// with blank lines between tiles +int tileW = 8; +int tileH = 8; + +int tileImageW; +int tileImageH; + + +int numTileSets; + +// how wide the swath of a world is that uses a given tile set +int tileSetWorldSpan = 200; +// overlap during tile set transition +int tileSetWorldOverlap = 50; + +// for testing +int tileSetSkip = 0; + +int tileSetOrder[18] = { 0, 2, 10, 1, 8, 3, 5, 6, 4, 7, 13, 9, 15, + 14, 16, 12, 11, 17 }; + + + +int mapTileSet( int inSetNumber ) { + // stay in bounds of tileSetOrder + inSetNumber = inSetNumber % 18; + + int mappedSetNumber = tileSetOrder[ inSetNumber ]; + + // stay in bounds of tile set collection + return mappedSetNumber % numTileSets; + } + + + + + +int chestW = 8; +int chestH = 8; + +int numGems = 6; +int gemLocations[6] = { 41, 42, 43, 44, 45, 46 }; +//int gemLocations[4] = { 10, 11, 12, 13 }; +double gemColors[6][3] = { { 255, 0, 0 }, + { 0, 255, 0 }, + { 255, 160, 0 }, + { 0, 0, 255 }, + { 255, 255, 0 }, + { 255, 0, 255 } }; + + + + +class Animation { + public: + + Animation( int inX, int inY, int inFrameW, int inFrameH, + char inAutoStep, + char inRemoveAtEnd, GraphicContainer *inGraphics ) + : mX( inX ), mY( inY ), + mFrameW( inFrameW ), mFrameH( inFrameH ), + mPageNumber( 0 ), + mFrameNumber( 0 ), + mAutoStep( inAutoStep ), + mRemoveAtEnd( inRemoveAtEnd ), + mGraphics( inGraphics ) { + + mImageW = mGraphics->mW; + + mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1; + mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1; + } + + // default constructor so that we can build a vector + // of Animations + Animation() { + } + + + // replaces graphics of animation + void swapGraphics( GraphicContainer *inNewGraphics ) { + + mGraphics = inNewGraphics; + + mImageW = mGraphics->mW; + + mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1; + mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1; + + if( mPageNumber >= mNumPages ) { + mPageNumber = mNumPages - 1; + } + if( mFrameNumber >= mNumFrames ) { + mFrameNumber = mNumFrames - 1; + } + } + + + + int mX, mY; + + + int mFrameW, mFrameH; + int mImageW; + + // can blend between pages + double mPageNumber; + + int mNumPages; + + int mFrameNumber; + int mNumFrames; + char mAutoStep; + char mRemoveAtEnd; + + + GraphicContainer *mGraphics; + + }; + + + +SimpleVector animationList; + + + + +// pointers into vectors are unsafe +//Animation *spriteAnimation; +//Animation *spouseAnimation; + +int spriteAnimationIndex; +int spouseAnimationIndex; + + + +char playerDead = false; +char spouseDead = false; +char metSpouse = false; + + + +void resetSampleHashTable(); + + + +void initWorld() { + resetMap(); + resetSampleHashTable(); + + playerDead = false; + spouseDead = false; + metSpouse = false; + + animationList.deleteAll(); + + + tileImageW = tileContainer->mW; + tileImageH = tileContainer->mH; + + numTileSets = (tileImageW + 1) / (tileW + 1); + + Animation character( 0, 0, 8, 8, false, false, spriteContainer ); + + + animationList.push_back( character ); + + // unsafe + // get pointer to animation in vector + //spriteAnimation = animationList.getElement( animationList.size() - 1 ); + spriteAnimationIndex = animationList.size() - 1; + + Animation spouse( 100, 7, 8, 8, false, false, spouseContainer ); + spouse.mFrameNumber = 7; + + + animationList.push_back( spouse ); + + // unsafe! + // get pointer to animation in vector + //spouseAnimation = animationList.getElement( animationList.size() - 1 ); + spouseAnimationIndex = animationList.size() - 1; + } + + + + + + + + +char isSpriteTransparent( GraphicContainer *inContainer, int inSpriteIndex ) { + + // take transparent color from corner + return + inContainer->mRed[ inSpriteIndex ] == + inContainer->mRed[ 0 ] + && + inContainer->mGreen[ inSpriteIndex ] == + inContainer->mGreen[ 0 ] + && + inContainer->mBlue[ inSpriteIndex ] == + inContainer->mBlue[ 0 ]; + } + + + +struct rgbColorStruct { + double r; + double g; + double b; + }; +typedef struct rgbColorStruct rgbColor; + + + +// outTransient set to true if sample returned is part of a transient +// world feature (character sprite, chest, etc.) +rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient ); + +// same, but wrapped in a hash table to store non-transient results +rgbColor sampleFromWorldNoWeightHash( int inX, int inY ); + + + +Uint32 sampleFromWorld( int inX, int inY, double inWeight ) { + rgbColor c = sampleFromWorldNoWeightHash( inX, inY ); + + unsigned char r = (unsigned char)( inWeight * c.r ); + unsigned char g = (unsigned char)( inWeight * c.g ); + unsigned char b = (unsigned char)( inWeight * c.b ); + + + return r << 16 | g << 8 | b; + } + + + +char isSpritePresent( int inX, int inY ) { + // look at animations + for( int i=0; i= 0 && animX < animW + && + animY >= 0 && animY < animH ) { + return true; + } + + } + + + return false; + } + + + +#include "HashTable.h" + +HashTable worldSampleHashTable( 30000 ); + + +void resetSampleHashTable() { + worldSampleHashTable.clear(); + } + + + +rgbColor sampleFromWorldNoWeightHash( int inX, int inY ) { + + char found; + + rgbColor sample = worldSampleHashTable.lookup( inX, inY, &found ); + + if( isSpritePresent( inX, inY ) ) { + // don't consider cached result if a sprite is present + found = false; + } + + + if( found ) { + return sample; + } + + // else not found + + // call real function to get result + char transient; + sample = sampleFromWorldNoWeight( inX, inY, &transient ); + + // insert, but only if not transient + if( !transient ) { + worldSampleHashTable.insert( inX, inY, sample ); + } + + return sample; + } + + + +rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient ) { + *outTransient = false; + + rgbColor returnColor; + + char isSampleAnim = false; + + // consider sampling from an animation + // draw them in reverse order so that oldest sprites are drawn on top + for( int i=animationList.size() - 1; i>=0; i-- ) { + + Animation a = *( animationList.getElement( i ) ); + + int animW = a.mFrameW; + int animH = a.mFrameH; + + + // pixel position relative to animation center + // player position centered on sprint left-to-right + int animX = (int)( inX - a.mX + a.mFrameW / 2 ); + int animY = (int)( inY - a.mY + a.mFrameH / 2 ); + + if( animX >= 0 && animX < animW + && + animY >= 0 && animY < animH ) { + + // pixel is in animation frame + + int animIndex = animY * a.mImageW + animX; + + // skip to appropriate anim page + animIndex += (int)a.mPageNumber * (animW + 1); + + + // skip to appropriate anim frame + animIndex += + a.mFrameNumber * + a.mImageW * + ( animH + 1 ); + + // page to blend with + int animBlendIndex = animIndex + animW + 1; + + double blendWeight = a.mPageNumber - (int)a.mPageNumber; + + + if( !isSpriteTransparent( a.mGraphics, animIndex ) ) { + + // in non-transparent part + + if( blendWeight != 0 ) { + returnColor.r = + ( 1 - blendWeight ) * a.mGraphics->mRed[ animIndex ] + + + blendWeight * a.mGraphics->mRed[ animBlendIndex ]; + returnColor.g = + ( 1 - blendWeight ) * a.mGraphics->mGreen[ animIndex ] + + + blendWeight * a.mGraphics->mGreen[ animBlendIndex ]; + returnColor.b = + ( 1 - blendWeight ) * a.mGraphics->mBlue[ animIndex ] + + + blendWeight * a.mGraphics->mBlue[ animBlendIndex ]; + } + else { + // no blend + returnColor.r = a.mGraphics->mRed[ animIndex ]; + returnColor.g = a.mGraphics->mGreen[ animIndex ]; + returnColor.b = a.mGraphics->mBlue[ animIndex ]; + } + + *outTransient = true; + // don't return here, because there might be other + // animations and sprites that are on top of + // this one + isSampleAnim = true; + } + } + } + + if( isSampleAnim ) { + // we have already found an animation here that is on top + // of whatever map graphics we might sample below + + // thus, we can return now + return returnColor; + } + + + + int tileIndex; + + char blocked = isBlocked( inX, inY ); + + if( !blocked ) { + // empty tile + tileIndex = 0; + } + else { + + int neighborsBlockedBinary = 0; + + if( isBlocked( inX, inY - tileH ) ) { + // top + neighborsBlockedBinary = neighborsBlockedBinary | 1; + } + if( isBlocked( inX + tileW, inY ) ) { + // right + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1; + } + if( isBlocked( inX, inY + tileH ) ) { + // bottom + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2; + } + if( isBlocked( inX - tileW, inY ) ) { + // left + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3; + } + + // skip empty tile, treat as tile index + neighborsBlockedBinary += 1; + + tileIndex = neighborsBlockedBinary; + } + + + + // pick a tile set + int netWorldSpan = tileSetWorldSpan + tileSetWorldOverlap; + + int tileSet = inX / netWorldSpan + tileSetSkip; + int overhang = inX % netWorldSpan; + if( inX < 0 ) { + // fix to a constant tile set below 0 + overhang = 0; + tileSet = 0; + } + + // is there blending with next tile set? + int blendTileSet = tileSet + 1; + double blendWeight = 0; + + if( overhang > tileSetWorldSpan ) { + blendWeight = ( overhang - tileSetWorldSpan ) / + (double) tileSetWorldOverlap; + } + // else 100% blend of our first tile set + + + tileSet = tileSet % numTileSets; + blendTileSet = blendTileSet % numTileSets; + + // make sure not negative + if( tileSet < 0 ) { + tileSet += numTileSets; + } + if( blendTileSet < 0 ) { + blendTileSet += numTileSets; + } + + + // apply mapping + tileSet = mapTileSet( tileSet ); + blendTileSet = mapTileSet( blendTileSet ); + + + // sample from tile image + int imageY = inY % tileH; + int imageX = inX % tileW; + + + if( imageX < 0 ) { + imageX += tileW; + } + if( imageY < 0 ) { + imageY += tileH; + } + + // offset to top left corner of tile + int tileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW + + tileSet * (tileW + 1); + int blendTileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW + + blendTileSet * (tileW + 1); + + + int imageIndex = tileImageOffset + imageY * tileImageW + imageX; + int blendImageIndex = blendTileImageOffset + imageY * tileImageW + imageX; + + returnColor.r = + (1-blendWeight) * tileContainer->mRed[ imageIndex ] + + blendWeight * tileContainer->mRed[ blendImageIndex ]; + + returnColor.g = + (1-blendWeight) * tileContainer->mGreen[ imageIndex ] + + blendWeight * tileContainer->mGreen[ blendImageIndex ]; + + returnColor.b = + (1-blendWeight) * tileContainer->mBlue[ imageIndex ] + + blendWeight * tileContainer->mBlue[ blendImageIndex ]; + + + // only consider drawing chests in empty spots + if( !blocked && isChest( inX, inY ) ) { + // draw chest here + + char chestType = isChest( inX, inY ); + + + // sample from chest image + int imageY = inY % chestH; + int imageX = inX % chestW; + + + if( imageX < 0 ) { + imageX += chestW; + } + if( imageY < 0 ) { + imageY += chestH; + } + + int spriteIndex = 0; + + if( chestType == CHEST_OPEN ) { + spriteIndex = 1; + } + + // skip to sub-sprite + int spriteOffset = + spriteIndex * + chestW * + ( chestH + 1 ); + + int subSpriteIndex = imageY * chestW + imageX; + + int imageIndex = spriteOffset + subSpriteIndex; + + if( !isSpriteTransparent( chestContainer, imageIndex ) ) { + + if( chestType == CHEST_CLOSED ) { + *outTransient = true; + } + + // check if this is one of the gem locations + char isGem = false; + int gemNumber = 0; + + for( int i=0; imRed[ imageIndex ]; + returnColor.g = chestContainer->mGreen[ imageIndex ]; + returnColor.b = chestContainer->mBlue[ imageIndex ]; + } + + } + + } + + + return returnColor; + } + + + + + + +int getTileWidth() { + return tileW; + } + + + +int getTileHeight() { + return tileH; + } + + + +void destroyWorld() { + /* + printf( "%d hits, %d misses, %f hit ratio\n", + hitCount, missCount, hitCount / (double)( hitCount + missCount ) ); + */ + } + + + +void stepAnimations() { + + + for( int i=0; imAutoStep ) { + if( a->mFrameNumber < a->mNumFrames - 1 ) { + a->mFrameNumber ++; + } + else if( a->mRemoveAtEnd ) { + // remove it + animationList.deleteElement( i ); + // back up in list for next loop iteration + i--; + } + } + + } + } + + + +void startPrizeAnimation( int inX, int inY ) { + + Animation a( inX, inY, 16, 16, true, true, prizeAnimationContainer ); + + animationList.push_back( a ); + } + + + +void startDustAnimation( int inX, int inY ) { + + Animation a( inX, inY, 16, 16, true, true, dustAnimationContainer ); + + animationList.push_back( a ); + } + + + +void startHeartAnimation( int inX, int inY ) { + + Animation a( inX, inY, 16, 16, true, true, heartAnimationContainer ); + + animationList.push_back( a ); + } + + + + + +#include + + +void setPlayerPosition( int inX, int inY ) { + + Animation *spriteAnimation = + animationList.getElement( spriteAnimationIndex ); + + char moving = false; + + if( inX != spriteAnimation->mX ) { + moving = true; + } + + + spriteAnimation->mX = inX; + // player position centered at sprite's feet + int newSpriteY = inY - spriteAnimation->mFrameH / 2 + 1; + + + if( newSpriteY != spriteAnimation->mY ) { + moving = true; + } + + spriteAnimation->mY = newSpriteY; + + if( metSpouse && ! spouseDead ) { + Animation *spouseAnimation = + animationList.getElement( spouseAnimationIndex ); + + // spouse stands immediately in front of player + int desiredSpouseX = inX + spouseAnimation->mFrameH; + int desiredSpouseY = spriteAnimation->mY; + + // gravitates there gradually one pixel at a time in each x and y + int dX = desiredSpouseX - spouseAnimation->mX; + int dY = desiredSpouseY - spouseAnimation->mY; + + // convert to -1, 0, or +1 + if( dX != 0 ) { + dX = (int)( dX / fabs( dX ) ); + } + if( dY != 0 ) { + dY = (int)( dY / fabs( dY ) ); + } + + if( moving ) { + // only execute this transition when player is moving + spouseAnimation->mX += dX; + spouseAnimation->mY += dY; + } + + + // check for heart animation and have it track moving couple + for( int i=0; imGraphics == heartAnimationContainer ) { + + // move it halfway between player and spouse + a->mX = ( spouseAnimation->mX - spriteAnimation->mX ) / 2 + + spriteAnimation->mX; + a->mY = ( spouseAnimation->mY - spriteAnimation->mY ) / 2 + + spriteAnimation->mY + 1; + } + } + + + } + + } + + + +void setPlayerSpriteFrame( int inFrame ) { + Animation *spriteAnimation = + animationList.getElement( spriteAnimationIndex ); + + spriteAnimation->mFrameNumber = inFrame; + + if( metSpouse && ! spouseDead ) { + Animation *spouseAnimation = + animationList.getElement( spouseAnimationIndex ); + + // spouse follows player + spouseAnimation->mFrameNumber = inFrame; + } + } + + + +void setCharacterAges( double inAge ) { + Animation *spriteAnimation = + animationList.getElement( spriteAnimationIndex ); + Animation *spouseAnimation = + animationList.getElement( spouseAnimationIndex ); + + // 0 -> 0.25, constant page 0 + if( inAge <= 0.25 ) { + spriteAnimation->mPageNumber = 0; + spouseAnimation->mPageNumber = 0; + } + // 0.75 - 1.0, constant last page + else if( inAge >= 0.75 ) { + spriteAnimation->mPageNumber = spriteAnimation->mNumPages - 1; + spouseAnimation->mPageNumber = spouseAnimation->mNumPages - 1; + } + else { + // blend of pages in between + double blendingAge = ( inAge - 0.25 ) / 0.5; + + spriteAnimation->mPageNumber = + blendingAge * ( spriteAnimation->mNumPages - 1 ); + + spouseAnimation->mPageNumber = + blendingAge * ( spouseAnimation->mNumPages - 1 ); + } + } + + + +void getSpousePosition( int *outX, int *outY ) { + Animation *spouseAnimation = + animationList.getElement( spouseAnimationIndex ); + + *outX = spouseAnimation->mX; + *outY = spouseAnimation->mY + spouseAnimation->mFrameH / 2 - 1; + } + + + +char haveMetSpouse() { + return metSpouse && ! spouseDead; + } + + + +void meetSpouse() { + metSpouse = true; + } + + + +void diePlayer() { + Animation *spriteAnimation = + animationList.getElement( spriteAnimationIndex ); + + playerDead = true; + + // tombstone + spriteAnimation->mFrameNumber = 8; + } + + + +void dieSpouse() { + Animation *spriteAnimation = + animationList.getElement( spriteAnimationIndex ); + Animation *spouseAnimation = + animationList.getElement( spouseAnimationIndex ); + + spouseDead = true; + + // tombstone + spouseAnimation->mFrameNumber = 8; + + if( metSpouse ) { + // swap player sprite with sad sprite + spriteAnimation->swapGraphics( spriteSadContainer ); + } + } + + + +char isPlayerDead() { + return playerDead; + } + + + +char isSpouseDead() { + return spouseDead; + } + + + +void loadWorldGraphics() { + tileContainer = new GraphicContainer( "tileSet.tga" ); + chestContainer = new GraphicContainer( "chest.tga" ); + + spriteContainer = new GraphicContainer( "characterSprite.tga" ); + spriteSadContainer = new GraphicContainer( "characterSpriteSad.tga" ); + spouseContainer = new GraphicContainer( "spouseSprite.tga" ); + + prizeAnimationContainer = new GraphicContainer( "chestPrize.tga" ); + dustAnimationContainer = new GraphicContainer( "chestDust.tga" ); + heartAnimationContainer = new GraphicContainer( "heart.tga" ); + } + + + +void destroyWorldGraphics() { + delete tileContainer; + delete chestContainer; + + delete spriteContainer; + delete spriteSadContainer; + delete spouseContainer; + + delete prizeAnimationContainer; + delete dustAnimationContainer; + delete heartAnimationContainer; + } + diff --git a/gamma256/gameSource/World.h b/gamma256/gameSource/World.h new file mode 100644 index 0000000..4562580 --- /dev/null +++ b/gamma256/gameSource/World.h @@ -0,0 +1,60 @@ +#include +typedef uint32_t Uint32; + + +// these can be called once at beginning and end of app execution +// since loaded graphics can be reused for multiple games +void loadWorldGraphics(); +void destroyWorldGraphics(); + + +// these should be called at the beginning and end of each new game +void initWorld(); +void destroyWorld(); + + +Uint32 sampleFromWorld( int inX, int inY, double inWeight = 1.0 ); + + +void startPrizeAnimation( int inX, int inY ); +void startDustAnimation( int inX, int inY ); + +void setPlayerPosition( int inX, int inY ); +void setPlayerSpriteFrame( int inFrame ); + + +void getSpousePosition( int *outX, int *outY ); + +char haveMetSpouse(); + +void meetSpouse(); + +void startHeartAnimation( int inX, int inY ); + +void diePlayer(); + +void dieSpouse(); + +char isSpouseDead(); + +char isPlayerDead(); + + + +// age in range 0..1 +void setCharacterAges( double inAge ); + + + + +// push animations forward one step +void stepAnimations(); + + + + +int getTileWidth(); + + +int getTileHeight(); + diff --git a/gamma256/gameSource/blowUp.cpp b/gamma256/gameSource/blowUp.cpp new file mode 100644 index 0000000..ef91a18 --- /dev/null +++ b/gamma256/gameSource/blowUp.cpp @@ -0,0 +1,72 @@ +#include "blowUp.h" + +#include + + + +void blowupOntoScreen( Uint32 *inImage, int inWidth, int inHeight, + int inBlowFactor, SDL_Surface *inScreen ) { + + int newWidth = inBlowFactor * inWidth; + int newHeight = inBlowFactor * inHeight; + + int yOffset = ( inScreen->h - newHeight ) / 2; + int xOffset = ( inScreen->w - newWidth ) / 2; + + // pitch is in bytes + // convert to width in pixels + int scanlineWidth = inScreen->pitch / 4; + + Uint32 *pixels = (Uint32 *)( inScreen->pixels ); + + + // looping across the smaller image, instead of across the larger screen, + // was discovered using the profiler. + + + // an entire screen row is repeated inBlowFactor times down the screen + // (as a row of pixel boxes) + // Thus, we can offload a lot more work onto memcpy if we assemble one + // of these rows and then memcpy it onto the screen inBlowFactor times + for( int y=0; y +#include + + +/** + * Blows an image up onto the screen using nearest neighbor (pixelated) + * interpolation. + */ +void blowupOntoScreen( Uint32 *inImage, int inWidth, int inHeight, + int inBlowFactor, SDL_Surface *inScreen ); diff --git a/gamma256/gameSource/characterSprite.png b/gamma256/gameSource/characterSprite.png new file mode 100644 index 0000000000000000000000000000000000000000..ed280cd83e75d53c42ecec425fdb1b51193b2e23 GIT binary patch literal 1151 zcmV-_1c3XAP)i#EeKfZ*#>KVIhrI;ZunXH^=1E$i2Or0;vqilP7j01!d~0O#&e zaL#T0b>%kz7<_NWl3)}7xYB|8Zs7 zb0J?#04N4U&|vVrsgwc$##k%~A^d^6B7^|@eWhksk5OB({mrzQN^06BmTZ5h1k>?N zdg4lNe}2c#yX98chAYh}bZVhPw@Rs6=L(fHVS*9-m0;}QX3srkWm&>PO132TzeKJB_K`U6#Y;&J z*jRGFI%zVea!cfr42PF|5R#zT4ljqpOJl+%YK$+g@5a}6 z><0Od!=aKi-=1)|BzdbGioW8IZMnc+k#qUTTrV!LCUkr;hb_wQX{(f z)JaLXR4M6e{{ZPpYo2n;h~ z38@m$7h^at>G6I_xn-_&$}MxHQ{-}`{{?X_JMV6Peuu`no!Tm=fX2Cbo>NNEIJe%f zSI^!caju04jaV&AsAOS6B?}W0=Mq9tUv9Sn_2mE{H15j*K+XE{CAmv-Up~(L<=4@x zSKF~$)`CkZeqLWi<6O!Gr8IWS0!;gC|0j}#Zh87EG8M6+H{rzNVK`Q?kep;HYo)TGBq8X~zZu{+rZ)k=J0J6M5 zGgMYGl({Y|&ta8)$>phL$^G*Qa;qtFTdYWutMxL@om-r1jM19!>BHkb*z4~IZDAll7OzmT%?ycBT_?~m$bM!{RM9FWN2{o R<%R$N002ovPDHLkV1nYUElvOc literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/characterSpriteSad.png b/gamma256/gameSource/characterSpriteSad.png new file mode 100644 index 0000000000000000000000000000000000000000..a7228261966e8f8debc6c7b924d56a66862bb968 GIT binary patch literal 446 zcmV;v0YUzWP)IHkYn3NpI0-*PcO*{Ab1)#eQeT4ssU9-$5}6u{#ajoS04W^| z#!g+S5)9zMIp43d?~Cu8tX3niEGF3OQS2B0t^-!CG70C3pm5x`Tk1iBBP1G1P| z;3fh9jUADQTTkg(0EH3r$;Mq& z5K)^dl9gq=%MCI!Gxr5%E?QNz%4ui8Lixt!4Dw1!ssELzg@}+V0NbC>0r=-5W4kQ^ zc-ZC&ZtX!y=%bwlNvM(f8QTcNqG(l5OI=O&M&=S1vuZxsaI`;aQ;FF}AduIFj|@0C zz7$Fyl|D*LGV}KJ{n`ig8!!-jx*JP$JRTz=T{2FccQ_m>rL@*o8|0+6u0J6dOTUspfCjbBd07*qoM6N<$f-zaU5&!@I literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/chest.png b/gamma256/gameSource/chest.png new file mode 100644 index 0000000000000000000000000000000000000000..10c5a47ff9ade4970fe96017d3325c567d21bdb5 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^96&6{!2~4pw^a25sdP^l#}J9jYyEF?9x&k8Go^3Q z>6GbKJ$F_4Ccm3hvt))?);_JKqJs5Tq7Gkw^LWlAS*tA!HyBd zQ4WM43`C2&H|SyDob?I?8Y-i9wI>312ksE#E34!Hx_&!z85o>QkXgC ye6Z4KP2?dA_if>vJ=fvx?AK@&kqA&2Ci(#NSShw+OQYWa0000=Eo5L|3GYCU;?Uf|ZH$vgZ8zd@+Y8(gH(7t|Ve8Mo?Oq28mGk`xGx0V#5tSQB7gH_l?*T7fr%BM~+2qzG)eY*L}cz+{Em_La6J8lm!d@(}Ruo;iU zuF5mwVxmSgmf_a0Lq6^y_Y8X$$rIvqQ`oc9O<5yR@1F~(R!=(&QgRYof9ed5dqeMO zY<1`z4le8xb!so9Zt9_tA|Di&kZzz~!evp6QLDhaaGJ)h*jNU3RrZ_4*--&4BN>XK zm1rd>rMx3(_-MtoLaq98t(}kr^hlHEyzAlP{wXX6R9XPQh5|mlSJ6e=w;^}?E9Oiv zQMK#+F?lEe@D6s4SyICRf(^~H-g~!3E0Jdq5<&u1sWUh=G#nuLmgX6DYXHbIv=Tx> z97}s}JQm$>Aqk~a$3Axe@cB`?$IQ~(A9^T+>|^WI=UXb|eXsogpzGruSXo+o;6Ip+ z630?p&ZwA>L|o3;(C7~tzMfzH&L(quj3 " + + read platformSelection + + + if [ "$platformSelection" = "q" ] + then + exit + fi + + + # use ASCII comparison. + + if [[ "$platformSelection" > "3" ]] + then + platformSelection="" + fi + + if [[ "$platformSelection" < "1" ]] + then + platformSelection="" + fi + +done + + + +platformName="Generic" +platformMakefile="generic" + + + +case "$platformSelection" in + + + "1" ) + platformName="GNU/Linux" + platformMakefile="Makefile.GnuLinux" + ;; + + + "2" ) + platformName="MacOSX" + platformMakefile="Makefile.MacOSX" + ;; + + + "3" ) + platformName="Win32 MinGW" + platformMakefile="Makefile.MinGW" + ;; + + +esac + + + +rm -f Makefile.temp +echo "# Auto-generated by gamma256/gameSource/configure for the $platformName platform. Do not edit manually." > Makefile.temp + +rm -f Makefile +cat Makefile.temp $platformMakefile Makefile.all > Makefile + + +rm Makefile.temp + + + +exit diff --git a/gamma256/gameSource/game.cpp b/gamma256/gameSource/game.cpp new file mode 100644 index 0000000..b907aa9 --- /dev/null +++ b/gamma256/gameSource/game.cpp @@ -0,0 +1,1300 @@ +/* + * Modification History + * + * 2007-September-25 Jason Rohrer + * Created. + */ + + + +#include +#include +#include +#include +#include + + +// for memcpy +#include + + +// let SDL override our main function with SDLMain +#include + +// must do this before SDL include to prevent WinMain linker errors on win32 +int mainFunction( int inArgCount, char **inArgs ); + +int main( int inArgCount, char **inArgs ) { + return mainFunction( inArgCount, inArgs ); + } + + +#include + +#include "blowUp.h" +#include "World.h" +#include "map.h" +#include "score.h" +#include "common.h" +#include "musicPlayer.h" + +#include "minorGems/system/Time.h" +#include "minorGems/system/Thread.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SettingsManager.h" + + + +// size of game image +int width = 100; +int height = 12; +// area above game image for score +int scoreHeight = getScoreHeight(); + +// size of game image plus scoreboard +int totalImageHeight = height + scoreHeight; + +char paused = false; +char canPause = false; + + +// size of screen for fullscreen mode +int screenWidth = 640; +int screenHeight = 480; +//int screenWidth = 100; +//int screenHeight = 16; + +// blow-up factor when projecting game onto screen +// Max defined by image size vs screen size. +// This is the cap for in-game user-directed blowup adjustment. +int maxBlowUpFactor; + +// current blow-up setting +int blowUpFactor; + + +// step to take when user hits blowUp key +int blowUpStep = -1; +// flag to force update of entire screen +int blowUpChanged = true; + + +char fullScreen = true; + + +// lock down to 15 fps +int lockedFrameRate = 15; + + +// target length of game +int gameTime = 5 * 60; +//int gameTime = 30; + + +// life time that passes per frame +double timeDelta = - ( (double)width / ( gameTime * lockedFrameRate ) ); +//double timeDelta = -0.0; + + + + + + + +// the joystick to read from, or NULL if there's no available joystick +SDL_Joystick *joystick; + + + +// catch an interrupt signal +void catch_int(int sig_num) { + printf( "Quiting...\n" ); + SDL_Quit(); + exit( 0 ); + signal( SIGINT, catch_int ); + } + + + +char getKeyDown( int inKeyCode ) { + SDL_PumpEvents(); + Uint8 *keys = SDL_GetKeyState( NULL ); + return keys[ inKeyCode ] == SDL_PRESSED; + } + + + +char getHatDown( Uint8 inHatPosition ) { + if( joystick == NULL ) { + return false; + } + + SDL_JoystickUpdate(); + + Uint8 hatPosition = SDL_JoystickGetHat( joystick, 0 ); + + if( hatPosition & inHatPosition ) { + return true; + } + else { + return false; + } + } + + +int joyThreshold = 25000; + +char getJoyPushed( Uint8 inHatPosition ) { + + Sint16 x = SDL_JoystickGetAxis(joystick, 0); + Sint16 y = SDL_JoystickGetAxis(joystick, 1); + + switch( inHatPosition ) { + case SDL_HAT_DOWN: + return y > joyThreshold; + break; + case SDL_HAT_UP: + return y < -joyThreshold; + break; + case SDL_HAT_LEFT: + return x < - joyThreshold; + break; + case SDL_HAT_RIGHT: + return x > joyThreshold; + break; + default: + return false; + } + + + } + + + +// returns true if hit, returns false if user quits before hitting +char waitForKeyOrButton() { + SDL_Event event; + + while( true ) { + while( SDL_WaitEvent( &event ) ) { + switch( event.type ) { + case SDL_JOYHATMOTION: + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + case SDL_KEYDOWN: + case SDL_KEYUP: + return true; + break; + // watch for quit event + case SDL_QUIT: + return false; + break; + default: + break; + } + } + } + + + return false; + } + + + +Uint32 *gameImage; + + + +// flips back buffer onto screen (or updates rect) +void flipScreen( SDL_Surface *inScreen ) { + + // unlock the screen if necessary + if( SDL_MUSTLOCK( inScreen ) ) { + SDL_UnlockSurface( inScreen ); + } + + if( ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) { + // need to flip buffer + SDL_Flip( inScreen ); + } + else if( !blowUpChanged ) { + // just update center + + // small area in center that we actually draw in, black around it + int yOffset = ( inScreen->h - totalImageHeight * blowUpFactor ) / 2; + int xOffset = ( inScreen->w - width * blowUpFactor ) / 2; + + SDL_UpdateRect( inScreen, xOffset, yOffset, + width * blowUpFactor, + totalImageHeight * blowUpFactor ); + } + else { + // update the whole thing + SDL_UpdateRect( inScreen, 0, 0, inScreen->w, inScreen->h ); + + // reset flag + blowUpChanged = false; + } + + } + + + +void lockScreen( SDL_Surface * inScreen ) { + // check if we need to lock the screen + if( SDL_MUSTLOCK( inScreen ) ) { + if( SDL_LockSurface( inScreen ) < 0 ) { + printf( "Couldn't lock screen: %s\n", SDL_GetError() ); + } + } + } + + + +void flipGameImageOntoScreen( SDL_Surface *inScreen ) { + + if( blowUpChanged + && + ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) { + + // blow up size has changed, and we are double-buffered + // flip onto screen an additional time. + // This will cause us to black-out the background in both buffers. + + lockScreen( inScreen ); + + // when blow-up factor changes: + // clear screen to prepare for next draw, + // which will be bigger or smaller + SDL_FillRect( inScreen, NULL, 0x00000000 ); + + blowupOntoScreen( gameImage, + width, totalImageHeight, blowUpFactor, inScreen ); + + flipScreen( inScreen ); + } + + lockScreen( inScreen ); + if( blowUpChanged ) { + SDL_FillRect( inScreen, NULL, 0x00000000 ); + } + blowupOntoScreen( gameImage, + width, totalImageHeight, blowUpFactor, inScreen ); + + flipScreen( inScreen ); + + // we've handled any blow up change + blowUpChanged = false; + } + + + + + + + +SDL_Surface *screen = NULL; + + +// play a complete game, from title screen to end, on screen +// returns false if player quits +char playGame(); + + + +void createScreen() { + if( screen != NULL ) { + // destroy old one first + SDL_FreeSurface( screen ); + } + + int displayW = width * blowUpFactor; + int displayH = totalImageHeight * blowUpFactor; + + + Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF; + if( fullScreen ) { + flags = flags | SDL_FULLSCREEN; + + // use letterbox mode in full screen + displayW = screenWidth; + displayH = screenHeight; + } + + + + screen = SDL_SetVideoMode( displayW, displayH, + 32, + flags ); + + if ( screen == NULL ) { + printf( "Couldn't set %dx%dx32 video mode: %s\n", displayW, + displayH, + SDL_GetError() ); + } + } + + + +int mainFunction( int inArgCount, char **inArgs ) { + + // let catch_int handle interrupt (^c) + signal( SIGINT, catch_int ); + + + Uint32 initFlags = + SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_NOPARACHUTE; + + #ifdef __mac__ + // SDL_Init is dreadfully slow if we try to init JOYSTICK on MacOSX + // not sure why---maybe it's searching all devices or something like + // that. + // Couldn't find anything online about this. + initFlags = + SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE; + #endif + + if( SDL_Init( initFlags ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_ShowCursor( SDL_DISABLE ); + + + // read screen size from settings + char widthFound = false; + int readWidth = SettingsManager::getIntSetting( "screenWidth", + &widthFound ); + char heightFound = false; + int readHeight = SettingsManager::getIntSetting( "screenHeight", + &heightFound ); + + if( widthFound && heightFound ) { + // override hard-coded defaults + screenWidth = readWidth; + screenHeight = readHeight; + } + + printf( "Screen dimensions for fullscreen mode: %dx%d\n", + screenWidth, screenHeight ); + + // set here, since screenWidth may have changed from default + maxBlowUpFactor = screenWidth / width; + + // default to max + blowUpFactor = maxBlowUpFactor; + + + char fullscreenFound = false; + int readFullscreen = SettingsManager::getIntSetting( "fullscreen", + &fullscreenFound ); + if( fullscreenFound ) { + fullScreen = readFullscreen; + } + + printf( "Starting game in " ); + if( fullScreen ) { + printf( "fullscreen" ); + } + else { + printf( "windowed" ); + } + printf( " mode.\n" ); + + + + createScreen(); + + + // try to open joystick + int numJoysticks = SDL_NumJoysticks(); + printf( "Found %d joysticks\n", numJoysticks ); + + if( numJoysticks > 0 ) { + // open first one by default + joystick = SDL_JoystickOpen( 0 ); + + if( joystick == NULL ) { + printf( "Couldn't open joystick 0: %s\n", SDL_GetError() ); + } + int numHats = SDL_JoystickNumHats( joystick ); + + if( numHats <= 0 ) { + printf( "No d-pad found on joystick\n" ); + SDL_JoystickClose( joystick ); + joystick = NULL; + } + } + else { + joystick = NULL; + } + + + + #ifdef __mac__ + // make sure working directory is the same as the directory + // that the app resides in + // this is especially important on the mac platform, which + // doesn't set a proper working directory for double-clicked + // app bundles + + // arg 0 is the path to the app executable + char *appDirectoryPath = stringDuplicate( inArgs[ 0 ] ); + + printf( "Mac: app path %s\n", appDirectoryPath ); + + char *appNamePointer = strstr( appDirectoryPath, + "Passage.app" ); + + if( appNamePointer != NULL ) { + // terminate full app path to get parent directory + appNamePointer[0] = '\0'; + + printf( "Mac: changing working dir to %s\n", appDirectoryPath ); + chdir( appDirectoryPath ); + } + + delete [] appDirectoryPath; + #endif + + + + + // load graphics only once + loadWorldGraphics(); + + + setMusicLoudness( 0 ); + startMusic( "music.tga" ); + + // keep playing until player quits + while( playGame() ) { + } + + stopMusic(); + + + destroyWorldGraphics(); + + + if( joystick != NULL ) { + SDL_JoystickClose( joystick ); + } + + SDL_Quit(); + + return 0; + } + + +/* +// TEMP +#include "minorGems/graphics/converters/TGAImageConverter.h" + +#include "minorGems/io/file/File.h" + +#include "minorGems/io/file/FileInputStream.h" +// END TEMP +*/ + + +char playGame() { + + + int currentSpriteIndex = 2; + + double playerX, playerY; + + double maxPlayerX = 0; + + int score = 0; + + + // the gem that marks chests containing points + int specialGem = time( NULL ) % 4; + + int exploreScore = 0; + int exploreSubPoints = 0; + // make sure explore score never goes down + int maxExploreScore = 0; + + int chestScore = 0; + + // track whether we ever met the spouse + // separate from World's haveMetSpouse() + char knowSpouse = false; + + + + initWorld(); + initScore(); + + + + // room at top for score + int totalGameImagePixels = width * totalImageHeight; + + gameImage = new Uint32[ totalGameImagePixels ]; + + int i; + + // fill with black + for( i=0; igetWidth() * titleImage->getHeight(); + + double *titleRed = titleImage->getChannel( 0 ); + double *titleGreen = titleImage->getChannel( 1 ); + double *titleBlue = titleImage->getChannel( 2 ); + + Uint32 *titlePixels = new Uint32[ numTitlePixels ]; + + for( int i=0; i> 16) & 0xFF ) / 255.0; + muralG[ index ] = ( (sample >> 8) & 0xFF ) / 255.0; + muralB[ index ] = ( sample & 0xFF ) / 255.0; + } + } + } + + + File tgaFile( NULL, "mural.tga" ); + FileOutputStream tgaStream( &tgaFile ); + + TGAImageConverter converter; + + converter.formatImage( &mural, &tgaStream ); + + // END TEMP + */ + + + + while( !done ) { + + if( getKeyDown( SDLK_s ) ) { + stepDX = false; + } + else { + stepDX = true; + } + + // trick: + // dX advances linearly, which results in smooth motion + // instead of single-pixel tick-tick-tick + // However, this results in smudgy sampling of sprites, which + // makes them hard to see + // If we map our sloping dX, which looks like this: / + // to a constant between integers with a steep slope up to the next + // integer, like this: _/ + // Then we get to look at unsmudged sprites between steps, but + // still see a smoothed-out tick + + double drawDX = floor( dX ); + double floorWidth = 0.5; + + if( dX - drawDX > floorWidth ) { + // slope + double slopeValue = + ( dX - drawDX - floorWidth ) / ( 1 - floorWidth ); + + // us trig function to avoid jagged transition from floor to + // slope to next floor + + drawDX += -0.5 * cos( slopeValue * M_PI ) + 0.5; + } + + if( !stepDX ) { + // disable smoothed stepping + drawDX = dX; + } + + + for( int y=0; y maxDistance ) { + cappedDistanceFromPlayer = maxDistance; + } + if( cappedDistanceFromPlayer < -maxDistance ) { + cappedDistanceFromPlayer = -maxDistance; + } + + // the world position we will sample from + double worldX = x; + + // zone around player where no x compression happens + int noCompressZone = 10; + + + if( trueDistanceFromPlayer > noCompressZone ) { + + worldX = + x + + //(width/8) * + // use true distance as a factor so that compression + // continues at constant rate after we pass capped + // distance + // otherwise, compression stops after capped distance + // Still... constant rate looks weird, so avoid + // it by not letting it pass capped distance + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + // simpler formula + // actually, this does not approach 0 as + // cappedDistanceFromPlayer approaches 0, so use tan + // instead + // ( trueDistanceFromPlayer / 2 ) * 100 + // / pow( ( width - cappedDistanceFromPlayer ), 1.6 ); + } + else if( trueDistanceFromPlayer < - noCompressZone ) { + worldX = + x + + //(width/8) * + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( - cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( - cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + //( trueDistanceFromPlayer / 2 ) * 50 + /// ( width + cappedDistanceFromPlayer ); + } + else { + // inside no-compresison zone + worldX = x; + } + + + //int worldX = x; + + worldX += drawDX; + + if( worldX > maxWorldX ) { + maxWorldX = worldX; + } + if( worldX < minWorldX ) { + minWorldX = worldX; + } + + + int worldY = (int)floor( y + dY ); + + + // linear interpolation of two samples for worldX + int intWorldX = (int)floor( worldX ); + + double bWeight = worldX - intWorldX; + double aWeight = 1 - bWeight; + + + Uint32 sampleA = + sampleFromWorld( intWorldX, worldY, aWeight ); + Uint32 sampleB = + sampleFromWorld( intWorldX + 1, worldY, bWeight ); + + + + Uint32 combined = sampleB + sampleA; + + + gameImage[ ( y + scoreHeight ) * width + x ] = combined; + + } + } + + drawScore( gameImage, width, height, score ); + + + if( isPlayerDead() ) { + // fade to title screen + double titleWeight = + titleFadeFrame / (double)( numTitleFadeFrames - 1 ); + double gameWeight = 1 - titleWeight; + + // wipe from left to right during fade + int wipePosition = (int)( titleWeight * width ); + + // fade out music while we do it + setMusicLoudness( 1.0 - titleWeight ); + + + + for( i=0; i> 16 & 0xFF; + unsigned char gameGreen = gamePixel >> 8 & 0xFF; + unsigned char gameBlue = gamePixel & 0xFF; + + Uint32 titlePixel = titlePixels[i]; + + unsigned char titleRed = titlePixel >> 16 & 0xFF; + unsigned char titleGreen = titlePixel >> 8 & 0xFF; + unsigned char titleBlue = titlePixel & 0xFF; + + unsigned char red = + (unsigned char)( + gameWeight * gameRed + titleWeight * titleRed ); + unsigned char green = + (unsigned char)( + gameWeight * gameGreen + titleWeight * titleGreen ); + unsigned char blue = + (unsigned char)( + gameWeight * gameBlue + titleWeight * titleBlue ); + + + int x = i % width; + if( x <= wipePosition ) { + gameImage[i] = red << 16 | green << 8 | blue; + } + + } + + if( !paused ) { + + titleFadeFrame ++; + } + + + if( titleFadeFrame == numTitleFadeFrames ) { + done = true; + } + } + + + flipGameImageOntoScreen( screen ); + + + // done with frame + double newTimestamp = Time::getCurrentTime(); + + double frameTime = newTimestamp - lastFrameTimeStamp; + + double extraTime = 1.0 / lockedFrameRate - frameTime; + + if( extraTime > 0 ) { + Thread::staticSleep( (int)( extraTime * 1000 ) ); + } + + // start timing next frame + lastFrameTimeStamp = Time::getCurrentTime(); + + + + int spouseX, spouseY; + getSpousePosition( &spouseX, &spouseY ); + + + int moveDelta = 1; + + if( isPlayerDead() ) { + // stop moving + moveDelta = 0; + } + + if( knowSpouse && isSpouseDead() ) { + + // player moves slower + // toggle motion on this frame + movingThisFrame = ( frameCount % 2 == 0 ); + } + + if( getKeyDown( SDLK_p ) && canPause ) { + paused = true; + } + if( getKeyDown( SDLK_o ) ) { + paused = false; + } + if( paused && getKeyDown( SDLK_i ) ) { + stepAnimations(); + } + + + if( getKeyDown( SDLK_b ) ) { + // adjust blowup factor + blowUpFactor += blowUpStep; + + if( blowUpFactor > maxBlowUpFactor ) { + blowUpStep *= -1; + blowUpFactor = maxBlowUpFactor - 1; + } + + if( blowUpFactor < 1 ) { + blowUpStep *= -1; + blowUpFactor = 2; + } + + if( fullScreen ) { + // force redraw of whole screen + blowUpChanged = true; + } + else { + // create a new screen using the new size + createScreen(); + } + + } + + if( getKeyDown( SDLK_f ) ) { + // toggle fullscreen mode + fullScreen = ! fullScreen; + + // create a new screen surface (and destroy old one) + createScreen(); + } + + + + if( getKeyDown( SDLK_LEFT ) || getJoyPushed( SDL_HAT_LEFT ) ) { + char notBlocked = + !isBlocked( (int)( playerX - moveDelta ), (int)playerY ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX - moveDelta, spouseY ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + playerX -= moveDelta; + + if( playerX < 0 ) { + // undo + playerX += moveDelta; + } + else { + // update screen position + dX -= moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerX / 2 ) % 2 == 0 ) { + currentSpriteIndex = 6; + } + else { + currentSpriteIndex = 7; + } + } + } + } + else if( getKeyDown( SDLK_RIGHT ) || getJoyPushed( SDL_HAT_RIGHT )) { + char notBlocked = + !isBlocked( (int)( playerX + moveDelta ), (int)playerY ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX + moveDelta, spouseY ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + dX += moveDelta; + + playerX += moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerX / 2 ) % 2 == 0 ) { + currentSpriteIndex = 3; + } + else { + currentSpriteIndex = 2; + } + } + } + else if( getKeyDown( SDLK_UP ) || getJoyPushed( SDL_HAT_UP ) ) { + char notBlocked = + !isBlocked( (int)playerX, (int)( playerY - moveDelta ) ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX, spouseY - moveDelta ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + playerY -= moveDelta; + + if( playerY < 0 ) { + // undo + playerY += moveDelta; + } + else { + // update screen position + dY -= moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerY / 2 ) % 2 == 0 ) { + currentSpriteIndex = 0; + } + else { + currentSpriteIndex = 1; + } + } + } + } + else if( getKeyDown( SDLK_DOWN ) || getJoyPushed( SDL_HAT_DOWN )) { + char notBlocked = + !isBlocked( (int)playerX, (int)( playerY + moveDelta ) ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX, spouseY + moveDelta ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + dY += moveDelta; + + playerY += moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerY / 2 ) % 2 == 0 ) { + currentSpriteIndex = 5; + } + else { + currentSpriteIndex = 4; + } + } + } + + setPlayerPosition( (int)playerX, (int)playerY ); + setPlayerSpriteFrame( currentSpriteIndex ); + + // may change after we set player position + getSpousePosition( &spouseX, &spouseY ); + + + // check for events to quit + SDL_Event event; + while( SDL_PollEvent(&event) ) { + switch( event.type ) { + case SDL_KEYDOWN: + switch( event.key.keysym.sym ) { + case SDLK_q: + case SDLK_ESCAPE: + done = true; + quit = true; + break; + default: + break; + } + break; + case SDL_QUIT: + done = true; + quit = true; + break; + default: + break; + } + } + + //t +=0.25; + frameCount ++; + + // other animations run independent of whether player is moving + if( !paused && frameCount % 6 == 0 ) { + stepAnimations(); + } + + if( ! isPlayerDead() && ! paused ) { + // player position on screen inches forward + dX += timeDelta; + } + + double age = ( playerX - dX ) / width; + + setCharacterAges( age ); + + if( age >= 0.85 ) { + dieSpouse(); + } + if( age >= 0.95 ) { + diePlayer(); + } + + + if( isChest( (int)playerX, (int)playerY ) == CHEST_CLOSED ) { + + openChest( (int)playerX, (int)playerY ); + + int chestX, chestY; + + getChestCenter( (int)playerX, (int)playerY, &chestX, &chestY ); + + if( getChestCode( (int)playerX, (int)playerY ) & + 0x01 << specialGem ) { + + // reward player + chestScore += 100; + + + startPrizeAnimation( chestX, chestY ); + } + else { + startDustAnimation( chestX, chestY ); + } + + } + + + int distanceFromSpouse = (int) sqrt( pow( spouseX - playerX, 2 ) + + pow( spouseY - playerY, 2 ) ); + + + if( ! haveMetSpouse() && + ! isSpouseDead() && + distanceFromSpouse < 10 ) { + + meetSpouse(); + + knowSpouse = true; + + startHeartAnimation( + (int)( ( spouseX - playerX ) / 2 + playerX ), + (int)( ( spouseY - playerY ) / 2 + playerY ) - 2 ); + } + + + // stop after player has gone off right end of screen + if( playerX - dX > width ) { + dX = playerX - width; + } + + int exploreDelta = 0; + + if( playerX > maxPlayerX ) { + exploreDelta = (int)( playerX - maxPlayerX ); + maxPlayerX = playerX; + } + + int spouseExploreFactor = 2; + + if( haveMetSpouse() ) { + // exploring worth more + exploreDelta *= spouseExploreFactor; + } + + exploreSubPoints += exploreDelta; + + + exploreScore = (int)( exploreSubPoints / 10 ); + + if( haveMetSpouse() ) { + // show explore score contribution in jumps + exploreScore = + ( exploreScore / spouseExploreFactor ) + * spouseExploreFactor; + // note: + // this can cause our score to go down (to the previous + // jump) as we transition from not having a spouse to + // having one. + // we fix this below with maxExploreScore + } + + if( exploreScore < maxExploreScore ) { + // don't let it go down from max + exploreScore = maxExploreScore; + } + + + score = chestScore + exploreScore; + if( exploreScore > maxExploreScore ) { + maxExploreScore = exploreScore; + } + + + } + + unsigned long netTime = time( NULL ) - startTime; + double frameRate = frameCount / (double)netTime; + + printf( "Max world x = %f\n", maxWorldX ); + printf( "Min world x = %f\n", minWorldX ); + + printf( "Frame rate = %f fps (%d frames)\n", + frameRate, frameCount ); + + printf( "Game time = %d:%d\n", + (int)netTime / 60, (int)netTime % 60 ); + + fflush( stdout ); + + + delete titleImage; + + delete [] gameImage; + delete [] titlePixels; + + + destroyWorld(); + destroyScore(); + + + if( quit ) { + return false; + } + else { + return true; + } + + } + diff --git a/gamma256/gameSource/graphics/characterSprite.tga b/gamma256/gameSource/graphics/characterSprite.tga new file mode 100644 index 0000000000000000000000000000000000000000..451b471609fc45bf3bc25278f0fc013bdca1e3bb GIT binary patch literal 8418 zcmeI0&28K;5QPP&iQKaVcUknkEgTkOpF{0JJAe3lw(b>acbxlO~B9P41btEd#7eh?kpx9eB zXt&$B3w3OJNg0gL;rO`QALE;D9}Z)%w+xT_J&*g*+Q-j7d76+8WD|P#{RfXBH;&_} zM+DR(0)op%qI*D1qTznvX-;8DS5`F|k8RHHe1vT^HMSulOrGJC9?{!3K{sFSdCr9% zbI~l-SUZtEIz&J{qIC!!EJq?yI2<5HBnpQUAq5*vc~%nw-Jsy1rOMIrS%q58ORB!K zmxyMmCcz#b8DF4Ts#&np<*64?(6`%L9!p6?peP7aJeHCnd04y0EEII}^?|3B5)@_$ zz9mPXFiQc!MM*E^Vjt*1QM0gj(vf%8Qy58d-R+tXuTh$i1Z!w=1oE?Auh z&Xo@a4bGCgF})U0Os^&M#GZ{EP-{;SSc|-|o!pzRg@OpRlFb)_++thfwGgKO#cQF2 z!mU+6&1*qwrMAB8cG7AtHN-nAH6*oC8_EHtYRe1@yB)w)`JlnqL$ZeMWQxW#G80`2 zl3D7Q$LTsE+A)t8P>l4q(6pm({0BE&I9zVWh!~8TEug5`5?bupa7yS%$^AHmcGnsS zz3bkXHEWYoL)n})S3eRdn>QicgQy89n;Vi@y4Gw#1eEofsv5!{l^U{U1-ZTomZdmL z%gfS*5AS4Tz8Pr$qk`p7^%7tF9xj4S`syUUd`Qd0$MkK!R7DiU7550i_8=Ca2O-!9 zL7}pZj^NscNXdvu9jup5XZu`8zh2|4Y2C7jBtVT6MCU?Kz!hi7Y+Aa^<)5)80#Z=a zg%5P)Gxc*IY;I8GiP8#f%NA-osq|{u8uEXfEZYU@{ESuBiyy0=qO6B0|GG3J_v{;v z8B*43eynbDGq9V|QN68<;4`HO5dx(N{n_8JSHbG!Q?omItc4Xw8~WC@9f;|bL3je!j3%fYkM zkXosEp_r1z+7_(0wVvhgVMAuzGQ&!?MMO?Ubv{~wlwnmn78=fab>dsGwgN%HqVdp( zMCS6Kx0MkYY(ld3OvrosrVTqoBWgBA1Qffmou+p_i4)Y)EGHN#Wi(aM8ftZVEg;m| za}Tv5B^z`@dECc75-E?jq5RXJ2`P^o5)oZ%RwE_?vSw?D*HmiAnib^wDp;1{?-cAW Dqj-N7 literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/graphics/characterSpriteSad.tga b/gamma256/gameSource/graphics/characterSpriteSad.tga new file mode 100644 index 0000000000000000000000000000000000000000..066c6c4e7583bff1c396ce4ec1d2fa4f5147fbf2 GIT binary patch literal 1938 zcmd6n!EwSc5JffQLJ28=W_+U&9aDnO%y3I1I?xKmm}mWHStQwpLr63lv;Nw(_UYS% zunT4E!!dk@7~?pO=f{y!dPX0HL3LGrS4Y&KMESM_x);%H$ucFj*J)w6%yc-oyApnU zUzp|Y{(DZ7*SXc?Nb;%tWFk%>aBcN#9zIz$B(rkgX*F8Fno5zNI%wkZ2r5CLfl?lc zHQWCGlq+`4qV3(+DaQG=^aBL+3XKFoRcPo!ql&;k2VZm=twGt$ZP&K^ns-}3OBv0# zGrI#6I)PvKhg=z|_JyA5pXr-d+?pj_XmY_*n=bGC16h(gK3@qk)F)T*x?h57(FE1y zqKB?-{R_?7F#VledH0T4=rrHHpMrY+JtW__jFdn&bw8!*SbUX_*ZnG)9WHWt>fA&})2F$Ka{{b7{-OHXi}<`a$!a>^LHDAuRM@mE?40NK6Y%BK+WuW~i13(* zw+{{uKv)4VK{3HiSYv_(U;@I-eZh!+42*cVQwX5iwRlDZCO&ct3tT6ieyV0M)oQMJ e8f50a3>xf&VOd=)NV%oFpgti>fC;<%rlb>TO2>Hs literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/graphics/chestPrize.tga b/gamma256/gameSource/graphics/chestPrize.tga new file mode 100644 index 0000000000000000000000000000000000000000..7b806c6e6962db16bb1e26d9fbede0aee54ebadd GIT binary patch literal 4866 zcmeHI+l|675VWY^J}HMj?)&;kRrkv;HBbXRon6Mw~7xGB5!9> zuu*G-8h{6w2H)3KQVfjqqJaDS7V(#mQUaKGQG9aZs}!(khViBdoYugS6&9aG3zo)7 z>Euko@JI2aWJz8%ot+!9SJgGX53ap@M%5s4^BGZBWD(gBMxaEc1=ST|R^f)HGT8!A z&)iP1)G^9^zpQ|fANai=(@QLTyVdq%D&Q7qU;9wA0KAWPy#dNbuZYf@K_@hdn*BPp zRV0Rg5&8bz3f`RmvHDd89QjMR!`|L6!&mn1guZg{O~e7wCiZ(f1I+Iion^_xFwI7a z&z8z5G9##ZI4xm}u%v9Y8U%)?$iN{_Q8`{6{gD9ne(7J;N3B-%-#VeBC_MwIV8)U@ zrTN9_>Qf2j*e(qLYZvaGO|z29Jgu}Pu^rj_rF}Oaw*n1kwt7U!ShflV02-EU!pGM5 L4>51q@F~n69*Z>? literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/graphics/heart.tga b/gamma256/gameSource/graphics/heart.tga new file mode 100644 index 0000000000000000000000000000000000000000..7f01f230de63ec2d359dc2115874c9f159e93537 GIT binary patch literal 4050 zcmeH_T?zs*41|j(@X>>L|Ep*qI1H0C&BB%yu@5p$zscV^#%+AgeLTlwye>N9NM(nc z7RFeogGL}mV4OXr@iaI~Rm1LLhBb1EV1~6|xH7}qfn|gN*D-=x$Z3pV5)sSkS1;H9 zJ-vZuLf+ESf^n9l0f1f97J+L^3ePMB94$*&EZ@ebYnLYDhX8=57{Svffr?GBsg2Xq z+Bp4#**g}%E^3RwwIzjTmI6*KyLsCKmx$MNQPQehQ_HnMp0T5^?RH;vo4tRqsE!4& zi`pV^ZAsz6!o0O1*R(OoCfC&R+d*DhNB{OVHcfZvZ^mC?*$>0bDu(%iSXTK1*uWbp CpEdvh literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/graphics/numerals.tga b/gamma256/gameSource/graphics/numerals.tga new file mode 100644 index 0000000000000000000000000000000000000000..bcd171c821da67c53ece4dcbfef098dafceebfe6 GIT binary patch literal 459 zcmZXR!3}^g3Q)`MG15ezUY7o(gGhy+dxX7h!j8({H#PP;mn+wyDx8?)$S@t zNUSw-Y+w7lXZy}5yWN}JTHfux?B4DUhr`|7-SM)VPN(zv`~v9(OCAYSY5V=&9JjeW zQTj_XqFYF><+dtQ=8sF{mh@QVd`3K+=SmZ6F5WDm?S@A8>5^-=b)_}+aS8QS#kj}& z6NiScX*dGk#_4dReSeQ|Y!Wj*e*N+O^S5QH^5Fm^Pg?>cPm_S;SxL+9&tGSNpT7TY zOXwm2B#og2kjxj#`eSZG+F`fj8Pd#cNCS0^2I?9ORHLyMo*_+XNK+cpJ{{OsbK2asG=0KImN24wX*3!m(r8o~%caF6p&{j6XtXKl z{D7)7;5`_m+N?bwn?YwlV0QyEa5*l5A zbhqwB0?6%9*TOC6kPL=~6!)rDNrz+=4U8d*nzePw4FN!*nYXhWFX|w~} z`YphAu)y;)TE>$=-Dy-;Sz}$tX*AliB-i>*SvMLR%+n+`m=cN1Z&hSk5C1tRk;Bk$ zsKb8_)w;|fTRF|TRbmE*u*`GzT5GxO#i8rop#+L2k}$e4u$D`1)6CVtTCPgcs5y!? zuZP#+)x8?nlgYWox;ZqY8y3THp;)(Cju1-NgV1!h?qvqn-C8d8pwYIY*{qusdn|#K z*AHejVR9ohsG&6Fys<7wY-V~Z{^8A<9IJa*ErF!9#H0yaS6t8*;Hp)cz*Sj(LGiGi z>?lh>@eCzUlq!jn=4aNCMjJ}fs+1%JQa2i1)0_4?mjt3GkUA3Tt%^*)x?C#;P81u& z)oes;l3!g$leQCX=w8oi=r@Db7lG&|=~^H$pcw+NL*cc`j8FNF53F zRz)UP5Z@+>4eUWI+f8CL3D*g<0bctb0+78>HyT}l+y5cl3nvCJA(9Ug2?R9})>M&* za*_AE9*)<_1c6zC*}R3V#8ht~l50g-H<~ExCW*3E`uAyMShq>{;oMgDW-nE0+eEry zNkkgMe-2I{VoXB2uHu3&`Wx#qIZK*#s{~|gT?1*kI43kzfFv-Kv|J=nbiIYV4xu3} bX5*VVmnwM+ZKtp9^`PoY%1F+Qc609^Sq^f= literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/graphics/tileSet.tga b/gamma256/gameSource/graphics/tileSet.tga new file mode 100644 index 0000000000000000000000000000000000000000..5648fad697879ea861d1749d4f830064d7c70cb3 GIT binary patch literal 77538 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 Iz@HO%01cqMv;Y7A literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/heart.png b/gamma256/gameSource/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..3c7b9d002a7242c096ebd070e80b7c98088c3a10 GIT binary patch literal 265 zcmV+k0rvihP) zOAf#w2t>hn|0~l~jShpKMVo$f)gq+e@Th8Lva*sauCE5D;zPZO$LgJ0ht%H|rxHu4 zI5AB#FD;h;mrAQG24vX)iKsRm4zCyh=rEGvlo)OnuTg0}rIV{csN~5B@36sA0B=yNb9k0F)c= z+Ruzw8h}7AYI*cs<4m!41tb7PG1UP$far7?00i@E3ZOFi764-2w+MFt-&K@{gwg@x P00000NkvXXu0mjftUGJz literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/Icon.png b/gamma256/gameSource/iPhone/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f8483855ef3d7ccb96190cc7d91129037d49143 GIT binary patch literal 506 zcmVlg;`?~3Ua_kCoFt~SW;Eps92zy(u9oOHazSTm8B+u1<`X%3m z`itmev*4#U>Z=n>ac$WTX&{saz*h%`MpfTZ|1Ff%?*u^_2-OSXIIi|677KK}G{i9S zCor%7>)>3!;(fi0#Jyg#&$Ur?fU$+oN6mT9##G0ZDb2i@_Rp?R1MsBVUgo$s#9qMI z8)@M8d%?B_BfrU<>$Xu^BbDM83g$8i?tT}L27>j1*$e<6igd$zjf?3NiCHcmHJ@(x z2P;Z@%Wj~~WKy_fle#v-=FRI8D3;5ofOt5{7$2d!*NutEydS*bX znm#wgkZ1VSP|csKm1RGqfk1VD^B;(k)aeOghm4YX!C#sOYSCID2EqT+xi(4zn=Vi> w57@ec)5jFU?X2{!5r^gh0)apv5C~R$0^S{caA#Zj!vFvP07*qoM6N<$f^$sgcK`qY literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/Info.plist b/gamma256/gameSource/iPhone/Info.plist new file mode 100644 index 0000000..a9d47d2 --- /dev/null +++ b/gamma256/gameSource/iPhone/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + Icon.png + CFBundleIdentifier + net.sf.hcsoftware.${PRODUCT_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 3.1 + LSRequiresIPhoneOS + + NSMainNibFile + MainWindow + UIStatusBarHidden + + + diff --git a/gamma256/gameSource/iPhone/LargeIcon.png b/gamma256/gameSource/iPhone/LargeIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..e892c8482ed63062bc2fb8521e630d76af6f7f7e GIT binary patch literal 2114 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_lmJPZ!6Kid%2*Zp@bsl{ru- z|EcTIQjea1>dct}!EHXVZzKhN3I~Y3k6m?&dyNHi^mMP4EWc%o*~A2xIzy()Oc7iu zo3--Iae0PyZ$9gP`&>Q$eALsQd&|rJz4F|->^d{hqQM6qJP6sl(>LP0MadSmq77wH zXZ}W=c^h@XG}1_lNNU?714jU1-K$yN81?jJQ>xVTrD zJ-eOtZqiFX-7nAb-CwTzl2#UT;_bJOFV8O&H+{JD-xC&08%VZv!_K<(C(8>TpE!6c zaToX6Rc1v?zgE6@v+U%%Xlt+Uv+kYgn>_dS;e)FWTztju@4Wt{=ab1_W1rp6_hVpa z6=GmuWN=_$5MW?nA&c2C>1%OZjIhU(?PZCw-LGGtsQs^Ycj~tOn74X)X~$2Vd;9cd_{np*HnY^Mw?DkQ8|WP}qYNp; zBVV3>e!VQw_VbB@|7I-PeZi_^>ExBs%bWY{{+ktSnZEb_<=9uhk4-P#CM{R#m_eb6Gz%3!`BNU&*VByU;b};=7S7( z_nGzkEg2a^yvR))@HFLhO`d1lub)qi?rzQ9x#hnFzfAVd7?wV>*|F?>-+p{FDA;nc zLdGKMu1TxS%*hu`itdYjP6@BF>)o>~J$+(v^=Gxu@6FX28l0$+H+OC0ytC-z5xF<< zj}6~1$(>7$DEtJB!aJ*$^~vPgY|MW^jZA$Zw0`HESI^m-ORjHwdMR&e?dxSLmEEQN zmE5cA1gj87M?vU8uhx_x9iXq|lv{8=4>QZ^9@Z8k(nk*544n zH}+@Ofxq8Z%hm+ESN?$Bhvyt#?{un|d(SoN z-FG(5-?aInRZix&{cqmGOhGCZm>3+yit3mj{d;;U+-aTPsyWZ5ee$yQv2L|IeonVy z>DR8)wkyBYeJ=|*#|zhvS{OxJ{F$!y>O^@^**C4T@4`fOUz$?>Ti|`m-I&)Rd2h literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/MainWindow.xib b/gamma256/gameSource/iPhone/MainWindow.xib new file mode 100644 index 0000000..5ec568a --- /dev/null +++ b/gamma256/gameSource/iPhone/MainWindow.xib @@ -0,0 +1,237 @@ + + + + 528 + 9F33 + 677 + 949.34 + 352.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + + 1316 + + YES + + + 1298 + {320, 480} + + + 3 + MQA + + 2 + + + NO + + + + {320, 480} + + + 1 + MSAxIDEAA + + NO + NO + + + + + YES + + + delegate + + + + 4 + + + + window + + + + 5 + + + + view + + + + 11 + + + + + YES + + 0 + + YES + + + + + + 2 + + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + 3 + + + + + -2 + + + + + 10 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.CustomClassName + 10.IBPluginDependency + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 3.CustomClassName + 3.IBPluginDependency + + + YES + UIApplication + UIResponder + MyView + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + YES + + YES + + + YES + + + {{411, 129}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + gameWindowAppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 11 + + + + YES + + MyView + UIView + + IBProjectSource + gameWindowAppDelegate.h + + + + MyView + UIView + + IBUserSource + + + + + gameWindowAppDelegate + NSObject + + YES + + YES + view + window + + + YES + MyView + UIWindow + + + + + + + 0 + gameWindowApp.xcodeproj + 3 + + diff --git a/gamma256/gameSource/iPhone/arrows.png b/gamma256/gameSource/iPhone/arrows.png new file mode 100644 index 0000000000000000000000000000000000000000..fa995385dfa5d5f68b8664586c0f44ed7780e8c0 GIT binary patch literal 1109 zcmZvaduYyK6vvNuE{l!*fkJB2<1*%Ma@mBo?6}NavY{3s`QzB!tz8PaMX8fn7PGiTcK z%)oz0YwlmFNilE~ALkszI%nJVr9_(SS(I3i*dc+_4qk%^ZD%)-Xon1B@x+Q`Jz#JY zTW;iyuoqHBvCbio)fxL55MujdB0(91z>a$m1r{K-XDc39O#&Dw`ZWy?bOz{@f;lr5 z=iT{OU6;ITOiFG;zqHvWx~|#1IqzNSh?56zbWJZx%e?nuQ16z;q?YHsQxo2fpP1I? z(wYUwejjhzc4+*KpcJ0Z4wPQKdFJq>j0qdKcHzgAq5ZPo{K$ARxB17dyTv7^ANT#e ztZeY*u@?t#TDS4k%ZHtY)!t7Uy6<~cU$~oJUD#N|x7Sap3|u_=^}^b+!i~YDq0FaC zN}i3JGkQ^vgz0*2PzyK-6g@TU$Wmfhqr{%9@S^J4SCS;l@1JS zEgR4?D}1DO_0;#*gEcFw_O?~NZVR?<`!=uYNoMHXnCeGj_Sy_7`CSh)V&V`vPcK!iyjSk)b literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/arrows.tga b/gamma256/gameSource/iPhone/arrows.tga new file mode 100644 index 0000000000000000000000000000000000000000..6b99df484baee4c96f433f9123fe79720e17b90d GIT binary patch literal 3573 zcmeH{u}uR(5JgusfJ8(_;vUF|aYO-#66k<{7Kot^2N2MQYoG`7&)@Uzdv!Kv5g}o% z#g^tU^}6#nE3n~4($>cb9=UE_d>|{M5E0nmI^vJZdBG6y?*>`Ql^sX(ji>exKj3^*}=^ZV~}aL^>rA?KO%T;KW0YDg#R$&1tB&XXRQmR1D% zE5DmnF}v@g~mgeri z%jd_Oxv#TLD@AI>Q|8y%N9Y&T06|j@QNC{i|L55yqBz=S{T-!8**lKVUy)Cp4eMjk H1hMlC@I{e^ literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/blowUp.cpp b/gamma256/gameSource/iPhone/blowUp.cpp new file mode 100644 index 0000000..da4271e --- /dev/null +++ b/gamma256/gameSource/iPhone/blowUp.cpp @@ -0,0 +1,84 @@ +#include "blowUp.h" + +#include + + + +void blowupOntoScreen( Uint32 *inImage, int inWidth, int inHeight, + int inBlowFactor, Uint32 *inScreenPixels, + int inScreenWidth, int inScreenHeight ) { + + int newWidth = inBlowFactor * inWidth; + int newHeight = inBlowFactor * inHeight; + + int yOffset = ( inScreenHeight - newHeight ) / 2; + + // "up" a bit more on iPhone to make room for touch widget + int xOffset = 2 * ( inScreenWidth - newWidth ) / 3; + + // pitch equals width for iPhone implementation + int scanlineWidth = inScreenWidth; + + Uint32 *pixels = inScreenPixels; + + + // looping across the smaller image, instead of across the larger screen, + // was discovered using the profiler. + + + // an entire screen row is repeated inBlowFactor times down the screen + // (as a row of pixel boxes) + // Thus, we can offload a lot more work onto memcpy if we assemble one + // of these rows and then memcpy it onto the screen inBlowFactor times + for( int y=0; y> 16) & 0xFF; + unsigned char green = (pixelValue >> 8) & 0xFF; + unsigned char blue = (pixelValue >> 0) & 0xFF; + + pixelValue = blue << 16 | green << 8 | red; + + // spread this pixel across an inBlowFactor-wide box row in + // the screen row + + int boxXStart = inBlowFactor * x; + int boxXEnd = boxXStart + inBlowFactor; + + // make an array to represent one row of this box + // we can thus replace the inner loop with a memcpy below + for( int i=boxXStart; i + +typedef uint32_t Uint32; + + +void initScreenDrawer( Uint32 *inScreenBuffer, int inWidth, int inHeight ); + +// each pixel is 4 characters +void drawIntoScreen( Uint32 *inScreenBuffer, int inWidth, int inHeight ); + +// set device orientation from accelerometer +void setOrientation( float inX, float inY ); + +void touchStartPoint( float inX, float inY ); + +void touchMovePoint( float inX, float inY ); + +void touchEndPoint( float inX, float inY ); + +void freeScreenDrawer(); diff --git a/gamma256/gameSource/iPhone/game.cpp b/gamma256/gameSource/iPhone/game.cpp new file mode 100644 index 0000000..326fade --- /dev/null +++ b/gamma256/gameSource/iPhone/game.cpp @@ -0,0 +1,1568 @@ +/* + * Modification History + * + * 2007-September-25 Jason Rohrer + * Created. + */ + + + +#include +#include +#include +#include +#include +#include + +// for memcpy +#include + +/* +// let SDL override our main function with SDLMain +#include + +// must do this before SDL include to prevent WinMain linker errors on win32 +int mainFunction( int inArgCount, char **inArgs ); + +int main( int inArgCount, char **inArgs ) { + return mainFunction( inArgCount, inArgs ); + } +*/ + +//#include + +#include "blowUp.h" +#include "World.h" +#include "map.h" +#include "score.h" +#include "common.h" +#include "musicPlayer.h" + +#include "minorGems/system/Time.h" +#include "minorGems/system/Thread.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SettingsManager.h" + +#include "drawIntoScreen.h" + + +// size of game image +int width = 100; +int height = 12; +// area above game image for score +int scoreHeight = getScoreHeight(); + +// size of game image plus scoreboard +int totalImageHeight = height + scoreHeight; + + + +// size of screen for fullscreen mode +int screenWidth = 480; +int screenHeight = 320; +//int screenWidth = 100; +//int screenHeight = 16; + +// blow-up factor when projecting game onto screen +// Max defined by image size vs screen size. +// This is the cap for in-game user-directed blowup adjustment. +int maxBlowUpFactor; + +// current blow-up setting +int blowUpFactor; + + +// step to take when user hits blowUp key +int blowUpStep = -1; +// flag to force update of entire screen +int blowUpChanged = true; + + +char fullScreen = true; + + +// lock down to 15 fps +int lockedFrameRate = 15; + + +// target length of game +int gameTime = 5 * 60; +//int gameTime = 30; + + +// life time that passes per frame +double timeDelta = - ( (double)width / ( gameTime * lockedFrameRate ) ); +//double timeDelta = -0.0; + +Uint32 *gameImage; +Uint32 *rotatedGameImage; + + + + +// the joystick to read from, or NULL if there's no available joystick +//SDL_Joystick *joystick; + + +/* +// catch an interrupt signal +void catch_int(int sig_num) { + printf( "Quiting...\n" ); + SDL_Quit(); + exit( 0 ); + signal( SIGINT, catch_int ); + } + + + +char getKeyDown( int inKeyCode ) { + SDL_PumpEvents(); + Uint8 *keys = SDL_GetKeyState( NULL ); + return keys[ inKeyCode ] == SDL_PRESSED; + } + + + +char getHatDown( Uint8 inHatPosition ) { + if( joystick == NULL ) { + return false; + } + + SDL_JoystickUpdate(); + + Uint8 hatPosition = SDL_JoystickGetHat( joystick, 0 ); + + if( hatPosition & inHatPosition ) { + return true; + } + else { + return false; + } + } + + +int joyThreshold = 25000; + +char getJoyPushed( Uint8 inHatPosition ) { + + Sint16 x = SDL_JoystickGetAxis(joystick, 0); + Sint16 y = SDL_JoystickGetAxis(joystick, 1); + + switch( inHatPosition ) { + case SDL_HAT_DOWN: + return y > joyThreshold; + break; + case SDL_HAT_UP: + return y < -joyThreshold; + break; + case SDL_HAT_LEFT: + return x < - joyThreshold; + break; + case SDL_HAT_RIGHT: + return x > joyThreshold; + break; + default: + return false; + } + + + } + + + +// returns true if hit, returns false if user quits before hitting +char waitForKeyOrButton() { + SDL_Event event; + + while( true ) { + while( SDL_WaitEvent( &event ) ) { + switch( event.type ) { + case SDL_JOYHATMOTION: + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + case SDL_KEYDOWN: + case SDL_KEYUP: + return true; + break; + // watch for quit event + case SDL_QUIT: + return false; + break; + default: + break; + } + } + } + + + return false; + } + + + + + + + +// flips back buffer onto screen (or updates rect) +void flipScreen( SDL_Surface *inScreen ) { + + // unlock the screen if necessary + if( SDL_MUSTLOCK( inScreen ) ) { + SDL_UnlockSurface( inScreen ); + } + + if( ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) { + // need to flip buffer + SDL_Flip( inScreen ); + } + else if( !blowUpChanged ) { + // just update center + + // small area in center that we actually draw in, black around it + int yOffset = ( inScreen->h - totalImageHeight * blowUpFactor ) / 2; + int xOffset = ( inScreen->w - width * blowUpFactor ) / 2; + + SDL_UpdateRect( inScreen, xOffset, yOffset, + width * blowUpFactor, + totalImageHeight * blowUpFactor ); + } + else { + // update the whole thing + SDL_UpdateRect( inScreen, 0, 0, inScreen->w, inScreen->h ); + + // reset flag + blowUpChanged = false; + } + + } + + + +void lockScreen( SDL_Surface * inScreen ) { + // check if we need to lock the screen + if( SDL_MUSTLOCK( inScreen ) ) { + if( SDL_LockSurface( inScreen ) < 0 ) { + printf( "Couldn't lock screen: %s\n", SDL_GetError() ); + } + } + } + + +*/ + +void flipGameImageOntoScreen( Uint32 *inScreen ) { + + /* + if( blowUpChanged + && + ( inScreen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) { + + // blow up size has changed, and we are double-buffered + // flip onto screen an additional time. + // This will cause us to black-out the background in both buffers. + + lockScreen( inScreen ); + + // when blow-up factor changes: + // clear screen to prepare for next draw, + // which will be bigger or smaller + SDL_FillRect( inScreen, NULL, 0x00000000 ); + + blowupOntoScreen( gameImage, + width, totalImageHeight, blowUpFactor, inScreen ); + + flipScreen( inScreen ); + } + + lockScreen( inScreen ); + if( blowUpChanged ) { + SDL_FillRect( inScreen, NULL, 0x00000000 ); + } + */ + // make rotated version for iPhone + for( int y=0; y 0 ) { + // open first one by default + joystick = SDL_JoystickOpen( 0 ); + + if( joystick == NULL ) { + printf( "Couldn't open joystick 0: %s\n", SDL_GetError() ); + } + int numHats = SDL_JoystickNumHats( joystick ); + + if( numHats <= 0 ) { + printf( "No d-pad found on joystick\n" ); + SDL_JoystickClose( joystick ); + joystick = NULL; + } + } + else { + joystick = NULL; + } + + + + #ifdef __mac__ + // make sure working directory is the same as the directory + // that the app resides in + // this is especially important on the mac platform, which + // doesn't set a proper working directory for double-clicked + // app bundles + + // arg 0 is the path to the app executable + char *appDirectoryPath = stringDuplicate( inArgs[ 0 ] ); + + printf( "Mac: app path %s\n", appDirectoryPath ); + + char *appNamePointer = strstr( appDirectoryPath, + "Passage.app" ); + + if( appNamePointer != NULL ) { + // terminate full app path to get parent directory + appNamePointer[0] = '\0'; + + printf( "Mac: changing working dir to %s\n", appDirectoryPath ); + chdir( appDirectoryPath ); + } + + delete [] appDirectoryPath; + #endif +*/ + + + + // load graphics only once + loadWorldGraphics(); + + + // load the iPhone-only arrow graphics + + Image *arrowsImage = readTGA( "arrows.tga" ); + + int imagePixelCount = arrowsImage->getWidth() * arrowsImage->getHeight(); + + double *arrowsRed = new double[ imagePixelCount ]; + double *arrowsGreen = new double[ imagePixelCount ]; + double *arrowsBlue = new double[ imagePixelCount ]; + + arrowsARGB = new Uint32[ imagePixelCount ]; + + for( int i=0; igetChannel(0)[ i ]; + arrowsGreen[i] = 255 * arrowsImage->getChannel(1)[ i ]; + arrowsBlue[i] = 255 * arrowsImage->getChannel(2)[ i ]; + + unsigned char r = + (unsigned char)( + arrowsRed[ i ] ); + + unsigned char g = + (unsigned char)( + arrowsGreen[ i ] ); + + unsigned char b = + (unsigned char)( + arrowsBlue[ i ] ); + + // swap r and b on iphone + arrowsARGB[i] = b << 16 | g << 8 | r; + } + delete arrowsImage; + + + + setMusicLoudness( 0 ); + startMusic( "music.tga" ); + + startGame(); + } + + +void freeScreenDrawer() { + endGame(); + + stopMusic(); + + destroyWorldGraphics(); + + delete [] arrowsARGB; + + /* + if( joystick != NULL ) { + SDL_JoystickClose( joystick ); + } + + SDL_Quit(); + */ + //return 0; + } + + +char firstTouchHappened = false; +char handledFirstTouch = false; + +char movingLeft = false; +char movingRight = false; +char movingUp = false; +char movingDown = false; + +float lastOrientationX = 0; +float lastOrientationY = 0; + +float orientationOffsetX = 0; +float orientationOffsetY = 0; + +void setOrientation( float inX, float inY ) { + lastOrientationX = inX; + lastOrientationY = inY; + + inX -= orientationOffsetX; + inY -= orientationOffsetY; + + movingLeft = false; + movingRight = false; + movingUp = false; + movingDown = false; + + if( !handledFirstTouch ) { + return; + } + + if( true ) { + return; + } + + // old orientation-to-motion code. + // didn't work well (mapping continuous value to discrete switch doesn't work well) + + + float threshold = 0.1; + + char yBigger = true; + if( fabs( inX ) > fabs( inY ) ) { + yBigger = false; + } + + // landscape, so axises are reversed + if( yBigger ) { + if( inY < -threshold ) { + movingRight = true; + } + else if( inY > threshold ) { + movingLeft = true; + } + } + else { + if( inX < -threshold ) { + movingDown = true; + } + else if( inX > threshold ) { + movingUp = true; + } + } + } + +double touchDown = false; +float touchX = 0; +float touchY = 0; + +void touchStartPoint( float inX, float inY ) { + firstTouchHappened = true; + touchDown = true; + touchX = inX; + touchY = inY; + } + + +void touchMovePoint( float inX, float inY ) { + touchX = inX; + touchY = inY; + } + + +void touchEndPoint( float inX, float inY ) { + touchDown = false; + touchX = inX; + touchY = inY; + } + + +// variables shared by the 3 functions below: + +int currentSpriteIndex = 2; + +double playerX, playerY; + +double maxPlayerX = 0; + +int score = 0; + + +// the gem that marks chests containing points +int specialGem = time( NULL ) % 4; + +int exploreScore = 0; +int exploreSubPoints = 0; +// make sure explore score never goes down +int maxExploreScore = 0; + +int chestScore = 0; + +// track whether we ever met the spouse +// separate from World's haveMetSpouse() +char knowSpouse = false; + +int totalGameImagePixels; + + + +double dX = 0; +double dY = 0; + + +char done = false; + +double maxWorldX = 0; +double minWorldX = 0; + +double lastFrameTimeStamp = Time::getCurrentTime(); + +// use to slow player motion after spouse has died +char movingThisFrame = true; + + +Uint32 *titlePixels; + +int frameCount = 0; +unsigned long startTime = time( NULL ); + +char quit = false; + +int titleFadeFrame = 0; +int numTitleFadeFrames = 200; + +char stepDX = true; + + + + + +void startGame() { + touchDown = false; + + firstTouchHappened = false; + handledFirstTouch = false; + + currentSpriteIndex = 2; + + playerX, playerY; + + maxPlayerX = 0; + + score = 0; + + + // the gem that marks chests containing points + specialGem = time( NULL ) % 4; + + exploreScore = 0; + exploreSubPoints = 0; + // make sure explore score never goes down + maxExploreScore = 0; + + chestScore = 0; + + // track whether we ever met the spouse + // separate from World's haveMetSpouse() + knowSpouse = false; + + + + initWorld(); + initScore(); + + + + // room at top for score + totalGameImagePixels = width * totalImageHeight; + + gameImage = new Uint32[ totalGameImagePixels ]; + rotatedGameImage = new Uint32[ totalGameImagePixels ]; + + int i; + + // fill with black + for( i=0; igetWidth() * titleImage->getHeight(); + + double *titleRed = titleImage->getChannel( 0 ); + double *titleGreen = titleImage->getChannel( 1 ); + double *titleBlue = titleImage->getChannel( 2 ); + + titlePixels = new Uint32[ numTitlePixels ]; + + for( int i=0; i floorWidth ) { + // slope + double slopeValue = + ( dX - drawDX - floorWidth ) / ( 1 - floorWidth ); + + // us trig function to avoid jagged transition from floor to + // slope to next floor + + drawDX += -0.5 * cos( slopeValue * M_PI ) + 0.5; + } + + if( !stepDX ) { + // disable smoothed stepping + drawDX = dX; + } + + + for( int y=0; y maxDistance ) { + cappedDistanceFromPlayer = maxDistance; + } + if( cappedDistanceFromPlayer < -maxDistance ) { + cappedDistanceFromPlayer = -maxDistance; + } + + // the world position we will sample from + double worldX = x; + + // zone around player where no x compression happens + int noCompressZone = 10; + + + if( trueDistanceFromPlayer > noCompressZone ) { + + worldX = + x + + //(width/8) * + // use true distance as a factor so that compression + // continues at constant rate after we pass capped + // distance + // otherwise, compression stops after capped distance + // Still... constant rate looks weird, so avoid + // it by not letting it pass capped distance + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + // simpler formula + // actually, this does not approach 0 as + // cappedDistanceFromPlayer approaches 0, so use tan + // instead + // ( trueDistanceFromPlayer / 2 ) * 100 + // / pow( ( width - cappedDistanceFromPlayer ), 1.6 ); + } + else if( trueDistanceFromPlayer < - noCompressZone ) { + worldX = + x + + //(width/8) * + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( - cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( - cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + //( trueDistanceFromPlayer / 2 ) * 50 + /// ( width + cappedDistanceFromPlayer ); + } + else { + // inside no-compresison zone + worldX = x; + } + + + //int worldX = x; + + worldX += drawDX; + + if( worldX > maxWorldX ) { + maxWorldX = worldX; + } + if( worldX < minWorldX ) { + minWorldX = worldX; + } + + + int worldY = (int)floor( y + dY ); + + + // linear interpolation of two samples for worldX + int intWorldX = (int)floor( worldX ); + + double bWeight = worldX - intWorldX; + double aWeight = 1 - bWeight; + + + Uint32 sampleA = + sampleFromWorld( intWorldX, worldY, aWeight ); + Uint32 sampleB = + sampleFromWorld( intWorldX + 1, worldY, bWeight ); + + + + Uint32 combined = sampleB + sampleA; + + + gameImage[ ( y + scoreHeight ) * width + x ] = combined; + + } + } + + drawScore( gameImage, width, height, score ); + + + if( isPlayerDead() ) { + // fade to title screen + double titleWeight = + titleFadeFrame / (double)( numTitleFadeFrames - 1 ); + double gameWeight = 1 - titleWeight; + + // wipe from left to right during fade + int wipePosition = (int)( titleWeight * width ); + + // fade out music while we do it + setMusicLoudness( 1.0 - titleWeight ); + + + + for( i=0; i> 16 & 0xFF; + unsigned char gameGreen = gamePixel >> 8 & 0xFF; + unsigned char gameBlue = gamePixel & 0xFF; + + Uint32 titlePixel = titlePixels[i]; + + unsigned char titleRed = titlePixel >> 16 & 0xFF; + unsigned char titleGreen = titlePixel >> 8 & 0xFF; + unsigned char titleBlue = titlePixel & 0xFF; + + unsigned char red = + (unsigned char)( + gameWeight * gameRed + titleWeight * titleRed ); + unsigned char green = + (unsigned char)( + gameWeight * gameGreen + titleWeight * titleGreen ); + unsigned char blue = + (unsigned char)( + gameWeight * gameBlue + titleWeight * titleBlue ); + + + int x = i % width; + if( x <= wipePosition ) { + gameImage[i] = red << 16 | green << 8 | blue; + } + + } + + titleFadeFrame ++; + + if( titleFadeFrame == numTitleFadeFrames ) { + done = true; + } + } + + + flipGameImageOntoScreen( screen ); + + + // next draw arrow icon onto screen and interpret touch to control motion + + int xPosition = blowUpFactor * 36; + int yPosition = inHeight / 2 - ( arrowsH * blowUpFactor / 2 ); + + + int arrowsMiddleX = xPosition + ( arrowsW * blowUpFactor / 2 ); + int arrowsMiddleY= inHeight / 2; + + + // touchX is 0..320 + // touchY is 0..480 + // need to convert these to our screen bitmap size, which is centered on the + // 320x480 screen + // hard-code blow-up factor for now (4) and offsets to center (24 and 4) + + int touchXArrowRelative = touchX / 4 + 24 - arrowsMiddleX; + int touchYArrowRelative = touchY / 4 + 4 - arrowsMiddleY; + + movingLeft = false; + movingRight = false; + movingUp = false; + movingDown = false; + + + if( touchDown ) { + if( touchYArrowRelative > touchXArrowRelative ) { + if( touchYArrowRelative > - touchXArrowRelative ) { + movingLeft = true; + } + else if( touchYArrowRelative < - touchXArrowRelative ) { + movingDown = true; + } + } + else if( touchYArrowRelative < touchXArrowRelative ) { + if( touchYArrowRelative > - touchXArrowRelative ) { + movingUp = true; + } + else if( touchYArrowRelative < - touchXArrowRelative ) { + movingRight = true; + } + } + } + + + int arrowsFrame = 0; + + if( movingLeft ) { + arrowsFrame = 1; + } + if( movingRight ) { + arrowsFrame = 2; + } + if( movingDown ) { + arrowsFrame = 3; + } + if( movingUp ) { + arrowsFrame = 4; + } + + if( ! isPlayerDead() ) { + for( int y=0; y 0 ) { + //Thread::staticSleep( (int)( extraTime * 1000 ) ); + } + + // start timing next frame + lastFrameTimeStamp = Time::getCurrentTime(); + + + + int spouseX, spouseY; + getSpousePosition( &spouseX, &spouseY ); + + + int moveDelta = 1; + + if( isPlayerDead() ) { + // stop moving + moveDelta = 0; + } + + if( knowSpouse && isSpouseDead() ) { + + // player moves slower + // toggle motion on this frame + movingThisFrame = ( frameCount % 2 == 0 ); + } + + if( false /*getKeyDown( SDLK_b ) */ ) { + // adjust blowup factor + blowUpFactor += blowUpStep; + + if( blowUpFactor > maxBlowUpFactor ) { + blowUpStep *= -1; + blowUpFactor = maxBlowUpFactor - 1; + } + + if( blowUpFactor < 1 ) { + blowUpStep *= -1; + blowUpFactor = 2; + } + + if( fullScreen ) { + // force redraw of whole screen + blowUpChanged = true; + } + else { + // create a new screen using the new size + //createScreen(); + } + + } + + if( false /*getKeyDown( SDLK_f )*/ ) { + // toggle fullscreen mode + fullScreen = ! fullScreen; + + // create a new screen surface (and destroy old one) + //createScreen(); + } + + + + if( movingLeft /*getKeyDown( SDLK_LEFT ) || getJoyPushed( SDL_HAT_LEFT )*/ ) { + char notBlocked = + !isBlocked( (int)( playerX - moveDelta ), (int)playerY ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX - moveDelta, spouseY ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + playerX -= moveDelta; + + if( playerX < 0 ) { + // undo + playerX += moveDelta; + } + else { + // update screen position + dX -= moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerX / 2 ) % 2 == 0 ) { + currentSpriteIndex = 6; + } + else { + currentSpriteIndex = 7; + } + } + } + } + else if( movingRight /*getKeyDown( SDLK_RIGHT ) || getJoyPushed( SDL_HAT_RIGHT ) */ ) { + char notBlocked = + !isBlocked( (int)( playerX + moveDelta ), (int)playerY ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX + moveDelta, spouseY ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + dX += moveDelta; + + playerX += moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerX / 2 ) % 2 == 0 ) { + currentSpriteIndex = 3; + } + else { + currentSpriteIndex = 2; + } + } + } + else if( movingUp /*getKeyDown( SDLK_UP ) || getJoyPushed( SDL_HAT_UP ) */) { + char notBlocked = + !isBlocked( (int)playerX, (int)( playerY - moveDelta ) ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX, spouseY - moveDelta ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + playerY -= moveDelta; + + if( playerY < 0 ) { + // undo + playerY += moveDelta; + } + else { + // update screen position + dY -= moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerY / 2 ) % 2 == 0 ) { + currentSpriteIndex = 0; + } + else { + currentSpriteIndex = 1; + } + } + } + } + else if( movingDown /*getKeyDown( SDLK_DOWN ) || getJoyPushed( SDL_HAT_DOWN )*/ ) { + char notBlocked = + !isBlocked( (int)playerX, (int)( playerY + moveDelta ) ); + + // spouse and character move, and are blocked, together + if( haveMetSpouse() && + isBlocked( spouseX, spouseY + moveDelta ) ) { + notBlocked = false; + } + + if( movingThisFrame && notBlocked ) { + + dY += moveDelta; + + playerY += moveDelta; + + // pick sprite frame based on position in world + if( ( (int)playerY / 2 ) % 2 == 0 ) { + currentSpriteIndex = 5; + } + else { + currentSpriteIndex = 4; + } + } + } + + setPlayerPosition( (int)playerX, (int)playerY ); + setPlayerSpriteFrame( currentSpriteIndex ); + + // may change after we set player position + getSpousePosition( &spouseX, &spouseY ); + + + /* + // check for events to quit + SDL_Event event; + while( SDL_PollEvent(&event) ) { + switch( event.type ) { + case SDL_KEYDOWN: + switch( event.key.keysym.sym ) { + case SDLK_q: + case SDLK_ESCAPE: + done = true; + quit = true; + break; + default: + break; + } + break; + case SDL_QUIT: + done = true; + quit = true; + break; + default: + break; + } + } +*/ + + //t +=0.25; + frameCount ++; + + // other animations run independent of whether player is moving + if( frameCount % 6 == 0 ) { + stepAnimations(); + } + + if( ! isPlayerDead() ) { + // player position on screen inches forward + dX += timeDelta; + } + + double age = ( playerX - dX ) / width; + + setCharacterAges( age ); + + if( age >= 0.85 ) { + dieSpouse(); + } + if( age >= 0.95 ) { + diePlayer(); + } + + + if( isChest( (int)playerX, (int)playerY ) == CHEST_CLOSED ) { + + openChest( (int)playerX, (int)playerY ); + + int chestX, chestY; + + getChestCenter( (int)playerX, (int)playerY, &chestX, &chestY ); + + if( getChestCode( (int)playerX, (int)playerY ) & + 0x01 << specialGem ) { + + // reward player + chestScore += 100; + + + startPrizeAnimation( chestX, chestY ); + } + else { + startDustAnimation( chestX, chestY ); + } + + } + + + int distanceFromSpouse = (int) sqrt( pow( spouseX - playerX, 2 ) + + pow( spouseY - playerY, 2 ) ); + + + if( ! haveMetSpouse() && + ! isSpouseDead() && + distanceFromSpouse < 10 ) { + + meetSpouse(); + + knowSpouse = true; + + startHeartAnimation( + (int)( ( spouseX - playerX ) / 2 + playerX ), + (int)( ( spouseY - playerY ) / 2 + playerY ) - 2 ); + } + + + // stop after player has gone off right end of screen + if( playerX - dX > width ) { + dX = playerX - width; + } + + int exploreDelta = 0; + + if( playerX > maxPlayerX ) { + exploreDelta = (int)( playerX - maxPlayerX ); + maxPlayerX = playerX; + } + + int spouseExploreFactor = 2; + + if( haveMetSpouse() ) { + // exploring worth more + exploreDelta *= spouseExploreFactor; + } + + exploreSubPoints += exploreDelta; + + + exploreScore = (int)( exploreSubPoints / 10 ); + + if( haveMetSpouse() ) { + // show explore score contribution in jumps + exploreScore = + ( exploreScore / spouseExploreFactor ) + * spouseExploreFactor; + // note: + // this can cause our score to go down (to the previous + // jump) as we transition from not having a spouse to + // having one. + // we fix this below with maxExploreScore + } + + if( exploreScore < maxExploreScore ) { + // don't let it go down from max + exploreScore = maxExploreScore; + } + + + score = chestScore + exploreScore; + if( exploreScore > maxExploreScore ) { + maxExploreScore = exploreScore; + } + + + } + else { + endGame(); + + startGame(); + } + } + +void endGame() { + + unsigned long netTime = time( NULL ) - startTime; + double frameRate = frameCount / (double)netTime; + + printf( "Max world x = %f\n", maxWorldX ); + printf( "Min world x = %f\n", minWorldX ); + + printf( "Frame rate = %f fps (%d frames)\n", + frameRate, frameCount ); + + printf( "Game time = %d:%d\n", + (int)netTime / 60, (int)netTime % 60 ); + + fflush( stdout ); + + + + delete [] gameImage; + delete [] rotatedGameImage; + delete [] titlePixels; + + + destroyWorld(); + destroyScore(); + +/* + if( quit ) { + return false; + } + else { + return true; + } + */ + } + diff --git a/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.mode1v3 b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.mode1v3 new file mode 100644 index 0000000..f3fc177 --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.mode1v3 @@ -0,0 +1,1459 @@ + + + + + ActivePerspectiveName + Project + AllowedModules + + + BundleLoadPath + + MaxInstances + n + Module + PBXSmartGroupTreeModule + Name + Groups and Files Outline View + + + BundleLoadPath + + MaxInstances + n + Module + PBXNavigatorGroup + Name + Editor + + + BundleLoadPath + + MaxInstances + n + Module + XCTaskListModule + Name + Task List + + + BundleLoadPath + + MaxInstances + n + Module + XCDetailModule + Name + File and Smart Group Detail Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXBuildResultsModule + Name + Detailed Build Results Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXProjectFindModule + Name + Project Batch Find Tool + + + BundleLoadPath + + MaxInstances + n + Module + XCProjectFormatConflictsModule + Name + Project Format Conflicts List + + + BundleLoadPath + + MaxInstances + n + Module + PBXBookmarksModule + Name + Bookmarks Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXClassBrowserModule + Name + Class Browser + + + BundleLoadPath + + MaxInstances + n + Module + PBXCVSModule + Name + Source Code Control Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXDebugBreakpointsModule + Name + Debug Breakpoints Tool + + + BundleLoadPath + + MaxInstances + n + Module + XCDockableInspector + Name + Inspector + + + BundleLoadPath + + MaxInstances + n + Module + PBXOpenQuicklyModule + Name + Open Quickly Tool + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugSessionModule + Name + Debugger + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugCLIModule + Name + Debug Console + + + BundleLoadPath + + MaxInstances + n + Module + XCSnapshotModule + Name + Snapshots Tool + + + BundlePath + /Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources + Description + DefaultDescriptionKey + DockingSystemVisible + + Extension + mode1v3 + FavBarConfig + + PBXProjectModuleGUID + DA86AEBF0EF53C5800054E6B + XCBarModuleItemNames + + XCBarModuleItems + + + FirstTimeWindowDisplayed + + Identifier + com.apple.perspectives.project.mode1v3 + MajorVersion + 33 + MinorVersion + 0 + Name + Default + Notifications + + OpenEditors + + PerspectiveWidths + + -1 + -1 + + Perspectives + + + ChosenToolbarItems + + active-combo-popup + action + NSToolbarFlexibleSpaceItem + build-and-go + com.apple.ide.PBXToolbarStopButton + get-info + NSToolbarFlexibleSpaceItem + com.apple.pbx.toolbar.searchfield + + ControllerClassBaseName + + IconName + WindowOfProjectWithEditor + Identifier + perspective.project + IsVertical + + Layout + + + BecomeActive + + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C08E77C0454961000C914BD + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 270 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 29B97314FDCFA39411CA2CEA + 080E96DDFE201D6D7F000001 + 29B97315FDCFA39411CA2CEA + DA12F31E0EF6E84900A40C6D + DA12F31D0EF6E83D00A40C6D + 29B97317FDCFA39411CA2CEA + 19C28FACFE9D520D11CA2CBB + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 35 + 31 + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 305}, {270, 477}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {287, 495}} + GroupTreeTableConfiguration + + MainColumn + 270 + + RubberWindowFrame + 16 135 906 536 0 0 1024 746 + + Module + PBXSmartGroupTreeModule + Proportion + 287pt + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20306471E060097A5F4 + PBXProjectModuleLabel + Info.plist + PBXSplitModuleInNavigatorKey + + Split0 + + PBXProjectModuleGUID + 1CE0B20406471E060097A5F4 + PBXProjectModuleLabel + Info.plist + _historyCapacity + 0 + bookmark + DA0EC6610F0412F800DC5B33 + history + + DA7F12040EF6E3A400CB6D50 + DA12F3600EF6EB5E00A40C6D + DA12F3620EF6EB5E00A40C6D + DA12F3640EF6EB5E00A40C6D + DA12F3660EF6EB5E00A40C6D + DA12F3680EF6EB5E00A40C6D + DA12F38A0EF6EB9F00A40C6D + DA12F3A80EF6EDCA00A40C6D + DA12F3A90EF6EDCA00A40C6D + DA12F3D90EF6EFFB00A40C6D + DA8C9E560EF80B73002FCA67 + DA8C9E710EF80DA9002FCA67 + DA8C9EAC0EF811E6002FCA67 + DA8C9F4E0EF8266C002FCA67 + DA8C9F4F0EF8266C002FCA67 + DA8C9F500EF8266C002FCA67 + DAADB67F0EF981DA00169CE3 + DA8277520EF9FC240061079D + DA82779E0EFA03960061079D + DA90DDCD0EFAF5A3001BABB5 + DA90DDCE0EFAF5A3001BABB5 + DA90DDD10EFAF5A3001BABB5 + DA90DDD20EFAF5A3001BABB5 + DA90DDD50EFAF5A3001BABB5 + DA90DDD70EFAF5A3001BABB5 + DA9E30CD0EFC240B00DFACC7 + DA9E30CF0EFC240B00DFACC7 + DA9E30D00EFC240B00DFACC7 + DA0EC5C40F03E66500DC5B33 + DA0EC60E0F03FE7400DC5B33 + DA0EC6190F04026C00DC5B33 + DA0EC64F0F0412E300DC5B33 + DA0EC65F0F0412F800DC5B33 + DA0EC6500F0412E300DC5B33 + + prevStack + + DA86AEB60EF53C5800054E6B + DA86AEE50EF53F3C00054E6B + DA7F10FB0EF6CCF600CB6D50 + DA7F11AD0EF6D9CB00CB6D50 + DA7F11AE0EF6D9CB00CB6D50 + DA7F11B10EF6D9CB00CB6D50 + DA12F36D0EF6EB5E00A40C6D + DA12F36F0EF6EB5E00A40C6D + DA12F3710EF6EB5E00A40C6D + DA12F3730EF6EB5E00A40C6D + DA12F3750EF6EB5E00A40C6D + DA12F3770EF6EB5E00A40C6D + DA12F3790EF6EB5E00A40C6D + DA12F37B0EF6EB5E00A40C6D + DA12F37D0EF6EB5E00A40C6D + DA12F37F0EF6EB5E00A40C6D + DA12F3810EF6EB5E00A40C6D + DA12F3B30EF6EDCA00A40C6D + DA12F3CC0EF6EEC700A40C6D + DA12F3CD0EF6EEC700A40C6D + DA12F3DC0EF6EFFB00A40C6D + DA12F3F10EF6F1A100A40C6D + DA8C9E300EF809CA002FCA67 + DA8C9E310EF809CA002FCA67 + DA8C9E320EF809CA002FCA67 + DA8C9E730EF80DA9002FCA67 + DA8C9EAE0EF811E6002FCA67 + DAADB6880EF981DA00169CE3 + DA90DDDD0EFAF5A3001BABB5 + DA90DDE00EFAF5A3001BABB5 + DA90DDE10EFAF5A3001BABB5 + DA90DDE90EFAF5A3001BABB5 + DA90DDEF0EFAF5A3001BABB5 + DA90DDF30EFAF5A3001BABB5 + DA0EC6600F0412F800DC5B33 + + + SplitCount + 1 + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {614, 317}} + RubberWindowFrame + 16 135 906 536 0 0 1024 746 + + Module + PBXNavigatorGroup + Proportion + 317pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20506471E060097A5F4 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{0, 322}, {614, 173}} + RubberWindowFrame + 16 135 906 536 0 0 1024 746 + + Module + XCDetailModule + Proportion + 173pt + + + Proportion + 614pt + + + Name + Project + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + XCModuleDock + PBXNavigatorGroup + XCDetailModule + + TableOfContents + + DA0EC6620F0412F800DC5B33 + 1CE0B1FE06471DED0097A5F4 + DA0EC6630F0412F800DC5B33 + 1CE0B20306471E060097A5F4 + 1CE0B20506471E060097A5F4 + + ToolbarConfiguration + xcode.toolbar.config.defaultV3 + + + ControllerClassBaseName + + IconName + WindowOfProject + Identifier + perspective.morph + IsVertical + 0 + Layout + + + BecomeActive + 1 + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C08E77C0454961000C914BD + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 11E0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 186 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 29B97314FDCFA39411CA2CEA + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {186, 337}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + 1 + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {203, 355}} + GroupTreeTableConfiguration + + MainColumn + 186 + + RubberWindowFrame + 373 269 690 397 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 100% + + + Name + Morph + PreferredWidth + 300 + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + + TableOfContents + + 11E0B1FE06471DED0097A5F4 + + ToolbarConfiguration + xcode.toolbar.config.default.shortV3 + + + PerspectivesBarVisible + + ShelfIsVisible + + SourceDescription + file at '/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources/XCPerspectivesSpecificationMode1.xcperspec' + StatusbarIsVisible + + TimeStamp + 0.0 + ToolbarDisplayMode + 1 + ToolbarIsVisible + + ToolbarSizeMode + 1 + Type + Perspectives + UpdateMessage + The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature). You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature. Do you wish to update to the latest Workspace defaults for project '%@'? + WindowJustification + 5 + WindowOrderList + + /Users/jasonrohrer/cpp/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj + + WindowString + 16 135 906 536 0 0 1024 746 + WindowToolsV3 + + + FirstTimeWindowDisplayed + + Identifier + windowTool.build + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528F0623707200166675 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {772, 95}} + RubberWindowFrame + 191 148 772 506 0 0 1024 746 + + Module + PBXNavigatorGroup + Proportion + 95pt + + + BecomeActive + + ContentConfiguration + + PBXBuildLogShowsTranscriptDefaultKey + {{0, 5}, {772, 360}} + PBXProjectModuleGUID + XCMainBuildResultsModuleGUID + PBXProjectModuleLabel + Build + XCBuildResultsTrigger_Collapse + 1021 + XCBuildResultsTrigger_Open + 1011 + + GeometryConfiguration + + Frame + {{0, 100}, {772, 365}} + RubberWindowFrame + 191 148 772 506 0 0 1024 746 + + Module + PBXBuildResultsModule + Proportion + 365pt + + + Proportion + 465pt + + + Name + Build Results + ServiceClasses + + PBXBuildResultsModule + + StatusbarIsVisible + + TableOfContents + + DA86AEC00EF53C5800054E6B + DA0EC6460F04124700DC5B33 + 1CD0528F0623707200166675 + XCMainBuildResultsModuleGUID + + ToolbarConfiguration + xcode.toolbar.config.buildV3 + WindowString + 191 148 772 506 0 0 1024 746 + WindowToolGUID + DA86AEC00EF53C5800054E6B + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debugger + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + Debugger + + HorizontalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {316, 203}} + {{316, 0}, {378, 203}} + + + VerticalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {694, 203}} + {{0, 203}, {694, 178}} + + + + LauncherConfigVersion + 8 + PBXProjectModuleGUID + 1C162984064C10D400B95A72 + PBXProjectModuleLabel + Debug - GLUTExamples (Underwater) + + GeometryConfiguration + + DebugConsoleVisible + None + DebugConsoleWindowFrame + {{200, 200}, {500, 300}} + DebugSTDIOWindowFrame + {{200, 200}, {500, 300}} + Frame + {{0, 0}, {694, 381}} + PBXDebugSessionStackFrameViewKey + + DebugVariablesTableConfiguration + + Name + 120 + Value + 85 + Summary + 148 + + Frame + {{316, 0}, {378, 203}} + RubberWindowFrame + 128 288 694 422 0 0 1024 746 + + RubberWindowFrame + 128 288 694 422 0 0 1024 746 + + Module + PBXDebugSessionModule + Proportion + 381pt + + + Proportion + 381pt + + + Name + Debugger + ServiceClasses + + PBXDebugSessionModule + + StatusbarIsVisible + + TableOfContents + + 1CD10A99069EF8BA00B06720 + DA0EC6470F04124700DC5B33 + 1C162984064C10D400B95A72 + DA0EC6480F04124700DC5B33 + DA0EC6490F04124700DC5B33 + DA0EC64A0F04124700DC5B33 + DA0EC64B0F04124700DC5B33 + DA0EC64C0F04124700DC5B33 + + ToolbarConfiguration + xcode.toolbar.config.debugV3 + WindowString + 128 288 694 422 0 0 1024 746 + WindowToolGUID + 1CD10A99069EF8BA00B06720 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.find + IsVertical + + Layout + + + Dock + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CDD528C0622207200134675 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {781, 212}} + RubberWindowFrame + 69 170 781 470 0 0 1024 746 + + Module + PBXNavigatorGroup + Proportion + 781pt + + + Proportion + 212pt + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528E0623707200166675 + PBXProjectModuleLabel + Project Find + + GeometryConfiguration + + Frame + {{0, 217}, {781, 212}} + RubberWindowFrame + 69 170 781 470 0 0 1024 746 + + Module + PBXProjectFindModule + Proportion + 212pt + + + Proportion + 429pt + + + Name + Project Find + ServiceClasses + + PBXProjectFindModule + + StatusbarIsVisible + + TableOfContents + + 1C530D57069F1CE1000CFCEE + DA7F11390EF6D2EE00CB6D50 + DA7F113A0EF6D2EE00CB6D50 + 1CDD528C0622207200134675 + 1CD0528E0623707200166675 + + WindowString + 69 170 781 470 0 0 1024 746 + WindowToolGUID + 1C530D57069F1CE1000CFCEE + WindowToolIsVisible + + + + Identifier + MENUSEPARATOR + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debuggerConsole + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAAC065D492600B07095 + PBXProjectModuleLabel + Debugger Console + + GeometryConfiguration + + Frame + {{0, 0}, {655, 370}} + RubberWindowFrame + 17 141 655 411 0 0 1024 746 + + Module + PBXDebugCLIModule + Proportion + 370pt + + + Proportion + 370pt + + + Name + Debugger Console + ServiceClasses + + PBXDebugCLIModule + + StatusbarIsVisible + + TableOfContents + + 1C78EAAD065D492600B07095 + DA0EC64D0F04124700DC5B33 + 1C78EAAC065D492600B07095 + + ToolbarConfiguration + xcode.toolbar.config.consoleV3 + WindowString + 17 141 655 411 0 0 1024 746 + WindowToolGUID + 1C78EAAD065D492600B07095 + WindowToolIsVisible + + + + Identifier + windowTool.snapshots + Layout + + + Dock + + + Module + XCSnapshotModule + Proportion + 100% + + + Proportion + 100% + + + Name + Snapshots + ServiceClasses + + XCSnapshotModule + + StatusbarIsVisible + Yes + ToolbarConfiguration + xcode.toolbar.config.snapshots + WindowString + 315 824 300 550 0 0 1440 878 + WindowToolIsVisible + Yes + + + Identifier + windowTool.scm + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAB2065D492600B07095 + PBXProjectModuleLabel + <No Editor> + PBXSplitModuleInNavigatorKey + + Split0 + + PBXProjectModuleGUID + 1C78EAB3065D492600B07095 + + SplitCount + 1 + + StatusBarVisibility + 1 + + GeometryConfiguration + + Frame + {{0, 0}, {452, 0}} + RubberWindowFrame + 743 379 452 308 0 0 1280 1002 + + Module + PBXNavigatorGroup + Proportion + 0pt + + + BecomeActive + 1 + ContentConfiguration + + PBXProjectModuleGUID + 1CD052920623707200166675 + PBXProjectModuleLabel + SCM + + GeometryConfiguration + + ConsoleFrame + {{0, 259}, {452, 0}} + Frame + {{0, 7}, {452, 259}} + RubberWindowFrame + 743 379 452 308 0 0 1280 1002 + TableConfiguration + + Status + 30 + FileName + 199 + Path + 197.0950012207031 + + TableFrame + {{0, 0}, {452, 250}} + + Module + PBXCVSModule + Proportion + 262pt + + + Proportion + 266pt + + + Name + SCM + ServiceClasses + + PBXCVSModule + + StatusbarIsVisible + 1 + TableOfContents + + 1C78EAB4065D492600B07095 + 1C78EAB5065D492600B07095 + 1C78EAB2065D492600B07095 + 1CD052920623707200166675 + + ToolbarConfiguration + xcode.toolbar.config.scm + WindowString + 743 379 452 308 0 0 1280 1002 + + + Identifier + windowTool.breakpoints + IsVertical + 0 + Layout + + + Dock + + + BecomeActive + 1 + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C77FABC04509CD000000102 + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + no + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 168 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 1C77FABC04509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {168, 350}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + 0 + + GeometryConfiguration + + Frame + {{0, 0}, {185, 368}} + GroupTreeTableConfiguration + + MainColumn + 168 + + RubberWindowFrame + 315 424 744 409 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 185pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CA1AED706398EBD00589147 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{190, 0}, {554, 368}} + RubberWindowFrame + 315 424 744 409 0 0 1440 878 + + Module + XCDetailModule + Proportion + 554pt + + + Proportion + 368pt + + + MajorVersion + 3 + MinorVersion + 0 + Name + Breakpoints + ServiceClasses + + PBXSmartGroupTreeModule + XCDetailModule + + StatusbarIsVisible + 1 + TableOfContents + + 1CDDB66807F98D9800BB5817 + 1CDDB66907F98D9800BB5817 + 1CE0B1FE06471DED0097A5F4 + 1CA1AED706398EBD00589147 + + ToolbarConfiguration + xcode.toolbar.config.breakpointsV3 + WindowString + 315 424 744 409 0 0 1440 878 + WindowToolGUID + 1CDDB66807F98D9800BB5817 + WindowToolIsVisible + 1 + + + Identifier + windowTool.debugAnimator + Layout + + + Dock + + + Module + PBXNavigatorGroup + Proportion + 100% + + + Proportion + 100% + + + Name + Debug Visualizer + ServiceClasses + + PBXNavigatorGroup + + StatusbarIsVisible + 1 + ToolbarConfiguration + xcode.toolbar.config.debugAnimatorV3 + WindowString + 100 100 700 500 0 0 1280 1002 + + + Identifier + windowTool.bookmarks + Layout + + + Dock + + + Module + PBXBookmarksModule + Proportion + 100% + + + Proportion + 100% + + + Name + Bookmarks + ServiceClasses + + PBXBookmarksModule + + StatusbarIsVisible + 0 + WindowString + 538 42 401 187 0 0 1280 1002 + + + Identifier + windowTool.projectFormatConflicts + Layout + + + Dock + + + Module + XCProjectFormatConflictsModule + Proportion + 100% + + + Proportion + 100% + + + Name + Project Format Conflicts + ServiceClasses + + XCProjectFormatConflictsModule + + StatusbarIsVisible + 0 + WindowContentMinSize + 450 300 + WindowString + 50 850 472 307 0 0 1440 877 + + + Identifier + windowTool.classBrowser + Layout + + + Dock + + + BecomeActive + 1 + ContentConfiguration + + OptionsSetName + Hierarchy, all classes + PBXProjectModuleGUID + 1CA6456E063B45B4001379D8 + PBXProjectModuleLabel + Class Browser - NSObject + + GeometryConfiguration + + ClassesFrame + {{0, 0}, {374, 96}} + ClassesTreeTableConfiguration + + PBXClassNameColumnIdentifier + 208 + PBXClassBookColumnIdentifier + 22 + + Frame + {{0, 0}, {630, 331}} + MembersFrame + {{0, 105}, {374, 395}} + MembersTreeTableConfiguration + + PBXMemberTypeIconColumnIdentifier + 22 + PBXMemberNameColumnIdentifier + 216 + PBXMemberTypeColumnIdentifier + 97 + PBXMemberBookColumnIdentifier + 22 + + PBXModuleWindowStatusBarHidden2 + 1 + RubberWindowFrame + 385 179 630 352 0 0 1440 878 + + Module + PBXClassBrowserModule + Proportion + 332pt + + + Proportion + 332pt + + + Name + Class Browser + ServiceClasses + + PBXClassBrowserModule + + StatusbarIsVisible + 0 + TableOfContents + + 1C0AD2AF069F1E9B00FABCE6 + 1C0AD2B0069F1E9B00FABCE6 + 1CA6456E063B45B4001379D8 + + ToolbarConfiguration + xcode.toolbar.config.classbrowser + WindowString + 385 179 630 352 0 0 1440 878 + WindowToolGUID + 1C0AD2AF069F1E9B00FABCE6 + WindowToolIsVisible + 0 + + + Identifier + windowTool.refactoring + IncludeInToolsMenu + 0 + Layout + + + Dock + + + BecomeActive + 1 + GeometryConfiguration + + Frame + {0, 0}, {500, 335} + RubberWindowFrame + {0, 0}, {500, 335} + + Module + XCRefactoringModule + Proportion + 100% + + + Proportion + 100% + + + Name + Refactoring + ServiceClasses + + XCRefactoringModule + + WindowString + 200 200 500 356 0 0 1920 1200 + + + + diff --git a/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.pbxuser b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.pbxuser new file mode 100644 index 0000000..4c859be --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/jasonrohrer.pbxuser @@ -0,0 +1,1277 @@ +// !$*UTF8*$! +{ + 1D6058900D05DD3D006BFB54 /* Passage */ = { + activeExec = 0; + executables = ( + DA86AE6F0EF538E500054E6B /* Passage */, + ); + }; + 29B97313FDCFA39411CA2CEA /* Project object */ = { + activeBuildConfigurationName = Distribution; + activeExecutable = DA86AE6F0EF538E500054E6B /* Passage */; + activeSDKPreference = iphoneos2.2; + activeTarget = 1D6058900D05DD3D006BFB54 /* Passage */; + addToTargets = ( + 1D6058900D05DD3D006BFB54 /* Passage */, + ); + breakpoints = ( + ); + codeSenseManager = DA86AE940EF5391600054E6B /* Code sense */; + executables = ( + DA86AE6F0EF538E500054E6B /* Passage */, + ); + perUserDictionary = { + PBXConfiguration.PBXFileTableDataSource3.PBXExecutablesDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXExecutablesDataSource_NameID; + PBXFileTableDataSourceColumnWidthsKey = ( + 22, + 300, + 262.58349609375, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXExecutablesDataSource_ActiveFlagID, + PBXExecutablesDataSource_NameID, + PBXExecutablesDataSource_CommentsID, + ); + }; + PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 375, + 20, + 48, + 43, + 43, + 20, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + PBXFileDataSource_Target_ColumnID, + ); + }; + PBXConfiguration.PBXFileTableDataSource3.PBXSymbolsDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXSymbolsDataSource_SymbolNameID; + PBXFileTableDataSourceColumnWidthsKey = ( + 16, + 200, + 50, + 315, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXSymbolsDataSource_SymbolTypeIconID, + PBXSymbolsDataSource_SymbolNameID, + PBXSymbolsDataSource_SymbolTypeID, + PBXSymbolsDataSource_ReferenceNameID, + ); + }; + PBXConfiguration.PBXFileTableDataSource3.XCSCMDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 20, + 351, + 20, + 48.16259765625, + 43, + 43, + 20, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_SCM_ColumnID, + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + PBXFileDataSource_Target_ColumnID, + ); + }; + PBXConfiguration.PBXTargetDataSource.PBXTargetDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 335, + 60, + 20, + 48, + 43, + 43, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXTargetDataSource_PrimaryAttribute, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + ); + }; + PBXPerProjectTemplateStateSaveDate = 251925228; + PBXWorkspaceStateSaveDate = 251925228; + }; + perUserProjectItems = { + DA0EC5C40F03E66500DC5B33 /* PBXTextBookmark */ = DA0EC5C40F03E66500DC5B33 /* PBXTextBookmark */; + DA0EC60E0F03FE7400DC5B33 /* PBXTextBookmark */ = DA0EC60E0F03FE7400DC5B33 /* PBXTextBookmark */; + DA0EC6190F04026C00DC5B33 /* PBXTextBookmark */ = DA0EC6190F04026C00DC5B33 /* PBXTextBookmark */; + DA0EC64F0F0412E300DC5B33 /* PBXTextBookmark */ = DA0EC64F0F0412E300DC5B33 /* PBXTextBookmark */; + DA0EC6500F0412E300DC5B33 /* PlistBookmark */ = DA0EC6500F0412E300DC5B33 /* PlistBookmark */; + DA0EC65F0F0412F800DC5B33 /* PBXTextBookmark */ = DA0EC65F0F0412F800DC5B33 /* PBXTextBookmark */; + DA0EC6600F0412F800DC5B33 /* PBXTextBookmark */ = DA0EC6600F0412F800DC5B33 /* PBXTextBookmark */; + DA0EC6610F0412F800DC5B33 /* PlistBookmark */ = DA0EC6610F0412F800DC5B33 /* PlistBookmark */; + DA12F3600EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3600EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3620EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3620EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3640EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3640EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3660EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3660EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3680EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3680EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F36D0EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F36D0EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F36F0EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F36F0EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3710EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3710EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3730EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3730EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3750EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3750EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3770EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3770EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3790EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3790EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F37B0EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F37B0EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F37D0EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F37D0EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F37F0EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F37F0EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F3810EF6EB5E00A40C6D /* PBXTextBookmark */ = DA12F3810EF6EB5E00A40C6D /* PBXTextBookmark */; + DA12F38A0EF6EB9F00A40C6D /* PBXTextBookmark */ = DA12F38A0EF6EB9F00A40C6D /* PBXTextBookmark */; + DA12F3A80EF6EDCA00A40C6D /* PBXTextBookmark */ = DA12F3A80EF6EDCA00A40C6D /* PBXTextBookmark */; + DA12F3A90EF6EDCA00A40C6D /* PBXTextBookmark */ = DA12F3A90EF6EDCA00A40C6D /* PBXTextBookmark */; + DA12F3B30EF6EDCA00A40C6D /* PBXTextBookmark */ = DA12F3B30EF6EDCA00A40C6D /* PBXTextBookmark */; + DA12F3CC0EF6EEC700A40C6D /* PBXTextBookmark */ = DA12F3CC0EF6EEC700A40C6D /* PBXTextBookmark */; + DA12F3CD0EF6EEC700A40C6D /* PBXTextBookmark */ = DA12F3CD0EF6EEC700A40C6D /* PBXTextBookmark */; + DA12F3D90EF6EFFB00A40C6D /* PBXTextBookmark */ = DA12F3D90EF6EFFB00A40C6D /* PBXTextBookmark */; + DA12F3DC0EF6EFFB00A40C6D /* PBXTextBookmark */ = DA12F3DC0EF6EFFB00A40C6D /* PBXTextBookmark */; + DA12F3F10EF6F1A100A40C6D /* PBXTextBookmark */ = DA12F3F10EF6F1A100A40C6D /* PBXTextBookmark */; + DA7F10FB0EF6CCF600CB6D50 /* PBXTextBookmark */ = DA7F10FB0EF6CCF600CB6D50 /* PBXTextBookmark */; + DA7F11AD0EF6D9CB00CB6D50 /* PBXTextBookmark */ = DA7F11AD0EF6D9CB00CB6D50 /* PBXTextBookmark */; + DA7F11AE0EF6D9CB00CB6D50 /* PBXTextBookmark */ = DA7F11AE0EF6D9CB00CB6D50 /* PBXTextBookmark */; + DA7F11B10EF6D9CB00CB6D50 /* PBXTextBookmark */ = DA7F11B10EF6D9CB00CB6D50 /* PBXTextBookmark */; + DA7F12040EF6E3A400CB6D50 /* PBXTextBookmark */ = DA7F12040EF6E3A400CB6D50 /* PBXTextBookmark */; + DA8277520EF9FC240061079D /* PBXTextBookmark */ = DA8277520EF9FC240061079D /* PBXTextBookmark */; + DA82779E0EFA03960061079D /* PBXTextBookmark */ = DA82779E0EFA03960061079D /* PBXTextBookmark */; + DA86AEB60EF53C5800054E6B /* PBXTextBookmark */ = DA86AEB60EF53C5800054E6B /* PBXTextBookmark */; + DA86AEE50EF53F3C00054E6B /* PlistBookmark */ = DA86AEE50EF53F3C00054E6B /* PlistBookmark */; + DA8C9E300EF809CA002FCA67 /* PBXTextBookmark */ = DA8C9E300EF809CA002FCA67 /* PBXTextBookmark */; + DA8C9E310EF809CA002FCA67 /* PBXTextBookmark */ = DA8C9E310EF809CA002FCA67 /* PBXTextBookmark */; + DA8C9E320EF809CA002FCA67 /* PBXTextBookmark */ = DA8C9E320EF809CA002FCA67 /* PBXTextBookmark */; + DA8C9E560EF80B73002FCA67 /* PBXTextBookmark */ = DA8C9E560EF80B73002FCA67 /* PBXTextBookmark */; + DA8C9E710EF80DA9002FCA67 /* PBXTextBookmark */ = DA8C9E710EF80DA9002FCA67 /* PBXTextBookmark */; + DA8C9E730EF80DA9002FCA67 /* PBXTextBookmark */ = DA8C9E730EF80DA9002FCA67 /* PBXTextBookmark */; + DA8C9EAC0EF811E6002FCA67 /* PBXTextBookmark */ = DA8C9EAC0EF811E6002FCA67 /* PBXTextBookmark */; + DA8C9EAE0EF811E6002FCA67 /* PBXTextBookmark */ = DA8C9EAE0EF811E6002FCA67 /* PBXTextBookmark */; + DA8C9F4E0EF8266C002FCA67 /* PBXTextBookmark */ = DA8C9F4E0EF8266C002FCA67 /* PBXTextBookmark */; + DA8C9F4F0EF8266C002FCA67 /* PBXTextBookmark */ = DA8C9F4F0EF8266C002FCA67 /* PBXTextBookmark */; + DA8C9F500EF8266C002FCA67 /* PBXTextBookmark */ = DA8C9F500EF8266C002FCA67 /* PBXTextBookmark */; + DA90DDCD0EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDCD0EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDCE0EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDCE0EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDD10EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDD10EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDD20EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDD20EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDD50EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDD50EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDD70EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDD70EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDDD0EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDDD0EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDE00EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDE00EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDE10EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDE10EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDE90EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDE90EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDEF0EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDEF0EFAF5A3001BABB5 /* PBXTextBookmark */; + DA90DDF30EFAF5A3001BABB5 /* PBXTextBookmark */ = DA90DDF30EFAF5A3001BABB5 /* PBXTextBookmark */; + DA9E30CD0EFC240B00DFACC7 /* PBXTextBookmark */ = DA9E30CD0EFC240B00DFACC7 /* PBXTextBookmark */; + DA9E30CF0EFC240B00DFACC7 /* PBXTextBookmark */ = DA9E30CF0EFC240B00DFACC7 /* PBXTextBookmark */; + DA9E30D00EFC240B00DFACC7 /* PBXTextBookmark */ = DA9E30D00EFC240B00DFACC7 /* PBXTextBookmark */; + DAADB67F0EF981DA00169CE3 /* PBXTextBookmark */ = DAADB67F0EF981DA00169CE3 /* PBXTextBookmark */; + DAADB6880EF981DA00169CE3 /* PBXTextBookmark */ = DAADB6880EF981DA00169CE3 /* PBXTextBookmark */; + }; + sourceControlManager = DA86AE930EF5391600054E6B /* Source Control */; + userBuildSettings = { + }; + }; + 29B97316FDCFA39411CA2CEA /* main.m */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 574}}"; + sepNavSelRange = "{447, 0}"; + sepNavVisRange = "{252, 661}"; + sepNavWindowFrame = "{{172, 138}, {750, 558}}"; + }; + }; + 8D1107310486CEB800E47090 /* Info.plist */ = { + uiCtxt = { + sepNavWindowFrame = "{{61, 141}, {750, 558}}"; + }; + }; + DA0EC5C40F03E66500DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3CF0EF6EED000A40C6D /* common.h */; + name = "common.h: 6"; + rLen = 38; + rLoc = 51; + rType = 0; + vrLen = 252; + vrLoc = 0; + }; + DA0EC60E0F03FE7400DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3400EF6E8BD00A40C6D /* common.cpp */; + name = "common.cpp: 24"; + rLen = 0; + rLoc = 498; + rType = 0; + vrLen = 460; + vrLoc = 83; + }; + DA0EC6190F04026C00DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3410EF6E8BD00A40C6D /* game.cpp */; + name = "game.cpp: 420"; + rLen = 0; + rLoc = 9572; + rType = 0; + vrLen = 656; + vrLoc = 8975; + }; + DA0EC64F0F0412E300DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3780EF6EB5E00A40C6D /* map.cpp */; + name = "map.cpp: 243"; + rLen = 0; + rLoc = 6332; + rType = 0; + vrLen = 330; + vrLoc = 0; + }; + DA0EC6500F0412E300DC5B33 /* PlistBookmark */ = { + isa = PlistBookmark; + fRef = 8D1107310486CEB800E47090 /* Info.plist */; + fallbackIsa = PBXBookmark; + isK = 0; + kPath = ( + ); + name = /Users/jasonrohrer/cpp/gamma256/gameSource/iPhone/Info.plist; + rLen = 0; + rLoc = 2147483647; + }; + DA0EC65F0F0412F800DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */; + name = "Timbre.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 362; + vrLoc = 0; + }; + DA0EC6600F0412F800DC5B33 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */; + name = "Timbre.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 362; + vrLoc = 0; + }; + DA0EC6610F0412F800DC5B33 /* PlistBookmark */ = { + isa = PlistBookmark; + fRef = 8D1107310486CEB800E47090 /* Info.plist */; + fallbackIsa = PBXBookmark; + isK = 0; + kPath = ( + ); + name = /Users/jasonrohrer/cpp/gamma256/gameSource/iPhone/Info.plist; + rLen = 0; + rLoc = 2147483647; + }; + DA12F32D0EF6E89F00A40C6D /* World.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {787, 714}}"; + sepNavSelRange = "{0, 44}"; + sepNavVisRange = "{0, 755}"; + }; + }; + DA12F32E0EF6E89F00A40C6D /* World.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 13482}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 312}"; + }; + }; + DA12F3320EF6E89F00A40C6D /* map.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 3948}}"; + sepNavSelRange = "{6332, 0}"; + sepNavVisRange = "{0, 330}"; + }; + }; + DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 1176}}"; + sepNavSelRange = "{500, 0}"; + sepNavVisRange = "{21, 621}"; + sepNavWindowFrame = "{{204, 57}, {750, 558}}"; + }; + }; + DA12F33F0EF6E8BD00A40C6D /* blowUp.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {691, 430}}"; + sepNavSelRange = "{321, 0}"; + sepNavVisRange = "{0, 325}"; + sepNavWindowFrame = "{{38, 162}, {750, 558}}"; + }; + }; + DA12F3400EF6E8BD00A40C6D /* common.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 364}}"; + sepNavSelRange = "{498, 0}"; + sepNavVisRange = "{83, 460}"; + }; + }; + DA12F3410EF6E8BD00A40C6D /* game.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {711, 21784}}"; + sepNavSelRange = "{42027, 0}"; + sepNavVisRange = "{8437, 165}"; + sepNavWindowFrame = "{{202, 82}, {750, 558}}"; + }; + }; + DA12F35D0EF6EB5E00A40C6D /* map.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = map.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/map.h; + sourceTree = ""; + }; + DA12F3600EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3610EF6EB5E00A40C6D /* landscape.h */; + name = "landscape.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 692; + vrLoc = 0; + }; + DA12F3610EF6EB5E00A40C6D /* landscape.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = landscape.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/landscape.h; + sourceTree = ""; + }; + DA12F3620EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3630EF6EB5E00A40C6D /* landscape.cpp */; + name = "landscape.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 412; + vrLoc = 0; + }; + DA12F3630EF6EB5E00A40C6D /* landscape.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = landscape.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/landscape.cpp; + sourceTree = ""; + }; + DA12F3640EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3650EF6EB5E00A40C6D /* HashTable.h */; + name = "HashTable.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 424; + vrLoc = 0; + }; + DA12F3650EF6EB5E00A40C6D /* HashTable.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = HashTable.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/HashTable.h; + sourceTree = ""; + }; + DA12F3660EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3670EF6EB5E00A40C6D /* Envelope.h */; + name = "Envelope.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 669; + vrLoc = 0; + }; + DA12F3670EF6EB5E00A40C6D /* Envelope.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = Envelope.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/Envelope.h; + sourceTree = ""; + }; + DA12F3680EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3690EF6EB5E00A40C6D /* Envelope.cpp */; + name = "Envelope.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 723; + vrLoc = 0; + }; + DA12F3690EF6EB5E00A40C6D /* Envelope.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = Envelope.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/Envelope.cpp; + sourceTree = ""; + }; + DA12F36D0EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F36E0EF6EB5E00A40C6D /* World.h */; + name = "World.h: 1"; + rLen = 0; + rLoc = 20; + rType = 0; + vrLen = 530; + vrLoc = 0; + }; + DA12F36E0EF6EB5E00A40C6D /* World.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = World.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/World.h; + sourceTree = ""; + }; + DA12F36F0EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3700EF6EB5E00A40C6D /* World.cpp */; + name = "World.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 307; + vrLoc = 0; + }; + DA12F3700EF6EB5E00A40C6D /* World.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = World.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/World.cpp; + sourceTree = ""; + }; + DA12F3710EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3720EF6EB5E00A40C6D /* score.h */; + name = "score.h: 1"; + rLen = 0; + rLoc = 20; + rType = 0; + vrLen = 215; + vrLoc = 0; + }; + DA12F3720EF6EB5E00A40C6D /* score.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = score.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/score.h; + sourceTree = ""; + }; + DA12F3730EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3740EF6EB5E00A40C6D /* score.cpp */; + name = "score.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 288; + vrLoc = 0; + }; + DA12F3740EF6EB5E00A40C6D /* score.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = score.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/score.cpp; + sourceTree = ""; + }; + DA12F3750EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3760EF6EB5E00A40C6D /* map.h */; + name = "map.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 541; + vrLoc = 0; + }; + DA12F3760EF6EB5E00A40C6D /* map.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = map.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/map.h; + sourceTree = ""; + }; + DA12F3770EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3780EF6EB5E00A40C6D /* map.cpp */; + name = "map.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 330; + vrLoc = 0; + }; + DA12F3780EF6EB5E00A40C6D /* map.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = map.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/map.cpp; + sourceTree = ""; + }; + DA12F3790EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F37A0EF6EB5E00A40C6D /* landscape.h */; + name = "landscape.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 692; + vrLoc = 0; + }; + DA12F37A0EF6EB5E00A40C6D /* landscape.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = landscape.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/landscape.h; + sourceTree = ""; + }; + DA12F37B0EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F37C0EF6EB5E00A40C6D /* landscape.cpp */; + name = "landscape.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 412; + vrLoc = 0; + }; + DA12F37C0EF6EB5E00A40C6D /* landscape.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = landscape.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/landscape.cpp; + sourceTree = ""; + }; + DA12F37D0EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F37E0EF6EB5E00A40C6D /* HashTable.h */; + name = "HashTable.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 424; + vrLoc = 0; + }; + DA12F37E0EF6EB5E00A40C6D /* HashTable.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = HashTable.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/HashTable.h; + sourceTree = ""; + }; + DA12F37F0EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3800EF6EB5E00A40C6D /* Envelope.h */; + name = "Envelope.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 669; + vrLoc = 0; + }; + DA12F3800EF6EB5E00A40C6D /* Envelope.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = Envelope.h; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/Envelope.h; + sourceTree = ""; + }; + DA12F3810EF6EB5E00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3820EF6EB5E00A40C6D /* Envelope.cpp */; + name = "Envelope.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 723; + vrLoc = 0; + }; + DA12F3820EF6EB5E00A40C6D /* Envelope.cpp */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = Envelope.cpp; + path = /Users/jasonrohrer/cpp/gamma256/gameSource/Envelope.cpp; + sourceTree = ""; + }; + DA12F38A0EF6EB9F00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F35D0EF6EB5E00A40C6D /* map.h */; + name = "map.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 541; + vrLoc = 0; + }; + DA12F3A80EF6EDCA00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3720EF6EB5E00A40C6D /* score.h */; + name = "score.h: 1"; + rLen = 0; + rLoc = 19; + rType = 0; + vrLen = 239; + vrLoc = 0; + }; + DA12F3A90EF6EDCA00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3740EF6EB5E00A40C6D /* score.cpp */; + name = "score.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 222; + vrLoc = 0; + }; + DA12F3B30EF6EDCA00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3400EF6E8BD00A40C6D /* common.cpp */; + name = "common.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 458; + vrLoc = 0; + }; + DA12F3CC0EF6EEC700A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */; + name = "blowUp.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 550; + vrLoc = 0; + }; + DA12F3CD0EF6EEC700A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F33F0EF6E8BD00A40C6D /* blowUp.h */; + name = "blowUp.h: 1"; + rLen = 0; + rLoc = 27; + rType = 0; + vrLen = 282; + vrLoc = 0; + }; + DA12F3CF0EF6EED000A40C6D /* common.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 285}}"; + sepNavSelRange = "{51, 38}"; + sepNavVisRange = "{0, 252}"; + }; + }; + DA12F3D90EF6EFFB00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F33F0EF6E8BD00A40C6D /* blowUp.h */; + name = "blowUp.h: 1"; + rLen = 0; + rLoc = 27; + rType = 0; + vrLen = 310; + vrLoc = 0; + }; + DA12F3DC0EF6EFFB00A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3CF0EF6EED000A40C6D /* common.h */; + name = "common.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 252; + vrLoc = 0; + }; + DA12F3F10EF6F1A100A40C6D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3410EF6E8BD00A40C6D /* game.cpp */; + name = "game.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 388; + vrLoc = 84; + }; + DA7F10EE0EF6CA2500CB6D50 /* drawIntoScreen.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 322}}"; + sepNavSelRange = "{509, 0}"; + sepNavVisRange = "{50, 485}"; + sepNavWindowFrame = "{{205, 93}, {750, 558}}"; + }; + }; + DA7F10FB0EF6CCF600CB6D50 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F10EE0EF6CA2500CB6D50 /* drawIntoScreen.h */; + name = "drawIntoScreen.h: 4"; + rLen = 0; + rLoc = 279; + rType = 0; + vrLen = 154; + vrLoc = 0; + }; + DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 5950}}"; + sepNavSelRange = "{8506, 0}"; + sepNavVisRange = "{8370, 514}"; + }; + }; + DA7F117F0EF6D77000CB6D50 /* gameWindowAppDelegate.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {714, 1050}}"; + sepNavSelRange = "{1038, 0}"; + sepNavVisRange = "{1193, 379}"; + }; + }; + DA7F11800EF6D77000CB6D50 /* gameWindowApp_Prefix.pch */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {600, 262}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 195}"; + }; + }; + DA7F11AD0EF6D9CB00CB6D50 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F11800EF6D77000CB6D50 /* gameWindowApp_Prefix.pch */; + name = "gameWindowApp_Prefix.pch: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 195; + vrLoc = 0; + }; + DA7F11AE0EF6D9CB00CB6D50 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F117F0EF6D77000CB6D50 /* gameWindowAppDelegate.h */; + name = "gameWindowAppAppDelegate.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 387; + vrLoc = 0; + }; + DA7F11B10EF6D9CB00CB6D50 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */; + name = "gameWindowAppAppDelegate.mm: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 452; + vrLoc = 0; + }; + DA7F12040EF6E3A400CB6D50 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F11800EF6D77000CB6D50 /* gameWindowApp_Prefix.pch */; + name = "gameWindowApp_Prefix.pch: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 195; + vrLoc = 0; + }; + DA8277520EF9FC240061079D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F10EE0EF6CA2500CB6D50 /* drawIntoScreen.h */; + name = "drawIntoScreen.h: 21"; + rLen = 0; + rLoc = 509; + rType = 0; + vrLen = 485; + vrLoc = 50; + }; + DA82779E0EFA03960061079D /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F3700EF6EB5E00A40C6D /* World.cpp */; + name = "World.cpp: 23"; + rLen = 0; + rLoc = 312; + rType = 0; + vrLen = 571; + vrLoc = 143; + }; + DA86AE6F0EF538E500054E6B /* Passage */ = { + isa = PBXExecutable; + activeArgIndices = ( + ); + argumentStrings = ( + ); + autoAttachOnCrash = 1; + breakpointsEnabled = 0; + configStateDict = { + }; + customDataFormattersEnabled = 1; + debuggerPlugin = GDBDebugging; + disassemblyDisplayState = 0; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + executableSystemSymbolLevel = 0; + executableUserSymbolLevel = 0; + libgmallocEnabled = 0; + name = Passage; + savedGlobals = { + }; + sourceDirectories = ( + ); + variableFormatDictionary = { + }; + }; + DA86AE930EF5391600054E6B /* Source Control */ = { + isa = PBXSourceControlManager; + fallbackIsa = XCSourceControlManager; + isSCMEnabled = 0; + scmConfiguration = { + }; + }; + DA86AE940EF5391600054E6B /* Code sense */ = { + isa = PBXCodeSenseManager; + indexTemplatePath = ""; + }; + DA86AEB60EF53C5800054E6B /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 29B97316FDCFA39411CA2CEA /* main.m */; + name = "main.m: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 368; + vrLoc = 0; + }; + DA86AEE50EF53F3C00054E6B /* PlistBookmark */ = { + isa = PlistBookmark; + fRef = 8D1107310486CEB800E47090 /* Info.plist */; + fallbackIsa = PBXBookmark; + isK = 0; + kPath = ( + ); + name = "/Volumes/Untitled 2/testWindowApp/Info.plist"; + rLen = 0; + rLoc = 2147483647; + }; + DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 574}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 380}"; + }; + }; + DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 3430}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 545}"; + }; + }; + DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 1022}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 417}"; + }; + }; + DA8C9E300EF809CA002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */; + name = "TimeUnix.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 380; + vrLoc = 0; + }; + DA8C9E310EF809CA002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */; + name = "ThreadLinux.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 545; + vrLoc = 0; + }; + DA8C9E320EF809CA002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */; + name = "PathLinux.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 417; + vrLoc = 0; + }; + DA8C9E560EF80B73002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */; + name = "ThreadLinux.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 545; + vrLoc = 0; + }; + DA8C9E5C0EF80B73002FCA67 /* unistd.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = unistd.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/usr/include/unistd.h; + sourceTree = ""; + }; + DA8C9E710EF80DA9002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E5C0EF80B73002FCA67 /* unistd.h */; + name = "unistd.h: 461"; + rLen = 51; + rLoc = 15908; + rType = 0; + vrLen = 674; + vrLoc = 15562; + }; + DA8C9E730EF80DA9002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E5C0EF80B73002FCA67 /* unistd.h */; + name = "unistd.h: 461"; + rLen = 51; + rLoc = 15908; + rType = 0; + vrLen = 674; + vrLoc = 15562; + }; + DA8C9E910EF810D7002FCA67 /* string.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = string.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/usr/include/string.h; + sourceTree = ""; + }; + DA8C9EAC0EF811E6002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E910EF810D7002FCA67 /* string.h */; + name = "string.h: 116"; + rLen = 28; + rLoc = 4727; + rType = 0; + vrLen = 760; + vrLoc = 4173; + }; + DA8C9EAE0EF811E6002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E910EF810D7002FCA67 /* string.h */; + name = "string.h: 116"; + rLen = 28; + rLoc = 4727; + rType = 0; + vrLen = 760; + vrLoc = 4173; + }; + DA8C9F4E0EF8266C002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */; + name = "TimeUnix.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 380; + vrLoc = 0; + }; + DA8C9F4F0EF8266C002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */; + name = "PathLinux.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 417; + vrLoc = 0; + }; + DA8C9F500EF8266C002FCA67 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F36E0EF6EB5E00A40C6D /* World.h */; + name = "World.h: 1"; + rLen = 19; + rLoc = 0; + rType = 0; + vrLen = 510; + vrLoc = 0; + }; + DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {678, 11298}}"; + sepNavSelRange = "{18324, 0}"; + sepNavVisRange = "{18145, 879}"; + sepNavWindowFrame = "{{15, 144}, {846, 597}}"; + }; + }; + DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {553, 2324}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRange = "{0, 362}"; + }; + }; + DA90DD120EFAC2AF001BABB5 /* Timbre.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {787, 504}}"; + sepNavSelRange = "{28, 0}"; + sepNavVisRange = "{0, 1102}"; + sepNavWindowFrame = "{{223, -30}, {846, 597}}"; + }; + }; + DA90DDCD0EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 29B97316FDCFA39411CA2CEA /* main.m */; + name = "main.m: 22"; + rLen = 0; + rLoc = 447; + rType = 0; + vrLen = 661; + vrLoc = 252; + }; + DA90DDCE0EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDCF0EFAF5A3001BABB5 /* stdint.h */; + name = "stdint.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 402; + vrLoc = 464; + }; + DA90DDCF0EFAF5A3001BABB5 /* stdint.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = stdint.h; + path = /Developer/Platforms/iPhoneOS.platform/Developer/usr/include/gcc/darwin/4.0/stdint.h; + sourceTree = ""; + }; + DA90DDD10EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD120EFAC2AF001BABB5 /* Timbre.h */; + name = "Timbre.h: 1"; + rLen = 43; + rLoc = 0; + rType = 0; + vrLen = 728; + vrLoc = 0; + }; + DA90DDD20EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDD30EFAF5A3001BABB5 /* AUComponent.h */; + name = "AUComponent.h: 60"; + rLen = 0; + rLoc = 1369; + rType = 0; + vrLen = 534; + vrLoc = 1246; + }; + DA90DDD30EFAF5A3001BABB5 /* AUComponent.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = AUComponent.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/System/Library/Frameworks/AudioUnit.framework/Headers/AUComponent.h; + sourceTree = ""; + }; + DA90DDD50EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDD60EFAF5A3001BABB5 /* AUComponent.h */; + name = "AUComponent.h: 67"; + rLen = 31; + rLoc = 1524; + rType = 0; + vrLen = 613; + vrLoc = 1166; + }; + DA90DDD60EFAF5A3001BABB5 /* AUComponent.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = AUComponent.h; + path = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk/System/Library/Frameworks/AudioUnit.framework/Headers/AUComponent.h; + sourceTree = ""; + }; + DA90DDD70EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */; + name = "musicPlayer.cpp: 637"; + rLen = 0; + rLoc = 18324; + rType = 0; + vrLen = 879; + vrLoc = 18145; + }; + DA90DDDD0EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */; + name = "musicPlayer.cpp: 793"; + rLen = 0; + rLoc = 23411; + rType = 0; + vrLen = 620; + vrLoc = 16250; + }; + DA90DDE00EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD120EFAC2AF001BABB5 /* Timbre.h */; + name = "Timbre.h: 2"; + rLen = 0; + rLoc = 43; + rType = 0; + vrLen = 729; + vrLoc = 0; + }; + DA90DDE10EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */; + name = "Timbre.cpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 435; + vrLoc = 1669; + }; + DA90DDE90EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDEA0EFAF5A3001BABB5 /* stdint.h */; + name = "stdint.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 402; + vrLoc = 464; + }; + DA90DDEA0EFAF5A3001BABB5 /* stdint.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = stdint.h; + path = /Developer/Platforms/iPhoneOS.platform/Developer/usr/include/gcc/darwin/4.0/stdint.h; + sourceTree = ""; + }; + DA90DDEF0EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDF00EFAF5A3001BABB5 /* AUComponent.h */; + name = "AUComponent.h: 60"; + rLen = 0; + rLoc = 1369; + rType = 0; + vrLen = 534; + vrLoc = 1246; + }; + DA90DDF00EFAF5A3001BABB5 /* AUComponent.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = AUComponent.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/System/Library/Frameworks/AudioUnit.framework/Headers/AUComponent.h; + sourceTree = ""; + }; + DA90DDF30EFAF5A3001BABB5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA90DDF40EFAF5A3001BABB5 /* AUComponent.h */; + name = "AUComponent.h: 67"; + rLen = 31; + rLoc = 1524; + rType = 0; + vrLen = 613; + vrLoc = 1166; + }; + DA90DDF40EFAF5A3001BABB5 /* AUComponent.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = AUComponent.h; + path = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk/System/Library/Frameworks/AudioUnit.framework/Headers/AUComponent.h; + sourceTree = ""; + }; + DA9E30CD0EFC240B00DFACC7 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */; + name = "blowUp.cpp: 17"; + rLen = 0; + rLoc = 500; + rType = 0; + vrLen = 621; + vrLoc = 21; + }; + DA9E30CF0EFC240B00DFACC7 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F117F0EF6D77000CB6D50 /* gameWindowAppDelegate.h */; + name = "gameWindowAppDelegate.h: 46"; + rLen = 0; + rLoc = 1038; + rType = 0; + vrLen = 379; + vrLoc = 1193; + }; + DA9E30D00EFC240B00DFACC7 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */; + name = "gameWindowAppDelegate.mm: 334"; + rLen = 0; + rLoc = 8506; + rType = 0; + vrLen = 514; + vrLoc = 8370; + }; + DAADB67F0EF981DA00169CE3 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DAADB6800EF981DA00169CE3 /* UIAccelerometer.h */; + name = "UIAccelerometer.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 535; + vrLoc = 714; + }; + DAADB6800EF981DA00169CE3 /* UIAccelerometer.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = UIAccelerometer.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIAccelerometer.h; + sourceTree = ""; + }; + DAADB6880EF981DA00169CE3 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = DAADB6890EF981DA00169CE3 /* UIAccelerometer.h */; + name = "UIAccelerometer.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 535; + vrLoc = 714; + }; + DAADB6890EF981DA00169CE3 /* UIAccelerometer.h */ = { + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = UIAccelerometer.h; + path = /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/System/Library/Frameworks/UIKit.framework/Headers/UIAccelerometer.h; + sourceTree = ""; + }; +} diff --git a/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/project.pbxproj b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/project.pbxproj new file mode 100755 index 0000000..a1dcf4b --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowApp.xcodeproj/project.pbxproj @@ -0,0 +1,468 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + DA12F3060EF6E70100A40C6D /* title.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F2FC0EF6E70100A40C6D /* title.tga */; }; + DA12F3070EF6E70100A40C6D /* heart.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F2FD0EF6E70100A40C6D /* heart.tga */; }; + DA12F3080EF6E70100A40C6D /* numerals.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F2FE0EF6E70100A40C6D /* numerals.tga */; }; + DA12F3090EF6E70100A40C6D /* spouseSprite.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F2FF0EF6E70100A40C6D /* spouseSprite.tga */; }; + DA12F30A0EF6E70100A40C6D /* tileSet.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3000EF6E70100A40C6D /* tileSet.tga */; }; + DA12F30B0EF6E70100A40C6D /* chestPrize.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3010EF6E70100A40C6D /* chestPrize.tga */; }; + DA12F30C0EF6E70100A40C6D /* chestDust.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3020EF6E70100A40C6D /* chestDust.tga */; }; + DA12F30D0EF6E70100A40C6D /* chest.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3030EF6E70100A40C6D /* chest.tga */; }; + DA12F30E0EF6E70100A40C6D /* characterSpriteSad.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3040EF6E70100A40C6D /* characterSpriteSad.tga */; }; + DA12F30F0EF6E70100A40C6D /* characterSprite.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3050EF6E70100A40C6D /* characterSprite.tga */; }; + DA12F3390EF6E89F00A40C6D /* World.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F32E0EF6E89F00A40C6D /* World.cpp */; }; + DA12F33A0EF6E89F00A40C6D /* score.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3300EF6E89F00A40C6D /* score.cpp */; }; + DA12F33B0EF6E89F00A40C6D /* map.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3320EF6E89F00A40C6D /* map.cpp */; }; + DA12F33C0EF6E89F00A40C6D /* landscape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3340EF6E89F00A40C6D /* landscape.cpp */; }; + DA12F33D0EF6E89F00A40C6D /* Envelope.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3370EF6E89F00A40C6D /* Envelope.cpp */; }; + DA12F3420EF6E8BD00A40C6D /* blowUp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */; }; + DA12F3430EF6E8BD00A40C6D /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3400EF6E8BD00A40C6D /* common.cpp */; }; + DA12F3440EF6E8BD00A40C6D /* game.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA12F3410EF6E8BD00A40C6D /* game.cpp */; }; + DA12F3E60EF6F00600A40C6D /* music.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA12F3E50EF6F00600A40C6D /* music.tga */; }; + DA7F11810EF6D77000CB6D50 /* gameWindowAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */; }; + DA8276E60EF9B2290061079D /* arrows.tga in Resources */ = {isa = PBXBuildFile; fileRef = DA8276E50EF9B2290061079D /* arrows.tga */; }; + DA8277840EF9FFE80061079D /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = DA8277830EF9FFE80061079D /* Icon.png */; }; + DA86AEC90EF53C7100054E6B /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD733E0D9D9553002E5188 /* MainWindow.xib */; }; + DA8C9E1C0EF8095C002FCA67 /* TimeUnix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */; }; + DA8C9E1E0EF80968002FCA67 /* ThreadLinux.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */; }; + DA8C9E200EF80985002FCA67 /* PathLinux.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */; }; + DA8C9F090EF817F7002FCA67 /* StringBufferOutputStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA8C9F050EF817F7002FCA67 /* StringBufferOutputStream.cpp */; }; + DA8C9F0A0EF817F7002FCA67 /* stringUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA8C9F070EF817F7002FCA67 /* stringUtils.cpp */; }; + DA90DD060EFAB907001BABB5 /* musicPlayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */; }; + DA90DD140EFAC2AF001BABB5 /* Timbre.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */; }; + DA90DD4A0EFADE15001BABB5 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA90DD490EFADE15001BABB5 /* AudioToolbox.framework */; }; + DA9E302E0EFC010300DFACC7 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9E302C0EFC010300DFACC7 /* QuartzCore.framework */; }; + DA9E302F0EFC010300DFACC7 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9E302D0EFC010300DFACC7 /* OpenGLES.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 1D6058910D05DD3D006BFB54 /* Passage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passage.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 28AD733E0D9D9553002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DA12F2FC0EF6E70100A40C6D /* title.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = title.tga; path = ../graphics/title.tga; sourceTree = SOURCE_ROOT; }; + DA12F2FD0EF6E70100A40C6D /* heart.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = heart.tga; path = ../graphics/heart.tga; sourceTree = SOURCE_ROOT; }; + DA12F2FE0EF6E70100A40C6D /* numerals.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = numerals.tga; path = ../graphics/numerals.tga; sourceTree = SOURCE_ROOT; }; + DA12F2FF0EF6E70100A40C6D /* spouseSprite.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = spouseSprite.tga; path = ../graphics/spouseSprite.tga; sourceTree = SOURCE_ROOT; }; + DA12F3000EF6E70100A40C6D /* tileSet.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = tileSet.tga; path = ../graphics/tileSet.tga; sourceTree = SOURCE_ROOT; }; + DA12F3010EF6E70100A40C6D /* chestPrize.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = chestPrize.tga; path = ../graphics/chestPrize.tga; sourceTree = SOURCE_ROOT; }; + DA12F3020EF6E70100A40C6D /* chestDust.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = chestDust.tga; path = ../graphics/chestDust.tga; sourceTree = SOURCE_ROOT; }; + DA12F3030EF6E70100A40C6D /* chest.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = chest.tga; path = ../graphics/chest.tga; sourceTree = SOURCE_ROOT; }; + DA12F3040EF6E70100A40C6D /* characterSpriteSad.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = characterSpriteSad.tga; path = ../graphics/characterSpriteSad.tga; sourceTree = SOURCE_ROOT; }; + DA12F3050EF6E70100A40C6D /* characterSprite.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = characterSprite.tga; path = ../graphics/characterSprite.tga; sourceTree = SOURCE_ROOT; }; + DA12F32D0EF6E89F00A40C6D /* World.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = World.h; path = ../World.h; sourceTree = SOURCE_ROOT; }; + DA12F32E0EF6E89F00A40C6D /* World.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = World.cpp; path = ../World.cpp; sourceTree = SOURCE_ROOT; }; + DA12F32F0EF6E89F00A40C6D /* score.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = score.h; path = ../score.h; sourceTree = SOURCE_ROOT; }; + DA12F3300EF6E89F00A40C6D /* score.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = score.cpp; path = ../score.cpp; sourceTree = SOURCE_ROOT; }; + DA12F3310EF6E89F00A40C6D /* map.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = map.h; path = ../map.h; sourceTree = SOURCE_ROOT; }; + DA12F3320EF6E89F00A40C6D /* map.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = map.cpp; path = ../map.cpp; sourceTree = SOURCE_ROOT; }; + DA12F3330EF6E89F00A40C6D /* landscape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = landscape.h; path = ../landscape.h; sourceTree = SOURCE_ROOT; }; + DA12F3340EF6E89F00A40C6D /* landscape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = landscape.cpp; path = ../landscape.cpp; sourceTree = SOURCE_ROOT; }; + DA12F3350EF6E89F00A40C6D /* HashTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HashTable.h; path = ../HashTable.h; sourceTree = SOURCE_ROOT; }; + DA12F3360EF6E89F00A40C6D /* Envelope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Envelope.h; path = ../Envelope.h; sourceTree = SOURCE_ROOT; }; + DA12F3370EF6E89F00A40C6D /* Envelope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Envelope.cpp; path = ../Envelope.cpp; sourceTree = SOURCE_ROOT; }; + DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = blowUp.cpp; sourceTree = ""; }; + DA12F33F0EF6E8BD00A40C6D /* blowUp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = blowUp.h; sourceTree = ""; }; + DA12F3400EF6E8BD00A40C6D /* common.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = common.cpp; sourceTree = ""; }; + DA12F3410EF6E8BD00A40C6D /* game.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = game.cpp; sourceTree = ""; }; + DA12F3CF0EF6EED000A40C6D /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = ../common.h; sourceTree = SOURCE_ROOT; }; + DA12F3E50EF6F00600A40C6D /* music.tga */ = {isa = PBXFileReference; lastKnownFileType = file; name = music.tga; path = ../music/music.tga; sourceTree = SOURCE_ROOT; }; + DA7F10EE0EF6CA2500CB6D50 /* drawIntoScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = drawIntoScreen.h; sourceTree = ""; }; + DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = gameWindowAppDelegate.mm; sourceTree = ""; }; + DA7F117F0EF6D77000CB6D50 /* gameWindowAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gameWindowAppDelegate.h; sourceTree = ""; }; + DA7F11800EF6D77000CB6D50 /* gameWindowApp_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gameWindowApp_Prefix.pch; sourceTree = ""; }; + DA8276E50EF9B2290061079D /* arrows.tga */ = {isa = PBXFileReference; lastKnownFileType = file; path = arrows.tga; sourceTree = ""; }; + DA8277830EF9FFE80061079D /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; + DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TimeUnix.cpp; path = ../../../minorGems/system/unix/TimeUnix.cpp; sourceTree = SOURCE_ROOT; }; + DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThreadLinux.cpp; path = ../../../minorGems/system/linux/ThreadLinux.cpp; sourceTree = SOURCE_ROOT; }; + DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PathLinux.cpp; path = ../../../minorGems/io/file/linux/PathLinux.cpp; sourceTree = SOURCE_ROOT; }; + DA8C9F050EF817F7002FCA67 /* StringBufferOutputStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringBufferOutputStream.cpp; path = ../../../minorGems/util/StringBufferOutputStream.cpp; sourceTree = SOURCE_ROOT; }; + DA8C9F060EF817F7002FCA67 /* StringBufferOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StringBufferOutputStream.h; path = ../../../minorGems/util/StringBufferOutputStream.h; sourceTree = SOURCE_ROOT; }; + DA8C9F070EF817F7002FCA67 /* stringUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stringUtils.cpp; path = ../../../minorGems/util/stringUtils.cpp; sourceTree = SOURCE_ROOT; }; + DA8C9F080EF817F7002FCA67 /* stringUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stringUtils.h; path = ../../../minorGems/util/stringUtils.h; sourceTree = SOURCE_ROOT; }; + DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = musicPlayer.cpp; sourceTree = ""; }; + DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Timbre.cpp; path = ../Timbre.cpp; sourceTree = SOURCE_ROOT; }; + DA90DD120EFAC2AF001BABB5 /* Timbre.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Timbre.h; path = ../Timbre.h; sourceTree = SOURCE_ROOT; }; + DA90DD130EFAC2AF001BABB5 /* musicPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = musicPlayer.h; path = ../musicPlayer.h; sourceTree = SOURCE_ROOT; }; + DA90DD490EFADE15001BABB5 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + DA9E302C0EFC010300DFACC7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + DA9E302D0EFC010300DFACC7 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, + 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, + DA90DD4A0EFADE15001BABB5 /* AudioToolbox.framework in Frameworks */, + DA9E302E0EFC010300DFACC7 /* QuartzCore.framework in Frameworks */, + DA9E302F0EFC010300DFACC7 /* OpenGLES.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 1D6058910D05DD3D006BFB54 /* Passage.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + isa = PBXGroup; + children = ( + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = CustomTemplate; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + DA12F31E0EF6E84900A40C6D /* Unchanged */, + DA12F31D0EF6E83D00A40C6D /* Customized */, + DA7F117E0EF6D77000CB6D50 /* gameWindowAppDelegate.mm */, + DA7F117F0EF6D77000CB6D50 /* gameWindowAppDelegate.h */, + DA7F11800EF6D77000CB6D50 /* gameWindowApp_Prefix.pch */, + DA7F10EE0EF6CA2500CB6D50 /* drawIntoScreen.h */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + DA8277830EF9FFE80061079D /* Icon.png */, + DA12F2FB0EF6E6B100A40C6D /* graphics */, + 28AD733E0D9D9553002E5188 /* MainWindow.xib */, + 8D1107310486CEB800E47090 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + DA9E302C0EFC010300DFACC7 /* QuartzCore.framework */, + DA9E302D0EFC010300DFACC7 /* OpenGLES.framework */, + DA90DD490EFADE15001BABB5 /* AudioToolbox.framework */, + 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, + 1D30AB110D05D00D00671497 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + DA12F2FB0EF6E6B100A40C6D /* graphics */ = { + isa = PBXGroup; + children = ( + DA8276E50EF9B2290061079D /* arrows.tga */, + DA12F3E50EF6F00600A40C6D /* music.tga */, + DA12F2FC0EF6E70100A40C6D /* title.tga */, + DA12F2FD0EF6E70100A40C6D /* heart.tga */, + DA12F2FE0EF6E70100A40C6D /* numerals.tga */, + DA12F2FF0EF6E70100A40C6D /* spouseSprite.tga */, + DA12F3000EF6E70100A40C6D /* tileSet.tga */, + DA12F3010EF6E70100A40C6D /* chestPrize.tga */, + DA12F3020EF6E70100A40C6D /* chestDust.tga */, + DA12F3030EF6E70100A40C6D /* chest.tga */, + DA12F3040EF6E70100A40C6D /* characterSpriteSad.tga */, + DA12F3050EF6E70100A40C6D /* characterSprite.tga */, + ); + name = graphics; + sourceTree = ""; + }; + DA12F31D0EF6E83D00A40C6D /* Customized */ = { + isa = PBXGroup; + children = ( + DA12F33E0EF6E8BD00A40C6D /* blowUp.cpp */, + DA90DD050EFAB907001BABB5 /* musicPlayer.cpp */, + DA12F33F0EF6E8BD00A40C6D /* blowUp.h */, + DA12F3400EF6E8BD00A40C6D /* common.cpp */, + DA12F3410EF6E8BD00A40C6D /* game.cpp */, + ); + name = Customized; + sourceTree = ""; + }; + DA12F31E0EF6E84900A40C6D /* Unchanged */ = { + isa = PBXGroup; + children = ( + DA8C9E030EF80930002FCA67 /* minorGems */, + DA90DD110EFAC2AF001BABB5 /* Timbre.cpp */, + DA90DD120EFAC2AF001BABB5 /* Timbre.h */, + DA90DD130EFAC2AF001BABB5 /* musicPlayer.h */, + DA12F3CF0EF6EED000A40C6D /* common.h */, + DA12F32D0EF6E89F00A40C6D /* World.h */, + DA12F32E0EF6E89F00A40C6D /* World.cpp */, + DA12F32F0EF6E89F00A40C6D /* score.h */, + DA12F3300EF6E89F00A40C6D /* score.cpp */, + DA12F3310EF6E89F00A40C6D /* map.h */, + DA12F3320EF6E89F00A40C6D /* map.cpp */, + DA12F3330EF6E89F00A40C6D /* landscape.h */, + DA12F3340EF6E89F00A40C6D /* landscape.cpp */, + DA12F3350EF6E89F00A40C6D /* HashTable.h */, + DA12F3360EF6E89F00A40C6D /* Envelope.h */, + DA12F3370EF6E89F00A40C6D /* Envelope.cpp */, + ); + name = Unchanged; + sourceTree = ""; + }; + DA8C9E030EF80930002FCA67 /* minorGems */ = { + isa = PBXGroup; + children = ( + DA8C9F050EF817F7002FCA67 /* StringBufferOutputStream.cpp */, + DA8C9F060EF817F7002FCA67 /* StringBufferOutputStream.h */, + DA8C9F070EF817F7002FCA67 /* stringUtils.cpp */, + DA8C9F080EF817F7002FCA67 /* stringUtils.h */, + DA8C9E1F0EF80985002FCA67 /* PathLinux.cpp */, + DA8C9E1B0EF8095C002FCA67 /* TimeUnix.cpp */, + DA8C9E1D0EF80968002FCA67 /* ThreadLinux.cpp */, + ); + name = minorGems; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D6058900D05DD3D006BFB54 /* Passage */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Passage" */; + buildPhases = ( + 1D60588D0D05DD3D006BFB54 /* Resources */, + 1D60588E0D05DD3D006BFB54 /* Sources */, + 1D60588F0D05DD3D006BFB54 /* Frameworks */, + DA12F31C0EF6E79F00A40C6D /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Passage; + productName = testWindowApp; + productReference = 1D6058910D05DD3D006BFB54 /* Passage.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "gameWindowApp" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1D6058900D05DD3D006BFB54 /* Passage */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D60588D0D05DD3D006BFB54 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA86AEC90EF53C7100054E6B /* MainWindow.xib in Resources */, + DA12F3060EF6E70100A40C6D /* title.tga in Resources */, + DA12F3070EF6E70100A40C6D /* heart.tga in Resources */, + DA12F3080EF6E70100A40C6D /* numerals.tga in Resources */, + DA12F3090EF6E70100A40C6D /* spouseSprite.tga in Resources */, + DA12F30A0EF6E70100A40C6D /* tileSet.tga in Resources */, + DA12F30B0EF6E70100A40C6D /* chestPrize.tga in Resources */, + DA12F30C0EF6E70100A40C6D /* chestDust.tga in Resources */, + DA12F30D0EF6E70100A40C6D /* chest.tga in Resources */, + DA12F30E0EF6E70100A40C6D /* characterSpriteSad.tga in Resources */, + DA12F30F0EF6E70100A40C6D /* characterSprite.tga in Resources */, + DA12F3E60EF6F00600A40C6D /* music.tga in Resources */, + DA8276E60EF9B2290061079D /* arrows.tga in Resources */, + DA8277840EF9FFE80061079D /* Icon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + DA12F31C0EF6E79F00A40C6D /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = ""; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D60588E0D05DD3D006BFB54 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D60589B0D05DD56006BFB54 /* main.m in Sources */, + DA7F11810EF6D77000CB6D50 /* gameWindowAppDelegate.mm in Sources */, + DA12F3390EF6E89F00A40C6D /* World.cpp in Sources */, + DA12F33A0EF6E89F00A40C6D /* score.cpp in Sources */, + DA12F33B0EF6E89F00A40C6D /* map.cpp in Sources */, + DA12F33C0EF6E89F00A40C6D /* landscape.cpp in Sources */, + DA12F33D0EF6E89F00A40C6D /* Envelope.cpp in Sources */, + DA12F3420EF6E8BD00A40C6D /* blowUp.cpp in Sources */, + DA12F3430EF6E8BD00A40C6D /* common.cpp in Sources */, + DA12F3440EF6E8BD00A40C6D /* game.cpp in Sources */, + DA8C9E1C0EF8095C002FCA67 /* TimeUnix.cpp in Sources */, + DA8C9E1E0EF80968002FCA67 /* ThreadLinux.cpp in Sources */, + DA8C9E200EF80985002FCA67 /* PathLinux.cpp in Sources */, + DA8C9F090EF817F7002FCA67 /* StringBufferOutputStream.cpp in Sources */, + DA8C9F0A0EF817F7002FCA67 /* stringUtils.cpp in Sources */, + DA90DD060EFAB907001BABB5 /* musicPlayer.cpp in Sources */, + DA90DD140EFAC2AF001BABB5 /* Timbre.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1D6058940D05DD3E006BFB54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = gameWindowApp_Prefix.pch; + HEADER_SEARCH_PATHS = /Users/jasonrohrer/cpp; + INFOPLIST_FILE = Info.plist; + PRODUCT_NAME = Passage; + }; + name = Debug; + }; + 1D6058950D05DD3E006BFB54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = gameWindowApp_Prefix.pch; + HEADER_SEARCH_PATHS = /Users/jasonrohrer/cpp; + INFOPLIST_FILE = Info.plist; + PRODUCT_NAME = Passage; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = iphoneos2.2; + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos2.2; + }; + name = Release; + }; + DA9E30CB0EFC240700DFACC7 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = iphoneos2.2; + }; + name = Distribution; + }; + DA9E30CC0EFC240700DFACC7 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Jason Rohrer"; + COPY_PHASE_STRIP = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = gameWindowApp_Prefix.pch; + HEADER_SEARCH_PATHS = /Users/jasonrohrer/cpp; + INFOPLIST_FILE = Info.plist; + PRODUCT_NAME = Passage; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = "0BE1AB5F-422D-4C27-A26D-B23A5A1F89C1"; + }; + name = Distribution; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Passage" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D6058940D05DD3E006BFB54 /* Debug */, + 1D6058950D05DD3E006BFB54 /* Release */, + DA9E30CC0EFC240700DFACC7 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "gameWindowApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + DA9E30CB0EFC240700DFACC7 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/gamma256/gameSource/iPhone/gameWindowAppDelegate.h b/gamma256/gameSource/iPhone/gameWindowAppDelegate.h new file mode 100644 index 0000000..4e5dc4e --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowAppDelegate.h @@ -0,0 +1,74 @@ +// +// testWindowAppAppDelegate.h +// testWindowApp +// +// Created by Jason Rohrer on 12/14/08. +// Copyright __MyCompanyName__ 2008. All rights reserved. +// + +#include "drawIntoScreen.h" + +#import +#import +#import +#import + +@interface MyView : UIView { +@private + NSTimer *animationTimer; + + /* The pixel dimensions of the backbuffer */ + GLint backingWidth; + GLint backingHeight; + + EAGLContext *context; + + /* OpenGL names for the renderbuffer and framebuffers used to render to this view */ + GLuint viewRenderbuffer, viewFramebuffer; + + /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */ + GLuint depthRenderbuffer; + + GLuint textureID; + + + + Uint32 *screenBitmap; + int bitmapW; + int bitmapH; + int bitmapBytes; + + // for old DrawImage version + // CGDataProviderRef provider; + // CGColorSpaceRef colorSpaceRef; + // CGImageRef imageRef; +} + +@property NSTimer *animationTimer; +@property (nonatomic, retain) EAGLContext *context; + +- (void)startAnimation; +- (void)stopAnimation; +- (void)drawFrame; +- (BOOL) createFramebuffer; +- (void) destroyFramebuffer; + +@end + + +@interface gameWindowAppDelegate : NSObject { + UIWindow *window; + MyView *view; + // used for low-pass filtering + UIAccelerationValue accelerationBuffer[3]; +} + +@property (nonatomic, retain) IBOutlet UIWindow *window; +@property (nonatomic, retain) IBOutlet MyView *view; + + + +@end + + + diff --git a/gamma256/gameSource/iPhone/gameWindowAppDelegate.mm b/gamma256/gameSource/iPhone/gameWindowAppDelegate.mm new file mode 100644 index 0000000..c4538fe --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowAppDelegate.mm @@ -0,0 +1,434 @@ +// +// testWindowAppAppDelegate.m +// testWindowApp +// +// Created by Jason Rohrer on 12/14/08. +// Copyright __MyCompanyName__ 2008. All rights reserved. +// + +#import "gameWindowAppDelegate.h" + + +@implementation gameWindowAppDelegate + +@synthesize window; +@synthesize view; + + +- (void)applicationDidFinishLaunching:(UIApplication *)application { + + printf( "App finished launching\n" ); + + printf( "Calling start anim\n" ); + [view startAnimation]; + printf( "Done starting animation\n" ); + + // Override point for customization after application launch + [window makeKeyAndVisible]; + + // Configure and start the accelerometer + // off for now + //[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)]; + //[[UIAccelerometer sharedAccelerometer] setDelegate:self]; + + + +} + + +// UIAccelerometerDelegate method, called when the device accelerates. +- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { + //printf( "%d --- accel called w %f,%f\n", timeOfCall, acceleration.x, acceleration.y ); + // low pass filter + float filterFactor = 0.5; + accelerationBuffer[0] = acceleration.x * filterFactor + (1-filterFactor) * accelerationBuffer[0]; + accelerationBuffer[1] = acceleration.y * filterFactor + (1-filterFactor) * accelerationBuffer[1]; + accelerationBuffer[2] = acceleration.z * filterFactor + (1-filterFactor) * accelerationBuffer[2]; + + setOrientation( asin( accelerationBuffer[0] ), asin( accelerationBuffer[1] ) ); + +} + + +- (void)dealloc { + [window release]; + [view stopAnimation]; + [view release]; + + [super dealloc]; +} + +@end + + + + + +#include "drawIntoScreen.h" + +#import +#import + +@implementation MyView + +@synthesize animationTimer; +@synthesize context; + +// You must implement this method ++ (Class)layerClass { + return [CAEAGLLayer class]; +} + + +//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder: +- (id)initWithCoder:(NSCoder*)coder { + + if ((self = [super initWithCoder:coder])) { + // Get the layer + CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; + + eaglLayer.opaque = YES; + eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil]; + + context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; + + if (!context || ![EAGLContext setCurrentContext:context]) { + [self release]; + return nil; + } + + } + return self; +} + + + +NSTimeInterval countStartTime; +int appFrameCount = 0; + +- (void)layoutSubviews { + [EAGLContext setCurrentContext:context]; + [self destroyFramebuffer]; + [self createFramebuffer]; + + NSDate *then = [NSDate date]; + + countStartTime = [then timeIntervalSinceReferenceDate]; + + [self drawFrame]; +} + + +- (BOOL)createFramebuffer { + + glGenFramebuffersOES(1, &viewFramebuffer); + glGenRenderbuffersOES(1, &viewRenderbuffer); + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); + glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); + [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; + glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); + + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); + glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); + + if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { + NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); + return NO; + } + + glGenTextures( 1, &textureID ); + + return YES; +} + + +- (void)destroyFramebuffer { + + glDeleteFramebuffersOES(1, &viewFramebuffer); + viewFramebuffer = 0; + glDeleteRenderbuffersOES(1, &viewRenderbuffer); + viewRenderbuffer = 0; + + if(depthRenderbuffer) { + glDeleteRenderbuffersOES(1, &depthRenderbuffer); + depthRenderbuffer = 0; + } + + glDeleteTextures( 1, &textureID ); +} + + + + + +- (void)setAnimationTimer:(NSTimer *)newTimer { + [animationTimer invalidate]; + animationTimer = newTimer; +} + +- (void)startAnimation { + NSTimeInterval animationInterval = 1 / 15.0; + + self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawFrame) userInfo:nil repeats:YES]; + + + + bitmapW = 128; + bitmapH = 128; + + int screenPixels = bitmapW * bitmapH; + bitmapBytes = screenPixels* 4; + + screenBitmap = (Uint32 *) malloc( bitmapBytes ); + /* + // for old DrawImage version + provider = CGDataProviderCreateWithData( NULL, screenBitmap, screenPixels * 4, NULL ); + colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + imageRef = CGImageCreate( + bitmapW, + bitmapH, + 8, + 32, + 4 * bitmapW, + colorSpaceRef, + kCGImageAlphaNoneSkipLast, + provider, + NULL, + NO, + kCGRenderingIntentDefault ); + */ + initScreenDrawer( screenBitmap, bitmapW, bitmapH ); +} + + +- (void)stopAnimation { + printf( "Stop anim called\n" ); + self.animationTimer = nil; + + free( screenBitmap ); + + freeScreenDrawer(); +} + + + + + + + + + + +- (void)drawFrame { + // old DrawImage version + //[self setNeedsDisplay]; + + + + + const GLfloat squareVertices[] = { + -1.6f, -1.6f, + 1.6f, -1.6f, + -1.6f, 1.6f, + 1.6f, 1.6f, + }; + /* + const GLubyte squareColors[] = { + 255, 255, 0, 255, + 0, 255, 255, 255, + 255, 0, 0, 0, + 255, 0, 255, 255, + }; + */ + const GLfloat squareTextureCoords[] = { + 0, 0, + 1, 0, + 0, 1, + 1, 1 + }; + + [EAGLContext setCurrentContext:context]; + + glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); + glViewport(0, 0, backingWidth, backingHeight); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f); + + + glMatrixMode(GL_MODELVIEW); + + //glClearColor(0.5f, 0.5f, 0.5f, 1.0f); + //glClear(GL_COLOR_BUFFER_BIT); + + glVertexPointer(2, GL_FLOAT, 0, squareVertices); + glEnableClientState(GL_VERTEX_ARRAY); + + //glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors); + //glEnableClientState(GL_COLOR_ARRAY); + + glTexCoordPointer(2, GL_FLOAT, 0, squareTextureCoords); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + + // set new texture data + drawIntoScreen( screenBitmap, bitmapW, bitmapH ); + + int error; + + GLenum texFormat = GL_RGBA; + glBindTexture( GL_TEXTURE_2D, textureID ); + + error = glGetError(); + if( error != GL_NO_ERROR ) { // error + printf( "Error binding to texture id %d, error = %d\n", + (int)textureID, + error ); + } + + + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + + glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + glTexImage2D( GL_TEXTURE_2D, 0, + texFormat, bitmapW, + bitmapH, 0, + texFormat, GL_UNSIGNED_BYTE, screenBitmap ); + + error = glGetError(); + if( error != GL_NO_ERROR ) { // error + printf( "Error setting texture data for id %d, error = %d\n", + (int)textureID, error ); + printf( "Perhaps texture image width or height is not a power of 2\n" + "Width = %lu, Height = %lu\n", + bitmapW, bitmapH ); + } + + + + + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, textureID ); + + + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); + [context presentRenderbuffer:GL_RENDERBUFFER_OES]; + + + appFrameCount++; + + // disable FPS tracking + if( false && appFrameCount > 100 ) { + NSDate *now = [NSDate date]; + + NSTimeInterval newStartTime = [now timeIntervalSinceReferenceDate]; + + NSTimeInterval elapsedTime = newStartTime - countStartTime; + + printf( "FPS: %f\n", appFrameCount / elapsedTime ); + + countStartTime = newStartTime; + appFrameCount = 0; + } + +} + + + + +/* + +// old DrawImage version + + #include + + unsigned int appFrameCountStartTime = time( NULL ); + + int appFrameCount = 0; + +- (void)drawRect:(CGRect)rect { + + //printf( "Draw Rect called!\n" ); + + drawIntoScreen( screenBitmap, bitmapW, bitmapH ); + + CGContextRef context = UIGraphicsGetCurrentContext(); + + //CGContextRotateCTM ( context, M_PI / 2 ); + //CGContextTranslateCTM ( context, 0, -bitmapH ); + + CGRect imageRect = CGRectMake ( 0, 0, bitmapW, bitmapH ); + + CGContextDrawImage(context, imageRect, imageRef ); + + appFrameCount++; + + if( appFrameCount > 100 ) { + unsigned int newTime = time( NULL ); + unsigned int timeDelta = newTime - appFrameCountStartTime; + + printf( "FPS = %f\n", (double)appFrameCount / (double)timeDelta ); + appFrameCount = 0; + appFrameCountStartTime = newTime; + } + +} +*/ + +// Handles the start of a touch +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGRect bounds = [self bounds]; + UITouch* touch = [[event touchesForView:self] anyObject]; + + //Convert touch point from UIView referential to OpenGL one (upside-down flip) + CGPoint location = [touch locationInView:self]; + location.y = bounds.size.height - location.y; + + touchStartPoint( location.x, location.y ); +} + + + +// Handles touch motion +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGRect bounds = [self bounds]; + UITouch* touch = [[event touchesForView:self] anyObject]; + + //Convert touch point from UIView referential to OpenGL one (upside-down flip) + CGPoint location = [touch locationInView:self]; + location.y = bounds.size.height - location.y; + + touchMovePoint( location.x, location.y ); +} + + +// Handles touch end +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGRect bounds = [self bounds]; + UITouch* touch = [[event touchesForView:self] anyObject]; + + //Convert touch point from UIView referential to OpenGL one (upside-down flip) + CGPoint location = [touch locationInView:self]; + location.y = bounds.size.height - location.y; + + touchEndPoint( location.x, location.y ); +} + + +@end diff --git a/gamma256/gameSource/iPhone/gameWindowApp_Prefix.pch b/gamma256/gameSource/iPhone/gameWindowApp_Prefix.pch new file mode 100644 index 0000000..037cfda --- /dev/null +++ b/gamma256/gameSource/iPhone/gameWindowApp_Prefix.pch @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'testWindowApp' target in the 'testWindowApp' project +// + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/gamma256/gameSource/iPhone/main.m b/gamma256/gameSource/iPhone/main.m new file mode 100644 index 0000000..d66a417 --- /dev/null +++ b/gamma256/gameSource/iPhone/main.m @@ -0,0 +1,41 @@ +// +// main.m +// testWindowApp +// +// Created by Jason Rohrer on 12/14/08. +// Copyright __MyCompanyName__ 2008. All rights reserved. +// + +#import + + + +int main(int argc, char *argv[]) { + + //printf( "Arg 0 = %s\n", argv[0] ); + + // arg 0 is the path to the app executable + char *appDirectoryPath = strdup( argv[ 0 ] ); + + //printf( "Mac: app path %s\n", appDirectoryPath ); + + char *bundleName = "Passage.app"; + + char *appNamePointer = strstr( appDirectoryPath, bundleName ); + + if( appNamePointer != NULL ) { + // terminate full app path to get bundle directory + appNamePointer[ strlen( bundleName ) ] = '\0'; + + printf( "Mac: changing working dir to %s\n", appDirectoryPath ); + chdir( appDirectoryPath ); + } + + free( appDirectoryPath ); + + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} diff --git a/gamma256/gameSource/iPhone/musicPlayer.cpp b/gamma256/gameSource/iPhone/musicPlayer.cpp new file mode 100644 index 0000000..af89110 --- /dev/null +++ b/gamma256/gameSource/iPhone/musicPlayer.cpp @@ -0,0 +1,799 @@ + +#include "common.h" +#include "Timbre.h" +#include "Envelope.h" + +#include "minorGems/util/SimpleVector.h" + + + +//#include +#include +#include + + +#include +#include + +#include +typedef int16_t Sint16; +typedef uint8_t Uint8; + +int sampleRate = 22050; +//int sampleRate = 11025; + + + + +Image *musicImage = NULL; +int w, h; + +// total number of samples played so far +int streamSamples = 0; + +// offset into grid at start +// for testing +int gridStartOffset = 0; + + + +// overal loudness of music +double musicLoudness = 1.0; + + + + + + +// one grid step in seconds +double gridStepDuration = 0.25; +int gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); + +double entireGridDuraton; + + +// c +double keyFrequency = 261.63; + + +int numTimbres = 4; + +Timbre *musicTimbres[ 4 ]; + +int numEnvelopes = 4; + +Envelope *musicEnvelopes[ 4 ]; + + + +class Note { + public: + // index into musicTimbres array + int mTimbreNumber; + + // index into musicEnvelopes array + int mEnvelopeNumber; + + int mScaleNoteNumber; + + // additional loudness adjustment + // places note in stereo space + double mLoudnessLeft; + double mLoudnessRight; + + + // start time, in seconds from start of note grid + double mStartTime; + + // duration in seconds + double mDuration; + + // used when note is currently playing to track progress in note + // negative if we should wait before starting to play the note + int mCurrentSampleNumber; + + // duration in samples + int mNumSamples; + + + }; + + +// isomorphic to our music image, except only has an entry for each note +// start (all others, including grid spots that contain note continuations, +// are NULL) +// indexed as noteGrid[y][x] +Note ***noteGrid; + + +SimpleVector currentlyPlayingNotes; + + + +// need to synch these with audio thread + +void setMusicLoudness( double inLoudness ) { + //SDL_LockAudio(); + + musicLoudness = inLoudness; + + //SDL_UnlockAudio(); + } + + + +void restartMusic() { + //SDL_LockAudio(); + + // return to beginning (and forget samples we've played so far) + streamSamples = 0; + + // drop all currently-playing notes + currentlyPlayingNotes.deleteAll(); + + //SDL_UnlockAudio(); + } + + + + +// called by SDL to get more samples +void audioCallback( void *inUserData, Uint8 *inStream, int inLengthToFill ) { + + // 2 bytes for each channel of stereo sample + int numSamples = inLengthToFill / 4; + + + Sint16 *samplesL = new Sint16[ numSamples ]; + Sint16 *samplesR = new Sint16[ numSamples ]; + + // first, zero-out the buffer to prepare it for our sum of note samples + // each sample is 2 bytes + memset( samplesL, 0, 2 * numSamples ); + memset( samplesR, 0, 2 * numSamples ); + + + int i; + + + // hop through all grid steps that *start* in this stream buffer + // add notes that start during this stream buffer + + // how far into stream buffer before we hit our first grid step? + int startOfFirstGridStep = streamSamples % gridStepDurationInSamples; + + if( startOfFirstGridStep != 0 ) { + startOfFirstGridStep = + gridStepDurationInSamples - startOfFirstGridStep; + } + + + // hop from start of grid step to start of next grid step + // ignore samples in between, since notes don't start there, + // and all we're doing right now is finding notes that start + for( i=startOfFirstGridStep; + imCurrentSampleNumber = -i; + } + } + } + + streamSamples += numSamples; + + + // loop over all current notes and add their samples to buffer + + for( int n=0; nmScaleNoteNumber; + Timbre *timbre = musicTimbres[ note->mTimbreNumber ]; + int tableLength = timbre->mWaveTableLengths[ waveTableNumber ]; + + Sint16 *waveTable = timbre->mWaveTable[ waveTableNumber ]; + + Envelope *env = musicEnvelopes[ note->mEnvelopeNumber ]; + double *envLevels = + env->getEnvelope( + // index envelope by number of grid steps in note + note->mNumSamples / gridStepDurationInSamples ); + + + double noteLoudnessL = note->mLoudnessLeft; + double noteLoudnessR = note->mLoudnessRight; + + // do this outside inner loop + noteLoudnessL *= musicLoudness; + noteLoudnessR *= musicLoudness; + + + int noteStartInBuffer = 0; + int noteEndInBuffer = numSamples; + + if( note->mCurrentSampleNumber < 0 ) { + // delay before note starts in this sample buffer + noteStartInBuffer = - note->mCurrentSampleNumber; + + // we've taken account of the delay + note->mCurrentSampleNumber = 0; + } + + char endNote = false; + + int numSamplesLeftInNote = + note->mNumSamples - note->mCurrentSampleNumber; + + if( noteStartInBuffer + numSamplesLeftInNote < noteEndInBuffer ) { + // note ends before end of buffer + noteEndInBuffer = noteStartInBuffer + numSamplesLeftInNote; + endNote = true; + } + + + int waveTablePos = note->mCurrentSampleNumber % tableLength; + + int currentSampleNumber = note->mCurrentSampleNumber; + + for( i=noteStartInBuffer; i != noteEndInBuffer; i++ ) { + double envelope = envLevels[ currentSampleNumber ]; + + double monoSample = envelope * + waveTable[ waveTablePos ]; + + + samplesL[i] += (Sint16)( noteLoudnessL * monoSample ); + samplesR[i] += (Sint16)( noteLoudnessR * monoSample ); + + currentSampleNumber ++; + + waveTablePos ++; + + // avoid using mod operator (%) in inner loop + // found with profiler + if( waveTablePos == tableLength ) { + // back to start of table + waveTablePos = 0; + } + + } + + note->mCurrentSampleNumber += ( noteEndInBuffer - noteStartInBuffer ); + + if( endNote ) { + // note ended in this buffer + currentlyPlayingNotes.deleteElement( n ); + n--; + } + + } + + + // now copy samples into Uint8 buffer + int streamPosition = 0; + for( i=0; i != numSamples; i++ ) { + Sint16 intSampleL = samplesL[i]; + Sint16 intSampleR = samplesR[i]; + + inStream[ streamPosition ] = (Uint8)( intSampleL & 0xFF ); + inStream[ streamPosition + 1 ] = (Uint8)( ( intSampleL >> 8 ) & 0xFF ); + + inStream[ streamPosition + 2 ] = (Uint8)( intSampleR & 0xFF ); + inStream[ streamPosition + 3 ] = (Uint8)( ( intSampleR >> 8 ) & 0xFF ); + + streamPosition += 4; + } + + delete [] samplesL; + delete [] samplesR; + + } + + + +// limit on n, based on Nyquist, when summing sine components +//int nLimit = (int)( sampleRate * M_PI ); +// actually, this is way too many: it takes forever to compute +// use a lower limit instead +// This produces fine results (almost perfect square wave) +int nLimit = 40; + + + +// square wave with period of 2pi +double squareWave( double inT ) { + double sum = 0; + + for( int n=1; ngetWidth(); + h = musicImage->getHeight(); + + // notes are in red and green channel + double *redChannel = musicImage->getChannel( 0 ); + double *greenChannel = musicImage->getChannel( 1 ); + + + entireGridDuraton = gridStepDuration * w; + + + // jump ahead in stream, if needed + streamSamples += gridStartOffset * gridStepDurationInSamples; + + + // blank line of pixels between timbres + int heightPerTimbre = (h+1) / numTimbres - 1; + + + // find the maximum number of simultaneous notes in the song + // take loudness into account + double maxNoteLoudnessInAColumn = 0; + + int x, y; + for( x=0; x 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + noteLoudnessInColumnL += greenChannel[ imageIndex ]; + noteLoudnessInColumnR += redChannel[ imageIndex ]; + + } + } + // pick loudest channel for this column and compare it to + // loudest column/channel seen so far + if( maxNoteLoudnessInAColumn < noteLoudnessInColumnL ) { + maxNoteLoudnessInAColumn = noteLoudnessInColumnL; + } + if( maxNoteLoudnessInAColumn < noteLoudnessInColumnR ) { + maxNoteLoudnessInAColumn = noteLoudnessInColumnR; + } + + } + + + // divide loudness amoung timbres to avoid clipping + double loudnessPerTimbre = 1.0 / maxNoteLoudnessInAColumn; + + // further adjust loudness per channel here as we construct + // each timbre. + // This is easier than tweaking loundness of a given part by hand + // using a painting program + + musicTimbres[0] = new Timbre( sampleRate, 0.6 * loudnessPerTimbre, + keyFrequency, + heightPerTimbre, sawWave ); + musicTimbres[1] = new Timbre( sampleRate, loudnessPerTimbre, + keyFrequency, + heightPerTimbre, sin ); + musicTimbres[2] = new Timbre( sampleRate, 0.4 * loudnessPerTimbre, + keyFrequency / 4, + heightPerTimbre, squareWave ); + musicTimbres[3] = new Timbre( sampleRate, 0.75 * loudnessPerTimbre, + keyFrequency / 4, + heightPerTimbre, smoothedWhiteNoise ); + + + // next, compute the longest note in the song + int maxNoteLength = 0; + + for( y=0; y 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + currentNoteLength ++; + } + else { + currentNoteLength = 0; + } + if( currentNoteLength > maxNoteLength ) { + maxNoteLength = currentNoteLength; + } + } + } + + printf( "Max note length in song = %d\n", maxNoteLength ); + + + + musicEnvelopes[0] = new Envelope( 0.05, 0.7, 0.25, 0.1, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[1] = new Envelope( 0.1, 0.9, 0.0, 0.0, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[2] = new Envelope( 0.25, 0.0, 1.0, 0.1, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[3] = new Envelope( 0.0, 0.2, 0.0, 0.0, + maxNoteLength, + gridStepDurationInSamples ); + + + + + noteGrid = new Note**[ h ]; + + for( int y=0; y 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + + if( notePlaying ) { + // part of note that's already playing + + // one more grid step + noteStart->mDuration += gridStepDuration; + noteStart->mNumSamples += gridStepDurationInSamples; + + } + else { + // start a new note + noteGrid[y][x] = new Note(); + + noteGrid[y][x]->mScaleNoteNumber = noteNumber; + + noteGrid[y][x]->mTimbreNumber = + y / ( heightPerTimbre + 1 ); + + // same as timbre number + noteGrid[y][x]->mEnvelopeNumber = + noteGrid[y][x]->mTimbreNumber; + + // left loudness from green brightness + noteGrid[y][x]->mLoudnessLeft = greenChannel[ imageIndex ]; + + // right loudness from red brightness + noteGrid[y][x]->mLoudnessRight = redChannel[ imageIndex ]; + + noteGrid[y][x]->mStartTime = gridStepDuration * x; + + // one grid step so far + noteGrid[y][x]->mDuration = gridStepDuration; + noteGrid[y][x]->mNumSamples = gridStepDurationInSamples; + + // track if it needs to be continued + notePlaying = true; + noteStart = noteGrid[y][x]; + } + } + else { + // no tone + + if( notePlaying ) { + // stop it + notePlaying = false; + noteStart = NULL; + } + } + } + } + + + } + + + + + + + +AudioUnit gOutputUnit; + + +OSStatus MyRenderer(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + + audioCallback( NULL, (Uint8 *)( ioData->mBuffers[0].mData ), ioData->mBuffers[0].mDataByteSize ); + + /* + RenderSin (sSinWaveFrameCount, + inNumberFrames, + ioData->mBuffers[0].mData, + sSampleRate, + sAmplitude, + sToneFrequency, + sWhichFormat); + + //we're just going to copy the data into each channel + for (UInt32 channel = 1; channel < ioData->mNumberBuffers; channel++) + memcpy (ioData->mBuffers[channel].mData, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize); + + sSinWaveFrameCount += inNumberFrames; + */ + return noErr; +} + +// ________________________________________________________________________________ +// +// CreateDefaultAU +// +void CreateDefaultAU() +{ + OSStatus err = noErr; + + // Open the default output unit + AudioComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + AudioComponent comp = AudioComponentFindNext(NULL, &desc); + if (comp == NULL) { printf ("FindNextComponent\n"); return; } + + err = AudioComponentInstanceNew(comp, &gOutputUnit); + if (comp == NULL) { printf ("OpenAComponent=%ld\n", (long int)err); return; } + + // Set up a callback function to generate output to the output unit + AURenderCallbackStruct input; + input.inputProc = MyRenderer; + input.inputProcRefCon = NULL; + + err = AudioUnitSetProperty (gOutputUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + if (err) { printf ("AudioUnitSetProperty-CB=%ld\n", (long int)err); return; } + +} + + +// ________________________________________________________________________________ +// +// TestDefaultAU +// +void TestDefaultAU() +{ + OSStatus err = noErr; + + // We tell the Output Unit what format we're going to supply data to it + // this is necessary if you're providing data through an input callback + // AND you want the DefaultOutputUnit to do any format conversions + // necessary from your format to the device's format. + AudioStreamBasicDescription streamFormat; + streamFormat.mSampleRate = sampleRate; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = + kLinearPCMFormatFlagIsSignedInteger + | kAudioFormatFlagsNativeEndian + | kLinearPCMFormatFlagIsPacked; + //| kAudioFormatFlagIsNonInterleaved; + streamFormat.mBytesPerPacket = 4; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = 4; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mBitsPerChannel = 16; + + printf("Rendering source:\n\t"); + printf ("SampleRate=%f,", streamFormat.mSampleRate); + printf ("BytesPerPacket=%ld,", (long int)streamFormat.mBytesPerPacket); + printf ("FramesPerPacket=%ld,", (long int)streamFormat.mFramesPerPacket); + printf ("BytesPerFrame=%ld,", (long int)streamFormat.mBytesPerFrame); + printf ("BitsPerChannel=%ld,", (long int)streamFormat.mBitsPerChannel); + printf ("ChannelsPerFrame=%ld\n", (long int)streamFormat.mChannelsPerFrame); + + err = AudioUnitSetProperty ( gOutputUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription) ); + if (err) { printf ("AudioUnitSetProperty-SF=%4.4s, %ld\n", (char*)&err, (long int)err); return; } + + // Initialize unit + err = AudioUnitInitialize(gOutputUnit); + if (err) { printf ("AudioUnitInitialize=%ld\n", (long int)err); return; } + + Float64 outSampleRate; + UInt32 size = sizeof(Float64); + err = AudioUnitGetProperty (gOutputUnit, + kAudioUnitProperty_SampleRate, + kAudioUnitScope_Output, + 0, + &outSampleRate, + &size); + if (err) { printf ("AudioUnitSetProperty-GF=%4.4s, %ld\n", (char*)&err, (long int)err); return; } + + // Start the rendering + // The DefaultOutputUnit will do any format conversions to the format of the default device + err = AudioOutputUnitStart (gOutputUnit); + if (err) { printf ("AudioOutputUnitStart=%ld\n", (long int)err); return; } + + // we call the CFRunLoopRunInMode to service any notifications that the audio + // system has to deal with + //CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false); + + +} + +void CloseDefaultAU () +{ + // Clean up + OSStatus err = noErr; + + err = AudioOutputUnitStop( gOutputUnit ); + if (err) { printf ("AudioOutputUnitStop=%ld\n", (long int)err); return; } + + err = AudioUnitUninitialize (gOutputUnit); + if (err) { printf ("AudioUnitUninitialize=%ld\n", (long int)err); return; } + + AudioComponentInstanceDispose( gOutputUnit ); + //CloseComponent(gOutputUnit); +} + + + + + + + + + + +void startMusic( char *inTGAFileName ) { + + loadMusicImage( inTGAFileName ); + + CreateDefaultAU(); + TestDefaultAU(); + } + + + +void stopMusic() { + CloseDefaultAU(); + + if( musicImage != NULL ) { + delete musicImage; + musicImage = NULL; + } + + for( int y=0; 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 Ri6ks6Ep>ghN)`JL{|nbws@4Di literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/iPhone/storeDescription.txt b/gamma256/gameSource/iPhone/storeDescription.txt new file mode 100644 index 0000000..9be060e --- /dev/null +++ b/gamma256/gameSource/iPhone/storeDescription.txt @@ -0,0 +1,9 @@ +The critically-acclaimed breakout-hit from art game designer Jason Rohrer. Passage is a tiny, touching, five-minute game about life. + +"More than any game I've ever played, it illustrates how a game can be a fantastically expressive, artistic vehicle for exploring the human condition." -- Clive Thompson, Wired + +"Passage may look primitive but it's an absolute pinnacle of videogame development." --Andy Chalk, The Escapist + +"Portal is a fine game---nothing less than excellent... Passage is even better." --Nick Montfort, MIT + +You can read an in-depth profile of Jason Rohrer, along with an extensive discussion of Passage, in Esquire Magazine's December 2008 "Genius" issue. diff --git a/gamma256/gameSource/iPhone/testScreenDrawer.cpp b/gamma256/gameSource/iPhone/testScreenDrawer.cpp new file mode 100644 index 0000000..948e2df --- /dev/null +++ b/gamma256/gameSource/iPhone/testScreenDrawer.cpp @@ -0,0 +1,21 @@ + + +#include "drawIntoScreen.h" + +#include + +void drawIntoScreen( Uint32 *inScreenBuffer, int inWidth, int inHeight ) { + // add dot at random spot + int x = rand() % inWidth; + int y = rand() % inHeight; + + int i = y * inWidth + x; + + inScreenBuffer[ i ] = 0xFFFFFFFF; +} + +void initScreenDrawer( Uint32 *inScreenBuffer, int inWidth, int inHeight ) { +} + +void freeScreenDrawer() { +} \ No newline at end of file diff --git a/gamma256/gameSource/landscape.cpp b/gamma256/gameSource/landscape.cpp new file mode 100644 index 0000000..f9646df --- /dev/null +++ b/gamma256/gameSource/landscape.cpp @@ -0,0 +1,454 @@ +/* + * Modification History + * + * 2006-September-26 Jason Rohrer + * Switched to cosine interpolation. + * Added optimizations discovered with profiler. Reduced running time by 18%. + */ + + + +#include "landscape.h" + +#include +#include + +#include "minorGems/util/SimpleVector.h" + + + +double landscape( double inX, double inY, double inT, + double inBaseFrequency, double inRoughness, + int inDetail ) { + + if( inDetail < 0 ) { + return 0.0; + } + else { + // frequency of octave + double frequency = inBaseFrequency * pow( 2, inDetail ); + double amplitude = pow( inRoughness, inDetail ); + return amplitude * noise4d( inX * frequency, + inY * frequency, + // index different planes of noise + inDetail, + inT ) + + landscape( inX, inY, inT, inBaseFrequency, inRoughness, + inDetail - 1 ); + } + } + + + +double variableRoughnessLandscape( double inX, double inY, double inT, + double inBaseFrequency, + double inRoughnessChangeFrequency, + double inMinRoughness, + double inMaxRoughness, + int inDetail ) { + + double roughnessFreqX = inX * inRoughnessChangeFrequency; + double roughnessFreqY = inY * inRoughnessChangeFrequency; + + // use low-frequency noise 4d to select landscape roughness + // between 0 and 1 + double roughness = + ( noise4d( 6, roughnessFreqX, roughnessFreqY, inT ) + 1 ) / 2; + + // move roughness into specified range + roughness = + roughness * ( inMaxRoughness - inMinRoughness ) + + inMinRoughness; + + return landscape( inX, inY, inT, inBaseFrequency, roughness, inDetail ); + } + + + +int getRandomPoints( double inStartX, double inEndX, + double inStartY, double inEndY, + double inT, + double inSampleStepSize, + double inDensity, + double **outXCoordinates, + double **outYCoordinates ) { + + SimpleVector *xCoordinates = new SimpleVector(); + SimpleVector *yCoordinates = new SimpleVector(); + + // discretize startX and start Y so that sample grid for differently-placed + // windows always meshes + // use ceil to ensure that starting points are always inside the + // inStart/inEnd bounds + double discretizedStartX = + inSampleStepSize * ceil( inStartX / inSampleStepSize ); + double discretizedStartY = + inSampleStepSize * ceil( inStartY / inSampleStepSize ); + + // put a point wherever we have a zero-crossing + double lastSample = 1; + + for( double x=discretizedStartX; x<=inEndX; x+=inSampleStepSize ) { + for( double y=discretizedStartY; y<=inEndY; y+=inSampleStepSize ) { + double landscapeSample = + variableRoughnessLandscape( + 30 * x + 1000, 30 * y + 1000, inT + 1000, + 0.01, 0.001, 0.25, 0.65, 0 ); + + // shift landscape up to reduce chance of zero-crossing + landscapeSample = (1-inDensity) * 0.5 + 0.5 + landscapeSample ; + + if( landscapeSample < 0 && + lastSample >= 0 || + landscapeSample >= 0 && + landscapeSample < 0 ) { + + // sign change + + // hit + xCoordinates->push_back( x ); + yCoordinates->push_back( y ); + } + + lastSample = landscapeSample; + } + } + + *outXCoordinates = xCoordinates->getElementArray(); + *outYCoordinates = yCoordinates->getElementArray(); + + int numPoints = xCoordinates->size(); + + delete xCoordinates; + delete yCoordinates; + + return numPoints; + } + + + +/** + * Computes a 32-bit random number. + * Use linear congruential method. + * + * @param inSeed the seed to use. + */ +// this is the readable version of the funcion +// it has been turned into a set of macros below +inline unsigned int random32_readable( unsigned int inSeed ) { + // this is the true hot-spot of the entire landscape function + // thus, optimization is warranted. + + // multiplier = 3141592621 + // use hex to avoid warnings + //unsigned int multiplier = 0xBB40E62D; + //unsigned int increment = 1; + + // better: + // unsigned int multiplier = 196314165 + // unsigned int increment = 907633515 + + // this will automatically be mod-ed by 2^32 because of the limit + // of the unsigned int type + // return multiplier * inSeed + increment; + //return 0xBB40E62D * inSeed + 1; + //return 196314165 * inSeed + 907633515; + + //int n = ( inSeed << 13 ) ^ inSeed; + //return n * (n * n * 15731 + 789221) + 1376312589; + + //const unsigned int Num1 = (inSeed * 0xFEA09B9DU) + 1; + //const unsigned int Num2 = ((inSeed * 0xB89C8895U) + 1) >> 16; + //return Num1 ^ Num2; + + /* + unsigned int rseed=(inSeed*15064013)^(inSeed*99991+604322121)^(inSeed*45120321)^(inSeed*5034121+13); + + const unsigned int Num1 = (inSeed * 0xFEA09B9DU) + 1; + + const unsigned int Num2 = ((inSeed * 0xB89C8895U) + 1) >> 16; + + rseed *= Num1 ^ Num2; + + return rseed; + */ + + const unsigned int Num1 = (inSeed * 0xFEA09B9DU) + 1; + const unsigned int Num2 = ((inSeed^Num1) * 0x9C129511U) + 1; + const unsigned int Num3 = (inSeed * 0x2512CFB8U) + 1; + const unsigned int Num4 = ((inSeed^Num3) * 0xB89C8895U) + 1; + const unsigned int Num5 = (inSeed * 0x6BF962C1U) + 1; + const unsigned int Num6 = ((inSeed^Num5) * 0x4BF962C1U) + 1; + + return Num2 ^ (Num4 >> 11) ^ (Num6 >> 22); + } + + +// faster as a set of macros +#define Num1( inSeed ) \ + ( ( inSeed * 0xFEA09B9DU ) + 1 ) + +#define Num2( inSeed ) \ + ( ( ( inSeed ^ Num1( inSeed ) ) * 0x9C129511U ) + 1 ) + +#define Num3( inSeed ) \ + ( ( inSeed * 0x2512CFB8U ) + 1 ) + +#define Num4( inSeed ) \ + ( ( ( inSeed ^ Num3( inSeed ) ) * 0xB89C8895U ) + 1 ) + +#define Num5( inSeed ) \ + ( ( inSeed * 0x6BF962C1U ) + 1 ) + +#define Num6( inSeed ) \ + ( ( ( inSeed ^ Num5( inSeed ) ) * 0x4BF962C1U ) + 1 ) + +#define random32( inSeed ) \ + ( Num2( inSeed ) ^ (Num4( inSeed ) >> 11) ^ (Num6( inSeed ) >> 22) ) + + + + + + + +#define invMaxIntAsDouble 2.32830643708e-10 +// 1/(x/2) = 2*(1/x) +//double invHalfMaxIntAsDouble = 2 * invMaxIntAsDouble; +// 2.32830643708e-10 +//+ 2.32830643708e-10 +//------------------- +// 4.65661287416e-10 +#define invHalfMaxIntAsDouble 4.65661287416e-10 + + + +#define mixFour( x, y, z, t ) ( x ^ (y * 57) ^ (z * 131) ^ (t * 2383) ) + + + +/** + * Maps 4d integer coordinates into a [-1..1] noise space. + * + * @param x, y, z, t the 4d coordinates. + * + * @return a random value in the range [-1..1] + */ +// keep readable version around for reference +// it has been replaced by a macro below +inline double noise4dInt32_readable( unsigned int x, + unsigned int y, + unsigned int z, + unsigned int t ) { + + + //double maxIntAsDouble = 4294967295.0; + + // modular addition automatic + // multiply x, y, z, and t by distinct primes to + // avoid correllations. + // using xor ( ^ ) here seems to avoid correllations that show + // up when using addition. + + // mix x, y, z, and t + unsigned int randomSeed = + x ^ + y * 57 ^ + z * 131 ^ + t * 2383; + + // a random value between 0 and max unsigned int + unsigned int randomValue = random32( randomSeed ); + + // a random value between 0 and 2 + double zeroTwoValue = randomValue * invHalfMaxIntAsDouble; + + // a random value between -1 and 1 + return zeroTwoValue - 1; + } + + +// noise4dInt32 function call itself was the slowest spot in code +// (found with profiler) +// turn into a set of macros + +// matches original parameter format +#define noise4dInt32( x, y, z, t ) \ + random32( mixFour( x, y, z, t ) ) \ + * invHalfMaxIntAsDouble - 1 + +// problem: now that random32 is a macro, we are passing the unevaluated +// expression, ( x ^ (y * 57) ^ (z * 131) ^ (t * 2383) ), down into it. +// it is being evaluated 6 times within the depths of the random32 macro + +// thus, we need to provide a new format where the caller can precompute +// the mix for us. This is even faster. +#define noise1dInt32( precomputedMix ) \ + random32( precomputedMix ) \ + * invHalfMaxIntAsDouble - 1 + + + + +/* + * The following functions (blendNoiseNd) do 4d linear interpolation + * one dimension at a time. + * + * The end result is 8 calls to blendNoise1d (and 16 calls to noise4dInt32). + * + * This method was inspired by the functional implementations---I am + * decomposing a complicated problem into sub-problems that are easier + * to solve. + */ + + +// faster than f * b + (1-f) * a +// one less multiply +#define linearInterpolation( t, a, b ) ( a + t * ( b - a ) ) + + +/** + * Blends 4d discrete (integer-parameter) noise function along one dimension + * with 3 fixed integer parameters. + */ +inline double blendNoise1d( double x, + unsigned int y, + unsigned int z, + unsigned int t ) { + + double floorX = floor( x ); + unsigned int floorIntX = (unsigned int)floorX; + + if( floorX == x ) { + unsigned int precomputedMix = mixFour( floorIntX, y, z, t ); + + return noise1dInt32( precomputedMix ); + } + else { + unsigned int ceilIntX = floorIntX + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( x - floorX ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + + // need to pre-store intermediate values because noise4dInt32 is a + // macro + // thus, we end up calling the noise1dInt32 function instead + + unsigned int precomputedMix = mixFour( floorIntX, y, z, t ); + double valueAtFloor = noise1dInt32( precomputedMix ); + + precomputedMix = mixFour( ceilIntX, y, z, t ); + double valueAtCeiling = noise1dInt32( precomputedMix ); + + return linearInterpolation( f, valueAtFloor, valueAtCeiling ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 2 dimensions + * with 2 fixed integer parameters. + */ +double blendNoise2d( double x, + double y, + unsigned int z, + unsigned int t ) { + + double floorY = floor( y ); + unsigned int floorIntY = (unsigned int)floorY; + + if( floorY == y ) { + return blendNoise1d( x, floorIntY, z, t ); + } + else { + unsigned int ceilIntY = floorIntY + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( y - floorY ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise1d( x, ceilIntY, z, t ) + + ( 1 - f ) * blendNoise1d( x, floorIntY, z, t ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 3 dimensions + * with 1 fixed integer parameters. + */ +double blendNoise3d( double x, + double y, + double z, + unsigned int t ) { + + double floorZ = floor( z ); + unsigned int floorIntZ = (unsigned int)floorZ; + + if( floorZ == z ) { + return blendNoise2d( x, y, floorIntZ, t ); + } + else { + unsigned int ceilIntZ = floorIntZ + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( z - floorZ ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise2d( x, y, ceilIntZ, t ) + + ( 1 - f ) * blendNoise2d( x, y, floorIntZ, t ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 4 dimensions. + */ +double noise4d( double x, + double y, + double z, + double t ) { + + double floorT = floor( t ); + unsigned int floorIntT = (unsigned int)floorT; + + if( floorT == t ) { + return blendNoise3d( x, y, z, floorIntT ); + } + else { + unsigned int ceilIntT = floorIntT + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( t - floorT ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise3d( x, y, z, ceilIntT ) + + ( 1 - f ) * blendNoise3d( x, y, z, floorIntT ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 3 dimensions + * to get a 3D noise function. + */ +double noise3d( double x, + double y, + double z ) { + return blendNoise3d( x, y, z, 0 ); + } + + + + + diff --git a/gamma256/gameSource/landscape.h b/gamma256/gameSource/landscape.h new file mode 100644 index 0000000..4e7ede2 --- /dev/null +++ b/gamma256/gameSource/landscape.h @@ -0,0 +1,113 @@ + + + +#ifndef LANDSCAPE_INCLUDED +#define LANDSCAPE_INCLUDED + + + +/** + * Gets height samples from an "infinite" fractal landscape. + * The landscape can change over time by varying t. + * + * @param x the x coordinate of the sample. + * @param y the y coordinate of the sample. + * @param t the time of the sample. + * @param baseFrequency the frequency to use for the lowest detail component. + * @param inRoughness the roughness of the landscape (how much high frequencies + * are factored in). Should be in the range [0..1] with 0 making a very + * smooth landscape and 1 making a very rough landscape. + * @param detail the detail level. Larger numbers result in more + * detail. Defaults to 10. + * + * @return the height of the landscape at the sample point/time. + */ +double landscape( double inX, double inY, double inT, + double inBaseFrequency, double inRoughness, + int inDetail = 10 ); + + + +/** + * Samples height of a landscape that varies in roughness over the xy plane. + * + * @params same as for landscape, except: + * @param inRoughnessChangeFrequency the rate at which roughness changes + * over space. Should, in general, be less than inBaseFrequency. + * @param inMinRoughness the minimum roughness value, in the range [0..1]. + * @param inMaxRoughness the maximum roughness value, in the range [0..1]. + * + * @return same as for landscape. + */ +double variableRoughnessLandscape( double inX, double inY, double inT, + double inBaseFrequency, + double inRoughnessChangeFrequency, + double inMinRoughness, + double inMaxRoughness, + int inDetail ); + + + +/** + * Computes linearly-blended random values in the range [-1..1] from a + * 4d parameterized noise space. + * + * @param x, y, z, t the 4d floating-point coordinates. + * + * @return a blended random value in the range [-1..1]. + */ +double noise4d( double x, + double y, + double z, + double t ); + + + +/** + * Computes linearly-blended random values in the range [-1..1] from a + * 4d parameterized noise space (keeping one dimension constant). + * + * Should be faster than noise4D. + * + * @param x, y, z the 3d floating-point coordinates. + * + * @return a blended random value in the range [-1..1]. + */ +double noise3d( double x, + double y, + double z ); + + + +/** + * Gets a set of randomly-chosen (though stable) points in a given + * region of the landscape. + * + * @param inStartX, inEndX the x region. + * @param inStartY, inEndY the y region. + * @param inT the time. + * @param inSampleStepSize the step size in the sample grid. + * Higher values are faster but result in sparser distributions of points. + * @param inDensity the density of points, in the range [0,1]. + * @param outXCoordinates pointer to where array of x coordinates should + * be returned. Array must be destroyed by caller. + * @param outYCoordinates pointer to where array of x coordinates should + * be returned. Array must be destroyed by caller. + * + * @return the number of points (the length of outXCoordinates). + */ +int getRandomPoints( double inStartX, double inEndX, + double inStartY, double inEndY, + double inT, + double inSampleStepSize, + double inDensity, + double **outXCoordinates, + double **outYCoordinates ); + + + +#endif + + + + diff --git a/gamma256/gameSource/mac/SDLMain.h b/gamma256/gameSource/mac/SDLMain.h new file mode 100644 index 0000000..4683df5 --- /dev/null +++ b/gamma256/gameSource/mac/SDLMain.h @@ -0,0 +1,11 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +@end diff --git a/gamma256/gameSource/mac/SDLMain.m b/gamma256/gameSource/mac/SDLMain.m new file mode 100644 index 0000000..122fcc8 --- /dev/null +++ b/gamma256/gameSource/mac/SDLMain.m @@ -0,0 +1,384 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import +#import "SDLMain.h" +#import /* for MAXPATHLEN */ +#import + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) { + assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } + +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } + [ aMenu sizeToFit ]; +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + [SDLApplication poseAsClass:[NSApplication class]]; + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + diff --git a/gamma256/gameSource/map.cpp b/gamma256/gameSource/map.cpp new file mode 100644 index 0000000..2f69bc1 --- /dev/null +++ b/gamma256/gameSource/map.cpp @@ -0,0 +1,297 @@ +#include "map.h" + +#include "landscape.h" + + +#include "minorGems/util/SimpleVector.h" + +#include + + + +int seed = time( NULL ); +//int seed = 10; + +extern int tileH; +extern int tileW; + + + +char isBlockedGrid( int inGridX, int inGridY, char inDoNotRecurse ); +char isBlockedGridHash( int inGridX, int inGridY, char inDoNotRecurse ); + + + + +char allNeighborsBlocked( int inGridX, int inGridY ) { + if( ! isBlockedGridHash( inGridX, inGridY - 1, true ) ) { + // top + return false; + } + if( ! isBlockedGridHash( inGridX + 1, inGridY, true ) ) { + // right + return false; + } + if( ! isBlockedGridHash( inGridX, inGridY + 1, true ) ) { + // bottom + return false; + } + if( ! isBlockedGridHash( inGridX - 1, inGridY, true ) ) { + // left + return false; + } + + // all neighbors blocked + return true; + } + + + +#include "HashTable.h" + +HashTable blockedHashTable( 3000 ); + + +char isBlockedGridHash( int inGridX, int inGridY, char inDoNotRecurse ) { + + char found; + + char blocked = blockedHashTable.lookup( inGridX, inGridY, &found ); + + if( found ) { + return blocked; + } + + // else not found + + // call real function to get result + blocked = isBlockedGrid( inGridX, inGridY, inDoNotRecurse ); + + // only insert result if we called the full recursive version of + // inBlockGrid. Otherwise, we aren't getting the real result back + // so we shouldn't be tracking it in our table + if( ! inDoNotRecurse ) { + blockedHashTable.insert( inGridX, inGridY, blocked ); + } + + return blocked; + } + + + +char isBlocked( int inX, int inY ) { + // reduce to grid coordinates + int gridX = inX / tileW; + int gridY = inY / tileH; + + // try hash first + return isBlockedGridHash( gridX, gridY, false ); + } + + + +char isBlockedGrid( int inGridX, int inGridY, char inDoNotRecurse ) { + + // wall along far left and top + if( inGridX <=0 || inGridY <= 0 ) { + return true; + } + + // empty passage at top + if( inGridY <= 1 ) { + return false; + } + + + // make a grid of empty spaces from which blocks can be + // removed below to make a maze + if( inGridX % 2 != 0 && inGridY % 2 != 0 ) { + + // however, if all neighbors are blocked, this should + // be blocked too + if( !inDoNotRecurse ) { + return allNeighborsBlocked( inGridX, inGridY ); + } + else { + // non-recursive mode + return false; + } + } + + + // blocks get denser as y increases + double threshold = 1 - inGridY / 20.0; + + double randValue = noise3d( inGridX, inGridY, seed ); + char returnValue = randValue > threshold; + + if( returnValue ) { + return true; + } + else { + // not blocked + + // should be blocked if all neighbors are blocked + if( !inDoNotRecurse ) { + return allNeighborsBlocked( inGridX, inGridY ); + } + else { + // non-recursive mode + return false; + } + } + + } + + +struct pairStruct { + int x; + int y; + }; +typedef struct pairStruct pair; + + + +SimpleVector openedChests; + + + +char isChest( int inX, int inY ) { + + // reduce to grid coordinates + int gridX = inX / tileW; + int gridY = inY / tileH; + + // chests get denser as y increases + // no chests where gridY < 5 + // even less dense than blocks + double threshold = 1 - ( gridY - 5 ) / 200.0; + + // use different seed than for blocks + double randValue = noise3d( 73642 * gridX, 283277 * gridY, seed * 987423 ); + char returnValue = randValue > threshold; + + if( returnValue ) { + // make sure it's not opened already + for( int i=0; ix == gridX && p->y == gridY ) { + return CHEST_OPEN; + } + } + return CHEST_CLOSED; + } + else { + return CHEST_NONE; + } + } + + + +unsigned char getChestCode( int inX, int inY ) { + // reduce to grid coordinates + int gridX = inX / tileW; + int gridY = inY / tileH; + + // use different seed + double randValue = noise3d( 37462 * gridX, 1747 * gridY, seed * 3748147 ); + + + /* + Note: + Original versions of Passage (up to v3 for Mac/PC/Unix) contained a bug. + + This was the original code: + + // use it to fill first 6 binary digits + return (unsigned char)( randValue * 15 ) & 0x3F; + + Note two things: + 1. That randValue is *signed* (and can be negative), so casting + it to an uchar is a weird thing to do. + + 2. That we're only multiplying randValue by 15, which only fills + the first 4 binary digits (there used to be only 4 gems). + Thus, if randValue is actually *positive* between 0 and 1, only + the first 4 gems have a chance of being activated. + + What this code should have done was convert randValue to the range + [0..1] first and then multiply it by 63 (to convert it to a 6-bit + random binary string). + + However, this code *seems* to work, anyway, since 2's compliment was + being invoked to handle casting of negative randValues, so all 6 gems + were sometimes being activated. + + However, the 6-bit gem strings were always of the form 00XXXX or 11XXXX + (where XXXX is a random 4-bit sting) due to the 2's compliment and + the fact that the negative numbers being complimented were never less + than -15. + + So, 2 of the gems were tied together in their on/off status, essentially + turning 6 gems into 5. + + The problem appeared on platforms (like iPhone) that don't preserve + 2's compliment bits when doing signed to unsigned casting. On these + platforms, all negative numbers were being casted to 0 when turned to + uchars. Thus, half the treasure chests, on average, had no gems at all. + + Of course, a proper fix, as described above, would go beyond just making + it work on these platforms, but would also change the behavior of the game + (those last two gems would no longer be tied together in their on-off + status). + + After much deliberation, I came to the following solution: + + The following code emulates 2's compliment casting, whether the platform + does it or not, making behavior on all platforms identical. + + (And preserving the original bug!!) + */ + + unsigned char castedChar; + if( randValue < 0 ) { + castedChar = (unsigned char)( 256 + (char)( randValue * 15 ) ); + } + else { + castedChar = (unsigned char)( randValue * 15 ); + } + + return castedChar & 0x3F; + } + + + +void getChestCenter( int inX, int inY, int *outCenterX, int *outCenterY ) { + int gridX = inX / tileW; + int gridY = inY / tileH; + + *outCenterX = gridX * tileW + tileW / 2; + *outCenterY = gridY * tileH + tileH / 2; + } + + + + +void openChest( int inX, int inY ) { + pair p; + + // reduce to grid coordinates + p.x = inX / tileW; + p.y = inY / tileH; + + openedChests.push_back( p ); + } + + + +void resetMap() { + // new seed + seed = time( NULL ); + //seed = 10; + + openedChests.deleteAll(); + + blockedHashTable.clear(); + } + diff --git a/gamma256/gameSource/map.h b/gamma256/gameSource/map.h new file mode 100644 index 0000000..d391dd7 --- /dev/null +++ b/gamma256/gameSource/map.h @@ -0,0 +1,30 @@ +// checks if position is blocked by wall +char isBlocked( int inX, int inY ); + + +// checks if chest is present +// assumes position is not blocked +#define CHEST_NONE 0 +#define CHEST_CLOSED 1 +#define CHEST_OPEN 2 +char isChest( int inX, int inY ); + + +// 8-bit binary indicating which of six external chest gems are present +#define CHEST_RED_GEM 0x01 +#define CHEST_GREEN_GEM 0x02 +#define CHEST_ORANGE_GEM 0x04 +#define CHEST_BLUE_GEM 0x08 +#define CHEST_YELLOW_GEM 0x10 +#define CHEST_MAGENTA_GEM 0x20 + +// assumes a chest is present +unsigned char getChestCode( int inX, int inY ); + +void getChestCenter( int inX, int inY, int *outCenterX, int *outCenterY ); + +void openChest( int inX, int inY ); + + +// resets map to a fresh state +void resetMap(); diff --git a/gamma256/gameSource/music.png b/gamma256/gameSource/music.png new file mode 100644 index 0000000000000000000000000000000000000000..7a6847bf80d5d01aa5798b6060b373aa76502a6f GIT binary patch literal 1277 zcmYLJX;4#F6uux!FrlT6O2A=NR8)c%L?}pDtcVfW+5(DAoFIV;NJK@WCJ|AJT9ejM zq8amaBAZ%c!zwC4D~kdqY?C$=C`1epkR=IAw$~Tb>G$KFJNKUR-80`k=M={5jj&zn zxD)_j8@X#o902C7$UXsOj;zA?^i=>@yo%fr`rS#{L|+G$8?yYupa(zhVeq{TouU1k z=Gb4jfI33$s(Hhytw4K zm5n=63fof0iN7BH-d}u4cnp4vZDVN#IMU4RE${Yw^bPss77<1d_|`FZwnbNnrVs8^ zjm;m3ZNQw0%S{h-)i{38Q+7#fh`U4H^PrgoB~ctb=AeI$3l$tU5(4P<+n}wLp1A|Q zgbYD_&}nszk)NYg|L=#`$7DA|lGrSDqeoIXwd0~AnO+)mpIH(Ku* zm8$!$jX19%uBbDhbv5{mi;S#Zuep{7Xzat`klQ7%UoMq7Wd9x^sU>@Pn_HEoS$eTI zBxheq%Yp9GEikU^$1{e&o+QFiZdG)=6-cpph9&pgr}dN1+$lvMh;mV1?=nFlVdN~c z1}*29W&ni+%#Q|?H0NyIK0Y*Cm0rMFD9{eNxTIBZ<3yL;(t~7T9NnlNtHr@%5r#5& zc;tY)QsJ@o%2UE!>HEI|`x`SUXGWZI{k}eyT*uJx>D`RGC=Pu#O6Gq!ly$PO)9}DW z!#7%D-mf2_ddFU0bP+K<3%l?thv2vSBrCKDc!U|v>FG2au?}xaLepL~5#|K+ZkEZ)4{h$N_6b&B zO^_1}WiA?T61(OOS6Z!7HuqDjj6E!EENpJI8hwjV=7OXI?z}n9@;d}VZZHeCOh)wk zwx7Q8rP_q5(euGsi|>eE0B|X^0ckZ12>ED0mplLqLU5ITcbFuN77VmG9Ab&;pk-Rk z{6hMZ*fu<@SBc^5R9lXsluW_leo=5G-4iOY>0BgXChc=A&Vm@m1qB3KTDHjr6B+3@ z$pzD&?tPFbzx;`#YkiOF5FR9m#h>>ZR@r1C0Z0__DkJm6Vw%BG4j=|)ZUtOP$Z+^* zIL=TB2DH3fn{Ou%4|Cr-x#+15mo!I3BbYR$?)LxJn<{`r3$k7US}ynnev%e2!;rQI zW=p^))1Xd6%IaMh*J(R~=O)cQb3Ak<4fVR!pQY<`iqV2|Ji3kc9}a`2jZ|mFE$Ty& zh#IF>MvG2ND|IZpFrDHE>TYo<6c6cI^+-W>!AmOcq3B=zEG$RH8i@RM?~X@dq`dzC DT@;56 literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/music/music.tga b/gamma256/gameSource/music/music.tga new file mode 100644 index 0000000000000000000000000000000000000000..6f4e400cc7984777448330906d55c0539e0686dc GIT binary patch literal 142002 zcmeI5!Exh85JgcNg^vzJS!@QrISFTA-<*U~Py$Y1%fZk?fCQhG>gEhYP&3p+p}4(gnhrn8`QJ1p=k(k6^3&?1!9E)?d!T0G0} zGqV0!5RpTq?XR4beB{I4(GU7TKja>ek9_C>YUgL}4f)8&e#t!`ANkM&)XvY`8}gBl z{gQh?KJuXls5_jWr6f-YNI(J-kigvuEN=$NE1!drE>~ZVD2D6CFSy8rT0Zj854i{A zBOiKz+WDD#Lq77cUvdw~M?UlbwevIghJ55>zvLc}k9_C>YUgL}4f)8&e#t!`ANkM& z)XvY`8}gBl{gQh?KJuXlsGXm=H{>H9`z7~)e1_&zMx3VvBp?9^NZ{@SmWN|`<@zvLc}k9_C>YUgL}4f)8&e#t!`ANkM&)XvY`8}gBl{gQh?K11_4 zv>as~wuB%5f6B%u1mu&zDL;~rd`Lh)kjAy{kWVSeQvwo@fCMCPcLK|Yuky;zp^+|E zUymq;>&7p*$b?!x^3e~u2jn9kdVt#bnR`P%^08lX56DM8^Z>Q)C9c;<;T zXP!tio_Qk8_$VjW86V~3I^&rq z(u`-GNHadl$@QM&qkH7K_3!t5C+A4ZU;fcNi9Ew~{iA;(|8PChJ^LrBnd{cSpa01@ z((;!-y`dkT-VbGqJtZIk2}nQ!cPFqsK+7xNS46s8eLbQWt{cDLA`@!)$VWfq9*~cG z=mBcyXYLL8$j5%kJs=lvND^)Hj+?Y`SD zwe#~Wx690VyZ$^p_vx4E<#4v)RHL`^9KHUxz8cSOqw}}^bEDFGH9gCi?fpBt|G!%O z`}E7>{4;v}qx)@i{?`BA^Wt&+JG%d^U;oVCBs^pA`yM9}eE;b@S)h;!wS46BlOKvD zJU^Gf^m5YCHz5d?(&|1&v{H_1oq~%Zc=Ei<(^^fuL zp9yU7+u?PAcl7Noy(Ry7YuFc&-je@T&AhKI`S-4W^!%gqxBj=!EcT}TvUDezP=7@} z59pWq5+}~7-ok(M`h8y9oL8x5=&P^jAN_yRKXZ$5&PRHSe@5@$*82DK+jMw2ms38x z+}2z6YxMfhfFcibeG8wx^N-Ho`r~<9)3c1}(Vu(!-~6$ETKzyT)#%pzm-xfWWv<5h z5+C2=B~Nie#WRjEt@$tUam|-{4lkGZy81_tC)*mm{eJi1UsE$KE0mv$Co$Xi@3apeES?M z)viYr!*$~qTx3EmANlBq+ynBF4?RHb{LH-}ANklXxd-GUA9{e=`I&n|KJu|&au3Ky zKJ);!^E3B`eB@)l`N+q9$vq$+`OpK@&d=N%@{y1Il6yctzAOQ*7oxt+ptGx1aXr#;4*CUGI zy73DxGNG1_eDp)^0r~XL=lDO=c<*$+<$EU|`8?Bndv^$wMcyN|{vXT#@9%H92Mqs@ z>@V|BGbgqF8OuNJm;1&3Wsg%chFbsVAN`PfKt9jMhqIB|d6@6DeB^VQhw~?t8b8=y zxo7x%AuDReP}lH(WISu8W)0N6)xUQ=WI|0Q)O**{nx8dOvu5hn{Kn_AVsAd&Q)=!h z^&a({t~-Nh&Ci;tSu=HO{%4JkSUv0g(gW1q@8`~+h`;_^_9fy&4^tbza2v=!Wy_xX zA zvZ8LiU;ZxjSL509{c>Mw_O~XUF}(Mh{KhXMh#Vpv@g#rhD84oSv&NGZb#M2}+NfLi zm%mGWulr?A>U-sHz2DY&)=WK0{jK-g8c!w{UFKKg_e)#j<$rpc3eFKjvbxXy`u*_p zhlEz1d&G0!_sIVd|5#7{UQRypvA=Q;$mhO%YMvL2`3QgJp=M6%hIyENKQOvhO@97v zP5c+Fe>UZge0IzKp#9yg{*8XCuIE8~M(^Kho_p_C5Bcet9_nEY)HUfY+Ko<0fCGt_-ndtDW4 zroN7JWhVm51E{?6xg6 +#include + +#include +#include + +int sampleRate = 22050; +//int sampleRate = 11025; + + + + +Image *musicImage = NULL; +int w, h; + +// total number of samples played so far +int streamSamples = 0; + +// offset into grid at start +// for testing +int gridStartOffset = 0; + + + +// overal loudness of music +double musicLoudness = 1.0; + + + + + + +// one grid step in seconds +double gridStepDuration = 0.25; +int gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); + +double entireGridDuraton; + + +// c +double keyFrequency = 261.63; + + +int numTimbres = 4; + +Timbre *musicTimbres[ 4 ]; + +int numEnvelopes = 4; + +Envelope *musicEnvelopes[ 4 ]; + + + +class Note { + public: + // index into musicTimbres array + int mTimbreNumber; + + // index into musicEnvelopes array + int mEnvelopeNumber; + + int mScaleNoteNumber; + + // additional loudness adjustment + // places note in stereo space + double mLoudnessLeft; + double mLoudnessRight; + + + // start time, in seconds from start of note grid + double mStartTime; + + // duration in seconds + double mDuration; + + // used when note is currently playing to track progress in note + // negative if we should wait before starting to play the note + int mCurrentSampleNumber; + + // duration in samples + int mNumSamples; + + + }; + + +// isomorphic to our music image, except only has an entry for each note +// start (all others, including grid spots that contain note continuations, +// are NULL) +// indexed as noteGrid[y][x] +Note ***noteGrid; + + +SimpleVector currentlyPlayingNotes; + + + +// need to synch these with audio thread + +void setMusicLoudness( double inLoudness ) { + SDL_LockAudio(); + + musicLoudness = inLoudness; + + SDL_UnlockAudio(); + } + + + +void restartMusic() { + SDL_LockAudio(); + + // return to beginning (and forget samples we've played so far) + streamSamples = 0; + + // drop all currently-playing notes + currentlyPlayingNotes.deleteAll(); + + SDL_UnlockAudio(); + } + + + + +// called by SDL to get more samples +void audioCallback( void *inUserData, Uint8 *inStream, int inLengthToFill ) { + + // 2 bytes for each channel of stereo sample + int numSamples = inLengthToFill / 4; + + + Sint16 *samplesL = new Sint16[ numSamples ]; + Sint16 *samplesR = new Sint16[ numSamples ]; + + // first, zero-out the buffer to prepare it for our sum of note samples + // each sample is 2 bytes + memset( samplesL, 0, 2 * numSamples ); + memset( samplesR, 0, 2 * numSamples ); + + + int i; + + + // hop through all grid steps that *start* in this stream buffer + // add notes that start during this stream buffer + + // how far into stream buffer before we hit our first grid step? + int startOfFirstGridStep = streamSamples % gridStepDurationInSamples; + + if( startOfFirstGridStep != 0 ) { + startOfFirstGridStep = + gridStepDurationInSamples - startOfFirstGridStep; + } + + + // hop from start of grid step to start of next grid step + // ignore samples in between, since notes don't start there, + // and all we're doing right now is finding notes that start + for( i=startOfFirstGridStep; + imCurrentSampleNumber = -i; + } + } + } + + streamSamples += numSamples; + + + // loop over all current notes and add their samples to buffer + + for( int n=0; nmScaleNoteNumber; + Timbre *timbre = musicTimbres[ note->mTimbreNumber ]; + int tableLength = timbre->mWaveTableLengths[ waveTableNumber ]; + + Sint16 *waveTable = timbre->mWaveTable[ waveTableNumber ]; + + Envelope *env = musicEnvelopes[ note->mEnvelopeNumber ]; + double *envLevels = + env->getEnvelope( + // index envelope by number of grid steps in note + note->mNumSamples / gridStepDurationInSamples ); + + + double noteLoudnessL = note->mLoudnessLeft; + double noteLoudnessR = note->mLoudnessRight; + + // do this outside inner loop + noteLoudnessL *= musicLoudness; + noteLoudnessR *= musicLoudness; + + + int noteStartInBuffer = 0; + int noteEndInBuffer = numSamples; + + if( note->mCurrentSampleNumber < 0 ) { + // delay before note starts in this sample buffer + noteStartInBuffer = - note->mCurrentSampleNumber; + + // we've taken account of the delay + note->mCurrentSampleNumber = 0; + } + + char endNote = false; + + int numSamplesLeftInNote = + note->mNumSamples - note->mCurrentSampleNumber; + + if( noteStartInBuffer + numSamplesLeftInNote < noteEndInBuffer ) { + // note ends before end of buffer + noteEndInBuffer = noteStartInBuffer + numSamplesLeftInNote; + endNote = true; + } + + + int waveTablePos = note->mCurrentSampleNumber % tableLength; + + int currentSampleNumber = note->mCurrentSampleNumber; + + for( i=noteStartInBuffer; i != noteEndInBuffer; i++ ) { + double envelope = envLevels[ currentSampleNumber ]; + + double monoSample = envelope * + waveTable[ waveTablePos ]; + + + samplesL[i] += (Sint16)( noteLoudnessL * monoSample ); + samplesR[i] += (Sint16)( noteLoudnessR * monoSample ); + + currentSampleNumber ++; + + waveTablePos ++; + + // avoid using mod operator (%) in inner loop + // found with profiler + if( waveTablePos == tableLength ) { + // back to start of table + waveTablePos = 0; + } + + } + + note->mCurrentSampleNumber += ( noteEndInBuffer - noteStartInBuffer ); + + if( endNote ) { + // note ended in this buffer + currentlyPlayingNotes.deleteElement( n ); + n--; + } + + } + + + // now copy samples into Uint8 buffer + int streamPosition = 0; + for( i=0; i != numSamples; i++ ) { + Sint16 intSampleL = samplesL[i]; + Sint16 intSampleR = samplesR[i]; + + inStream[ streamPosition ] = (Uint8)( intSampleL & 0xFF ); + inStream[ streamPosition + 1 ] = (Uint8)( ( intSampleL >> 8 ) & 0xFF ); + + inStream[ streamPosition + 2 ] = (Uint8)( intSampleR & 0xFF ); + inStream[ streamPosition + 3 ] = (Uint8)( ( intSampleR >> 8 ) & 0xFF ); + + streamPosition += 4; + } + + delete [] samplesL; + delete [] samplesR; + + } + + + +// limit on n, based on Nyquist, when summing sine components +//int nLimit = (int)( sampleRate * M_PI ); +// actually, this is way too many: it takes forever to compute +// use a lower limit instead +// This produces fine results (almost perfect square wave) +int nLimit = 40; + + + +// square wave with period of 2pi +double squareWave( double inT ) { + double sum = 0; + + for( int n=1; ngetWidth(); + h = musicImage->getHeight(); + + // notes are in red and green channel + double *redChannel = musicImage->getChannel( 0 ); + double *greenChannel = musicImage->getChannel( 1 ); + + + entireGridDuraton = gridStepDuration * w; + + + // jump ahead in stream, if needed + streamSamples += gridStartOffset * gridStepDurationInSamples; + + + // blank line of pixels between timbres + int heightPerTimbre = (h+1) / numTimbres - 1; + + + // find the maximum number of simultaneous notes in the song + // take loudness into account + double maxNoteLoudnessInAColumn = 0; + + int x, y; + for( x=0; x 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + noteLoudnessInColumnL += greenChannel[ imageIndex ]; + noteLoudnessInColumnR += redChannel[ imageIndex ]; + + } + } + // pick loudest channel for this column and compare it to + // loudest column/channel seen so far + if( maxNoteLoudnessInAColumn < noteLoudnessInColumnL ) { + maxNoteLoudnessInAColumn = noteLoudnessInColumnL; + } + if( maxNoteLoudnessInAColumn < noteLoudnessInColumnR ) { + maxNoteLoudnessInAColumn = noteLoudnessInColumnR; + } + + } + + + // divide loudness amoung timbres to avoid clipping + double loudnessPerTimbre = 1.0 / maxNoteLoudnessInAColumn; + + // further adjust loudness per channel here as we construct + // each timbre. + // This is easier than tweaking loundness of a given part by hand + // using a painting program + + musicTimbres[0] = new Timbre( sampleRate, 0.6 * loudnessPerTimbre, + keyFrequency, + heightPerTimbre, sawWave ); + musicTimbres[1] = new Timbre( sampleRate, loudnessPerTimbre, + keyFrequency, + heightPerTimbre, sin ); + musicTimbres[2] = new Timbre( sampleRate, 0.4 * loudnessPerTimbre, + keyFrequency / 4, + heightPerTimbre, squareWave ); + musicTimbres[3] = new Timbre( sampleRate, 0.75 * loudnessPerTimbre, + keyFrequency / 4, + heightPerTimbre, smoothedWhiteNoise ); + + + // next, compute the longest note in the song + int maxNoteLength = 0; + + for( y=0; y 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + currentNoteLength ++; + } + else { + currentNoteLength = 0; + } + if( currentNoteLength > maxNoteLength ) { + maxNoteLength = currentNoteLength; + } + } + } + + printf( "Max note length in song = %d\n", maxNoteLength ); + + + + musicEnvelopes[0] = new Envelope( 0.05, 0.7, 0.25, 0.1, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[1] = new Envelope( 0.1, 0.9, 0.0, 0.0, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[2] = new Envelope( 0.25, 0.0, 1.0, 0.1, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[3] = new Envelope( 0.0, 0.2, 0.0, 0.0, + maxNoteLength, + gridStepDurationInSamples ); + + + + + noteGrid = new Note**[ h ]; + + for( int y=0; y 0 || + greenChannel[ imageIndex ] > 0 ) ) { + + + if( notePlaying ) { + // part of note that's already playing + + // one more grid step + noteStart->mDuration += gridStepDuration; + noteStart->mNumSamples += gridStepDurationInSamples; + + } + else { + // start a new note + noteGrid[y][x] = new Note(); + + noteGrid[y][x]->mScaleNoteNumber = noteNumber; + + noteGrid[y][x]->mTimbreNumber = + y / ( heightPerTimbre + 1 ); + + // same as timbre number + noteGrid[y][x]->mEnvelopeNumber = + noteGrid[y][x]->mTimbreNumber; + + // left loudness from green brightness + noteGrid[y][x]->mLoudnessLeft = greenChannel[ imageIndex ]; + + // right loudness from red brightness + noteGrid[y][x]->mLoudnessRight = redChannel[ imageIndex ]; + + noteGrid[y][x]->mStartTime = gridStepDuration * x; + + // one grid step so far + noteGrid[y][x]->mDuration = gridStepDuration; + noteGrid[y][x]->mNumSamples = gridStepDurationInSamples; + + // track if it needs to be continued + notePlaying = true; + noteStart = noteGrid[y][x]; + } + } + else { + // no tone + + if( notePlaying ) { + // stop it + notePlaying = false; + noteStart = NULL; + } + } + } + } + + + } + + + +void startMusic( const char *inTGAFileName ) { + + loadMusicImage( inTGAFileName ); + + SDL_AudioSpec audioFormat; + + /* Set 16-bit stereo audio at 22Khz */ + audioFormat.freq = sampleRate; + audioFormat.format = AUDIO_S16; + audioFormat.channels = 2; + audioFormat.samples = 512; /* A good value for games */ + audioFormat.callback = audioCallback; + audioFormat.userdata = NULL; + + /* Open the audio device and start playing sound! */ + if( SDL_OpenAudio( &audioFormat, NULL ) < 0 ) { + printf( "Unable to open audio: %s\n", SDL_GetError() ); + } + + // set pause to 0 to start audio + SDL_PauseAudio(0); + + + } + + + +void stopMusic() { + SDL_CloseAudio(); + + if( musicImage != NULL ) { + delete musicImage; + musicImage = NULL; + } + + for( int y=0; y?y_4_8QE}B}pd$OHn&Ixs&OL|k;x%Ku-J!{CE)b@D#>T5RnJg?7ox_Wm$zLpm* woP2qX@8^xN(krc+d6RGD?O(9=lgb0etK1DnLaTSp06L4o)78&qol`;+08A20xBvhE literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/numerals3.png b/gamma256/gameSource/numerals3.png new file mode 100644 index 0000000000000000000000000000000000000000..375133963be1f0562de2a9e8faf1f53e20279fb0 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^%s{Np!2~2-%TrGPsc=sh#}J9jy%P-i7!)~J(og>V ze>%I7Q9meRrNud=O}q?@9!w#oZ$+7;qbt20%(RZX6LUYQ`q88pCSLdMB(99*`Rkh* tc|$+GkE|2 literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/score.cpp b/gamma256/gameSource/score.cpp new file mode 100644 index 0000000..284c07a --- /dev/null +++ b/gamma256/gameSource/score.cpp @@ -0,0 +1,110 @@ +#include "score.h" +#include "common.h" + + +#include "minorGems/util/stringUtils.h" +#include "minorGems/graphics/Image.h" + + + +Image *numeralImage; +int numeralW = 3; +int numeralH = 4; + +int imagePixelCount; + +double *numeralRed; +double *numeralGreen; +double *numeralBlue; + +Uint32 *numeralARGB; + + + +void initScore() { + numeralImage = readTGA( "numerals.tga" ); + + imagePixelCount = numeralImage->getWidth() * numeralImage->getHeight(); + + numeralRed = new double[ imagePixelCount ]; + numeralGreen = new double[ imagePixelCount ]; + numeralBlue = new double[ imagePixelCount ]; + + numeralARGB = new Uint32[ imagePixelCount ]; + + for( int i=0; igetChannel(0)[ i ]; + numeralGreen[i] = 255 * numeralImage->getChannel(1)[ i ]; + numeralBlue[i] = 255 * numeralImage->getChannel(2)[ i ]; + + unsigned char r = + (unsigned char)( + numeralRed[ i ] ); + + unsigned char g = + (unsigned char)( + numeralGreen[ i ] ); + + unsigned char b = + (unsigned char)( + numeralBlue[ i ] ); + + numeralARGB[i] = r << 16 | g << 8 | b; + } + } + + + +int getScoreHeight() { + return numeralH; + } + + + +void drawScore( Uint32 *inImage, int inWidth, int inHeight, int inScore ) { + + char *scoreString = autoSprintf( "%d", inScore ); + + int numDigits = strlen( scoreString ); + + int xPosition = inWidth - numDigits * ( numeralW + 1 ); + + for( int i=0; i +typedef uint32_t Uint32; + + + +void initScore(); + + + +int getScoreHeight(); + + + +// draws score in upper-right corner of image +void drawScore( Uint32 *inImage, int inWidth, int inHeight, int inScore ); + + + +void destroyScore(); diff --git a/gamma256/gameSource/settings/fullscreen.ini b/gamma256/gameSource/settings/fullscreen.ini new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/gamma256/gameSource/settings/fullscreen.ini @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/gamma256/gameSource/settings/screenHeight.ini b/gamma256/gameSource/settings/screenHeight.ini new file mode 100644 index 0000000..7ad8022 --- /dev/null +++ b/gamma256/gameSource/settings/screenHeight.ini @@ -0,0 +1 @@ +480 \ No newline at end of file diff --git a/gamma256/gameSource/settings/screenWidth.ini b/gamma256/gameSource/settings/screenWidth.ini new file mode 100644 index 0000000..c328260 --- /dev/null +++ b/gamma256/gameSource/settings/screenWidth.ini @@ -0,0 +1 @@ +640 \ No newline at end of file diff --git a/gamma256/gameSource/spouseSprite.png b/gamma256/gameSource/spouseSprite.png new file mode 100644 index 0000000000000000000000000000000000000000..771991dc2792895f527f95150fe6158b5803f7df GIT binary patch literal 1089 zcmV-H1it%;P)=I@83; z@(`?oaA#5mv`COtT3m{E`)>BlY@&D+*FYQ`mZnC0xqTt3hXl;uC8*8tG}vf`WA5p5pul?nbbm@FswsY^SQt1$@} zgNdcrnC0H&Xy#;|=qo@uvzEGPnzqpzOw@=jSRzWvBw##u_~YKArSnB`SL^rtBTPl@ z7UP?`6MBVbX+p2?JjA4+S9l({V|tI*R(-gb-k(u`JEoT?7E@_gEGOu5$GgH{;&aEl zV&-ICxEt<@Hrg~zEp`1}k&=(Thph0X4%(X ztV+J-Z61^=3afx%1!oly zG_eZM#htZr9LGV8(gll%L5%`{ov7t%lv|AdD~z{i5398C()oUsHb%s-Hr_dFNF7#i zb)r`P%L-$xOhhDgIG~ML0tB@&OMsv@E+jy?8uN)Uj^m&me&??rcVWk zS&mt=AlWIOB5r*JC8od;uP|u^B^*|1V;|$-s0pQ54JJFJxsy&O((yTa#F?zf2@%QC zL{9h{M&yKu;6xRF|t$Q&ZO3 zUazNyUCUH=uL6J?c3qRhoiXyMPd!&Qjq~kd@c!2_jX1jBdiV!%gc?7P00000NkvXX Hu0mjfOaKnB literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/testMusicPlayer.cpp b/gamma256/gameSource/testMusicPlayer.cpp new file mode 100644 index 0000000..d9ec639 --- /dev/null +++ b/gamma256/gameSource/testMusicPlayer.cpp @@ -0,0 +1,32 @@ +#include "musicPlayer.h" + +#include "minorGems/system/Thread.h" + +#include + + +extern void loadMusicImage( char *inTGAFileName ); + +extern void audioCallback( void *inUserData, Uint8 *inStream, + int inLengthToFill ); + + +int main() { + // don't actually start SDL loop, because profiler can't pick + // up callbacks (why not?) + + loadMusicImage( "music.tga" ); + + int length = 512; + + Uint8 *fakeStream = new Uint8[ length ]; + + + for( int i=0; i<20000; i++ ) { + + int x = 5 + 1; + x ++; + + audioCallback( NULL, fakeStream, length ); + } + } diff --git a/gamma256/gameSource/testNoise.cpp b/gamma256/gameSource/testNoise.cpp new file mode 100644 index 0000000..c75c14e --- /dev/null +++ b/gamma256/gameSource/testNoise.cpp @@ -0,0 +1,66 @@ +#include "landscape.h" + +#include + +void printBinary( unsigned char inVal ) { + for( int b=7; b>=0; b-- ) { + if( ( inVal >> b ) & 0x01 == 1 ) { + printf( "1" ); + } + else { + printf( "0" ); + } + } + } + +int main() { + + int numAllZeroOrig = 0; + int numAllZeroNew = 0; + + for( int i=0; i<20; i++ ) { + double value = noise3d( 37462 * i, 1747 * i, i * 3748147 ); + + + + printf( "%d ... %f .... ", i, value ); + + + + unsigned char castedChar = (unsigned char)( value * 15 ); + + unsigned char chestVal = (unsigned char)( value * 15 ) & 0x3F; + + // show in binary + printf( "casted char = %d ... original val = ", castedChar ); + printBinary( chestVal ); + + printf( "\n" ); + + /* + if( chestVal == 0 ) { + numAllZeroOrig ++; + } + + value += 1; + value /= 2; + + unsigned char newVal = (unsigned char)( value * 63 ) & 0x3F; + + printf( "\n ... new value = %f .... ", value ); + printBinary( newVal ); + printf( "\n\n" ); + + if( newVal == 0 ) { + numAllZeroNew ++; + } + */ + + + } + + printf( "Num all zero, orig = %d, new = %d \n", + numAllZeroOrig, numAllZeroNew ); + + return 0; + } diff --git a/gamma256/gameSource/tileSet.png b/gamma256/gameSource/tileSet.png new file mode 100644 index 0000000000000000000000000000000000000000..72e2110c8bba6bf2ef319f65dea31a0b38012e21 GIT binary patch literal 13840 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 X(zSE*S7m^2dV%zHAKWg~wtMq`FSnK* literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/tileTemplate.png b/gamma256/gameSource/tileTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..374eac7f25eacbb68cca57c1b894b162fe819bfb GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^96&sSg9%86aof)aQqw$L978;g-@ST}_kaQigF}WD z*Jn$f!w-z!B>mT&ab*I>FBacD934st>$n#LF&&pkd-P5=dU5!a$cY_WUTQn~RLd$Q z#9wEAR>H#cR=nh?W#eX{voASV#9kl#@4Re=a#okvn!C|h| BPTc?i literal 0 HcmV?d00001 diff --git a/gamma256/gameSource/title.png b/gamma256/gameSource/title.png new file mode 100644 index 0000000000000000000000000000000000000000..49109c1942e290209b874e9e0ef4efc8e786a971 GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^DL^d1!2~4pjeV~GsePU{%^a7;-59AI0Rd?kBJ;yC3wy) z&0&h^3cjsVme#(o*Aw*7OuCx?yMGx&(+3G_mMrI%^?M{Xb{}495Snzgd!=9`*ZzBf v2R8f>wMrIwzgTe~DWM4f?A&27 literal 0 HcmV?d00001 diff --git a/gamma256/prototypes/screenCompress/Makefile b/gamma256/prototypes/screenCompress/Makefile new file mode 100644 index 0000000..d3c2cdd --- /dev/null +++ b/gamma256/prototypes/screenCompress/Makefile @@ -0,0 +1,53 @@ + + +ROOT_PATH = ../../.. + +COMPILE = g++ -g -I${ROOT_PATH} -c +LINK = g++ -I${ROOT_PATH} + + + +MINOR_GEMS_SOURCES = \ +${ROOT_PATH}/minorGems/io/file/linux/PathLinux.cpp \ +#${ROOT_PATH}/minorGems/graphics/linux/ScreenGraphicsLinux.cpp \ +#${ROOT_PATH}/minorGems/system/win32/ThreadWin32.cpp \ +#${ROOT_PATH}/minorGems/system/linux/ThreadLinux.cpp \ + +MINOR_GEMS_OBJECTS = ${MINOR_GEMS_SOURCES:.cpp=.o} + +LIBRARIES = -lSDL + + +all: screenCompress tileSet.tga characterSprite.tga + +screenCompress: screenCompress.o landscape.o ${MINOR_GEMS_OBJECTS} + ${LINK} -o screenCompress screenCompress.o landscape.o ${MINOR_GEMS_OBJECTS} ${LIBRARIES} + + +sdlTest: sdlTest.o + ${LINK} -o sdlTest sdlTest.o ${MINOR_GEMS_OBJECTS} ${LIBRARIES} -lSDLmain + + +screenCompress.o: screenCompress.cpp landscape.h + +landscape.o: landscape.cpp landscape.h + +tileSet.tga: tileSet.png + convert tileSet.png tileSet.tga + +characterSprite.tga: characterSprite.png + convert characterSprite.png characterSprite.tga + + +# +# Generic: +# +# Map all .cpp C++ and C files into .o object files +# +# $@ represents the name.o file +# $< represents the name.cpp file +# +.cpp.o: + ${COMPILE} -o $@ $< +.c.o: + ${COMPILE} -o $@ $< diff --git a/gamma256/prototypes/screenCompress/characterSprite.png b/gamma256/prototypes/screenCompress/characterSprite.png new file mode 100644 index 0000000000000000000000000000000000000000..99888922b49d9ce9286f55dfff0bb9c6f36230d8 GIT binary patch literal 311 zcmV-70m%M|P)0u7*q`E#S{a*ki$9{uxB%w>`u(ZvcHl1tS?-Q5dcy$Ky|F49psA{>E zqGxiLI41HU;rD=@aRPL%k<$fW+iWea8eQHTvi|nY)q|rrwE~FDHELagUGNkQf{3T6 zzYE}b9YQqg{#8R*$mjxdT{v4AkY#o41}%z>IfYmyHpI5Wf&&QWEgtZB^I>}QlwTvh{Ey6eH;~e$XTB9mY1wn z>b{g}BkPp1qQ+9Umg?IksBwL&;}BG7TKg>!8+-+YOz2>&rh*j9bOWzy092AYE{xOy6y!^;Tope1f`kX>beV&&=S%UfdP^tNzdb*U0}!haJcD E0knvF`2YX_ literal 0 HcmV?d00001 diff --git a/gamma256/prototypes/screenCompress/compile b/gamma256/prototypes/screenCompress/compile new file mode 100755 index 0000000..f741d4e --- /dev/null +++ b/gamma256/prototypes/screenCompress/compile @@ -0,0 +1 @@ +g++ -I../../.. -o screenCompress screenCompress.cpp ../../../minorGems/graphics/linux/ScreenGraphicsLinux.cpp ../../../minorGems/io/file/linux/PathLinux.cpp landscape.o -lSDL \ No newline at end of file diff --git a/gamma256/prototypes/screenCompress/landscape.cpp b/gamma256/prototypes/screenCompress/landscape.cpp new file mode 100644 index 0000000..24ea2ea --- /dev/null +++ b/gamma256/prototypes/screenCompress/landscape.cpp @@ -0,0 +1,442 @@ +/* + * Modification History + * + * 2006-September-26 Jason Rohrer + * Switched to cosine interpolation. + * Added optimizations discovered with profiler. Reduced running time by 18%. + */ + + + +#include "landscape.h" + +#include +#include + +#include "minorGems/util/SimpleVector.h" + + + +double landscape( double inX, double inY, double inT, + double inBaseFrequency, double inRoughness, + int inDetail ) { + + if( inDetail < 0 ) { + return 0.0; + } + else { + // frequency of octave + double frequency = inBaseFrequency * pow( 2, inDetail ); + double amplitude = pow( inRoughness, inDetail ); + return amplitude * noise4d( inX * frequency, + inY * frequency, + // index different planes of noise + inDetail, + inT ) + + landscape( inX, inY, inT, inBaseFrequency, inRoughness, + inDetail - 1 ); + } + } + + + +double variableRoughnessLandscape( double inX, double inY, double inT, + double inBaseFrequency, + double inRoughnessChangeFrequency, + double inMinRoughness, + double inMaxRoughness, + int inDetail ) { + + double roughnessFreqX = inX * inRoughnessChangeFrequency; + double roughnessFreqY = inY * inRoughnessChangeFrequency; + + // use low-frequency noise 4d to select landscape roughness + // between 0 and 1 + double roughness = + ( noise4d( 6, roughnessFreqX, roughnessFreqY, inT ) + 1 ) / 2; + + // move roughness into specified range + roughness = + roughness * ( inMaxRoughness - inMinRoughness ) + + inMinRoughness; + + return landscape( inX, inY, inT, inBaseFrequency, roughness, inDetail ); + } + + + +int getRandomPoints( double inStartX, double inEndX, + double inStartY, double inEndY, + double inT, + double inSampleStepSize, + double inDensity, + double **outXCoordinates, + double **outYCoordinates ) { + + SimpleVector *xCoordinates = new SimpleVector(); + SimpleVector *yCoordinates = new SimpleVector(); + + // discretize startX and start Y so that sample grid for differently-placed + // windows always meshes + // use ceil to ensure that starting points are always inside the + // inStart/inEnd bounds + double discretizedStartX = + inSampleStepSize * ceil( inStartX / inSampleStepSize ); + double discretizedStartY = + inSampleStepSize * ceil( inStartY / inSampleStepSize ); + + // put a point wherever we have a zero-crossing + double lastSample = 1; + + for( double x=discretizedStartX; x<=inEndX; x+=inSampleStepSize ) { + for( double y=discretizedStartY; y<=inEndY; y+=inSampleStepSize ) { + double landscapeSample = + variableRoughnessLandscape( + 30 * x + 1000, 30 * y + 1000, inT + 1000, + 0.01, 0.001, 0.25, 0.65, 0 ); + + // shift landscape up to reduce chance of zero-crossing + landscapeSample = (1-inDensity) * 0.5 + 0.5 + landscapeSample ; + + if( landscapeSample < 0 && + lastSample >= 0 || + landscapeSample >= 0 && + landscapeSample < 0 ) { + + // sign change + + // hit + xCoordinates->push_back( x ); + yCoordinates->push_back( y ); + } + + lastSample = landscapeSample; + } + } + + *outXCoordinates = xCoordinates->getElementArray(); + *outYCoordinates = yCoordinates->getElementArray(); + + int numPoints = xCoordinates->size(); + + delete xCoordinates; + delete yCoordinates; + + return numPoints; + } + + + +/** + * Computes a 32-bit random number. + * Use linear congruential method. + * + * @param inSeed the seed to use. + */ +// this is the readable version of the funcion +// it has been turned into a set of macros below +inline unsigned long random32_readable( unsigned long inSeed ) { + // this is the true hot-spot of the entire landscape function + // thus, optimization is warranted. + + // multiplier = 3141592621 + // use hex to avoid warnings + //unsigned long multiplier = 0xBB40E62D; + //unsigned long increment = 1; + + // better: + // unsigned long multiplier = 196314165 + // unsigned long increment = 907633515 + + // this will automatically be mod-ed by 2^32 because of the limit + // of the unsigned long type + // return multiplier * inSeed + increment; + //return 0xBB40E62D * inSeed + 1; + //return 196314165 * inSeed + 907633515; + + //int n = ( inSeed << 13 ) ^ inSeed; + //return n * (n * n * 15731 + 789221) + 1376312589; + + //const unsigned long Num1 = (inSeed * 0xFEA09B9DLU) + 1; + //const unsigned long Num2 = ((inSeed * 0xB89C8895LU) + 1) >> 16; + //return Num1 ^ Num2; + + /* + unsigned int rseed=(inSeed*15064013)^(inSeed*99991+604322121)^(inSeed*45120321)^(inSeed*5034121+13); + + const unsigned long Num1 = (inSeed * 0xFEA09B9DLU) + 1; + + const unsigned long Num2 = ((inSeed * 0xB89C8895LU) + 1) >> 16; + + rseed *= Num1 ^ Num2; + + return rseed; + */ + + const unsigned long Num1 = (inSeed * 0xFEA09B9DLU) + 1; + const unsigned long Num2 = ((inSeed^Num1) * 0x9C129511LU) + 1; + const unsigned long Num3 = (inSeed * 0x2512CFB8LU) + 1; + const unsigned long Num4 = ((inSeed^Num3) * 0xB89C8895LU) + 1; + const unsigned long Num5 = (inSeed * 0x6BF962C1LU) + 1; + const unsigned long Num6 = ((inSeed^Num5) * 0x4BF962C1LU) + 1; + + return Num2 ^ (Num4 >> 11) ^ (Num6 >> 22); + } + + +// faster as a set of macros +#define Num1( inSeed ) \ + ( ( inSeed * 0xFEA09B9DLU ) + 1 ) + +#define Num2( inSeed ) \ + ( ( ( inSeed ^ Num1( inSeed ) ) * 0x9C129511LU ) + 1 ) + +#define Num3( inSeed ) \ + ( ( inSeed * 0x2512CFB8LU ) + 1 ) + +#define Num4( inSeed ) \ + ( ( ( inSeed ^ Num3( inSeed ) ) * 0xB89C8895LU ) + 1 ) + +#define Num5( inSeed ) \ + ( ( inSeed * 0x6BF962C1LU ) + 1 ) + +#define Num6( inSeed ) \ + ( ( ( inSeed ^ Num5( inSeed ) ) * 0x4BF962C1LU ) + 1 ) + +#define random32( inSeed ) \ + ( Num2( inSeed ) ^ (Num4( inSeed ) >> 11) ^ (Num6( inSeed ) >> 22) ) + + + + + + + +#define invMaxIntAsDouble 2.32830643708e-10 +// 1/(x/2) = 2*(1/x) +//double invHalfMaxIntAsDouble = 2 * invMaxIntAsDouble; +// 2.32830643708e-10 +//+ 2.32830643708e-10 +//------------------- +// 4.65661287416e-10 +#define invHalfMaxIntAsDouble 4.65661287416e-10 + + + +#define mixFour( x, y, z, t ) ( x ^ (y * 57) ^ (z * 131) ^ (t * 2383) ) + + + +/** + * Maps 4d integer coordinates into a [-1..1] noise space. + * + * @param x, y, z, t the 4d coordinates. + * + * @return a random value in the range [-1..1] + */ +// keep readable version around for reference +// it has been replaced by a macro below +inline double noise4dInt32_readable( unsigned long x, + unsigned long y, + unsigned long z, + unsigned long t ) { + + + //double maxIntAsDouble = 4294967295.0; + + // modular addition automatic + // multiply x, y, z, and t by distinct primes to + // avoid correllations. + // using xor ( ^ ) here seems to avoid correllations that show + // up when using addition. + + // mix x, y, z, and t + unsigned long randomSeed = + x ^ + y * 57 ^ + z * 131 ^ + t * 2383; + + // a random value between 0 and max unsigned long + unsigned long randomValue = random32( randomSeed ); + + // a random value between 0 and 2 + double zeroTwoValue = randomValue * invHalfMaxIntAsDouble; + + // a random value between -1 and 1 + return zeroTwoValue - 1; + } + + +// noise4dInt32 function call itself was the slowest spot in code +// (found with profiler) +// turn into a set of macros + +// matches original parameter format +#define noise4dInt32( x, y, z, t ) \ + random32( mixFour( x, y, z, t ) ) \ + * invHalfMaxIntAsDouble - 1 + +// problem: now that random32 is a macro, we are passing the unevaluated +// expression, ( x ^ (y * 57) ^ (z * 131) ^ (t * 2383) ), down into it. +// it is being evaluated 6 times within the depths of the random32 macro + +// thus, we need to provide a new format where the caller can precompute +// the mix for us. This is even faster. +#define noise1dInt32( precomputedMix ) \ + random32( precomputedMix ) \ + * invHalfMaxIntAsDouble - 1 + + + + +/* + * The following functions (blendNoiseNd) do 4d linear interpolation + * one dimension at a time. + * + * The end result is 8 calls to blendNoise1d (and 16 calls to noise4dInt32). + * + * This method was inspired by the functional implementations---I am + * decomposing a complicated problem into sub-problems that are easier + * to solve. + */ + + +// faster than f * b + (1-f) * a +// one less multiply +#define linearInterpolation( t, a, b ) ( a + t * ( b - a ) ) + + +/** + * Blends 4d discrete (integer-parameter) noise function along one dimension + * with 3 fixed integer parameters. + */ +inline double blendNoise1d( double x, + unsigned long y, + unsigned long z, + unsigned long t ) { + + double floorX = floor( x ); + unsigned long floorIntX = (unsigned long)floorX; + + if( floorX == x ) { + unsigned long precomputedMix = mixFour( floorIntX, y, z, t ); + + return noise1dInt32( precomputedMix ); + } + else { + unsigned long ceilIntX = floorIntX + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( x - floorX ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + + // need to pre-store intermediate values because noise4dInt32 is a + // macro + // thus, we end up calling the noise1dInt32 function instead + + unsigned long precomputedMix = mixFour( floorIntX, y, z, t ); + double valueAtFloor = noise1dInt32( precomputedMix ); + + precomputedMix = mixFour( ceilIntX, y, z, t ); + double valueAtCeiling = noise1dInt32( precomputedMix ); + + return linearInterpolation( f, valueAtFloor, valueAtCeiling ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 2 dimensions + * with 2 fixed integer parameters. + */ +double blendNoise2d( double x, + double y, + unsigned long z, + unsigned long t ) { + + double floorY = floor( y ); + unsigned long floorIntY = (unsigned long)floorY; + + if( floorY == y ) { + return blendNoise1d( x, floorIntY, z, t ); + } + else { + unsigned long ceilIntY = floorIntY + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( y - floorY ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise1d( x, ceilIntY, z, t ) + + ( 1 - f ) * blendNoise1d( x, floorIntY, z, t ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 3 dimensions + * with 1 fixed integer parameters. + */ +double blendNoise3d( double x, + double y, + double z, + unsigned long t ) { + + double floorZ = floor( z ); + unsigned long floorIntZ = (unsigned long)floorZ; + + if( floorZ == z ) { + return blendNoise2d( x, y, floorIntZ, t ); + } + else { + unsigned long ceilIntZ = floorIntZ + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( z - floorZ ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise2d( x, y, ceilIntZ, t ) + + ( 1 - f ) * blendNoise2d( x, y, floorIntZ, t ); + } + } + + + +/** + * Blends 4d discrete (integer-parameter) noise function along 4 dimensions. + */ +double noise4d( double x, + double y, + double z, + double t ) { + + double floorT = floor( t ); + unsigned long floorIntT = (unsigned long)floorT; + + if( floorT == t ) { + return blendNoise3d( x, y, z, floorIntT ); + } + else { + unsigned long ceilIntT = floorIntT + 1; + + // cosine interpolation + // from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm + double ft = ( t - floorT ) * M_PI; + double f = ( 1 - cos( ft ) ) * .5; + + return ( f ) * blendNoise3d( x, y, z, ceilIntT ) + + ( 1 - f ) * blendNoise3d( x, y, z, floorIntT ); + } + } + + + + + diff --git a/gamma256/prototypes/screenCompress/landscape.h b/gamma256/prototypes/screenCompress/landscape.h new file mode 100644 index 0000000..e7f8599 --- /dev/null +++ b/gamma256/prototypes/screenCompress/landscape.h @@ -0,0 +1,97 @@ + + + +#ifndef LANDSCAPE_INCLUDED +#define LANDSCAPE_INCLUDED + + + +/** + * Gets height samples from an "infinite" fractal landscape. + * The landscape can change over time by varying t. + * + * @param x the x coordinate of the sample. + * @param y the y coordinate of the sample. + * @param t the time of the sample. + * @param baseFrequency the frequency to use for the lowest detail component. + * @param inRoughness the roughness of the landscape (how much high frequencies + * are factored in). Should be in the range [0..1] with 0 making a very + * smooth landscape and 1 making a very rough landscape. + * @param detail the detail level. Larger numbers result in more + * detail. Defaults to 10. + * + * @return the height of the landscape at the sample point/time. + */ +double landscape( double inX, double inY, double inT, + double inBaseFrequency, double inRoughness, + int inDetail = 10 ); + + + +/** + * Samples height of a landscape that varies in roughness over the xy plane. + * + * @params same as for landscape, except: + * @param inRoughnessChangeFrequency the rate at which roughness changes + * over space. Should, in general, be less than inBaseFrequency. + * @param inMinRoughness the minimum roughness value, in the range [0..1]. + * @param inMaxRoughness the maximum roughness value, in the range [0..1]. + * + * @return same as for landscape. + */ +double variableRoughnessLandscape( double inX, double inY, double inT, + double inBaseFrequency, + double inRoughnessChangeFrequency, + double inMinRoughness, + double inMaxRoughness, + int inDetail ); + + + +/** + * Computes linearly-blended random values in the range [-1..1] from a + * 4d parameterized noise space. + * + * @param x, y, z, t the 4d floating-point coordinates. + * + * @return a blended random value in the range [-1..1]. + */ +double noise4d( double x, + double y, + double z, + double t ); + + + +/** + * Gets a set of randomly-chosen (though stable) points in a given + * region of the landscape. + * + * @param inStartX, inEndX the x region. + * @param inStartY, inEndY the y region. + * @param inT the time. + * @param inSampleStepSize the step size in the sample grid. + * Higher values are faster but result in sparser distributions of points. + * @param inDensity the density of points, in the range [0,1]. + * @param outXCoordinates pointer to where array of x coordinates should + * be returned. Array must be destroyed by caller. + * @param outYCoordinates pointer to where array of x coordinates should + * be returned. Array must be destroyed by caller. + * + * @return the number of points (the length of outXCoordinates). + */ +int getRandomPoints( double inStartX, double inEndX, + double inStartY, double inEndY, + double inT, + double inSampleStepSize, + double inDensity, + double **outXCoordinates, + double **outYCoordinates ); + + + +#endif + + + + diff --git a/gamma256/prototypes/screenCompress/mapTile.tga b/gamma256/prototypes/screenCompress/mapTile.tga new file mode 100644 index 0000000000000000000000000000000000000000..ce058e5fda0a6aec8fa67d384988ec53560021af GIT binary patch literal 236 zcmZQzU}As)4h9Ye35Ngw|HA|jG*GaEoqge{_EQtvfg}V0*+3N#akvPC0TBl(Ktpi# hl$!%_63}*Jgyi^;pitK^&tOk~Kivp7Paju31^|FYT>1b2 literal 0 HcmV?d00001 diff --git a/gamma256/prototypes/screenCompress/screenCompress.cpp b/gamma256/prototypes/screenCompress/screenCompress.cpp new file mode 100644 index 0000000..d1e908f --- /dev/null +++ b/gamma256/prototypes/screenCompress/screenCompress.cpp @@ -0,0 +1,798 @@ +/* + * Modification History + * + * 2007-September-25 Jason Rohrer + * Created. + */ + +#include "landscape.h" + +#include "minorGems/graphics/Image.h" +#include "minorGems/graphics/converters/TGAImageConverter.h" + +#include "minorGems/io/file/File.h" + +#include "minorGems/io/file/FileInputStream.h" + +// #include "minorGems/system/Thread.h" + + +#include +#include +#include +#include + + +// for memcpy +#include + + + +// size of game image +int width = 100; +int height = 16; + +// size of screen +int screenWidth = 640; +int screenHeight = 480; + +// blow-up factor when projecting game onto screen +int blowUpFactor = 6; +//int blowUpFactor = 1; + + +char fullScreen = false; + +double timeDelta = -0.1; +//double timeDelta = -0.0; + + +int mainFunction(); + + + +// must do this to prevent WinMain linker errors on win32 +int main() { + mainFunction(); + } + + + +#include + + +// the joystick to read from, or NULL if there's no available joystick +SDL_Joystick *joystick; + + + +void catch_int(int sig_num) { + printf( "Quiting...\n" ); + SDL_Quit(); + exit( 0 ); + signal( SIGINT, catch_int ); + } + + + + +void blowupOntoScreen( Uint32 *inImage, int inWidth, int inHeight, + int inBlowFactor, SDL_Surface *inScreen ) { + + int newWidth = inBlowFactor * inWidth; + int newHeight = inBlowFactor * inHeight; + + int yOffset = ( inScreen->h - newHeight ) / 2; + int xOffset = ( inScreen->w - newWidth ) / 2; + + int endY = yOffset + newHeight; + int endX = xOffset + newWidth; + + Uint32 *pixels = (Uint32 *)( inScreen->pixels ); + + for( int y=yOffset; yw + x ] = + inImage[ imageY * inWidth + imageX ]; + } + + } + + } + +// values in range 0..1 +Image *tileImage; +int imageW, imageH; + +// dimensions of one tile. TileImage contains 13 tiles, stacked vertically, +// with blank lines between tiles +int tileW = 8; +int tileH = 8; + + +int numTileSets; + +// how wide the swath of a world is that uses a given tile set +int tileSetWorldSpan = 200; +// overlap during tile set transition +int tileSetWorldOverlap = 50; + + + +// optimization (found with profiler) +// values in range 0..255 +double *tileRed; +double *tileGreen; +double *tileBlue; + + +Image *spriteImage; +int spriteW = 8; +int spriteH = 8; + +double *spriteRed; +double *spriteGreen; +double *spriteBlue; + +int currentSpriteIndex = 2; + + + +double playerX, playerY; +int playerRadius = spriteW / 2; + +int seed = time( NULL ); + + + +inline char isBlocked( int inX, int inY ) { + // reduce to grid coordinates + int gridX = inX / tileW; + int gridY = inY / tileH; + + // wall along far left and top + if( gridX <=0 || gridY <= 0 ) { + return true; + } + + // make a grid of empty spaces from which blocks can be + // removed below to make a maze + if( gridX % 2 !=0 && gridY % 2 != 0 ) { + return false; + } + + + // blocks get denser as y increases + double threshold = 1 - gridY / 20.0; + + double randValue = noise4d( gridX, gridY, seed, 0 ); + return randValue > threshold; + } + + +char isSpriteTransparent( int inSpriteIndex ) { + // take transparent color from corner + return + spriteRed[ inSpriteIndex ] == spriteRed[ 0 ] + && + spriteGreen[ inSpriteIndex ] == spriteGreen[ 0 ] + && + spriteBlue[ inSpriteIndex ] == spriteBlue[ 0 ]; + } + + + +Uint32 sampleFromWorld( int inX, int inY, double inWeight = 1.0 ) { + + // consider sampling from sprite + + // player position centered on sprint left-to-right + int spriteX = (int)( inX - playerX + spriteW / 2 ); + // player position at sprite's feet + int spriteY = (int)( inY - playerY + spriteH - 1); + + + if( spriteX >= 0 && spriteX < spriteW + && + spriteY >= 0 && spriteY < spriteH ) { + + + int spriteIndex = spriteY * spriteW + spriteX; + + // skip to appropriate sprite fram + spriteIndex += currentSpriteIndex * spriteW * ( spriteH + 1 ); + + if( !isSpriteTransparent( spriteIndex ) ) { + + unsigned char r = + (unsigned char)( + inWeight * spriteRed[ spriteIndex ] ); + + unsigned char g = + (unsigned char)( + inWeight * spriteGreen[ spriteIndex ] ); + + unsigned char b = + (unsigned char)( + inWeight * spriteBlue[ spriteIndex ] ); + + return r << 16 | g << 8 | b; + } + } + + + int tileIndex; + + if( !isBlocked( inX, inY ) ) { + // empty tile + tileIndex = 0; + } + else { + int neighborsBlockedBinary = 0; + + if( isBlocked( inX, inY - tileH ) ) { + // top + neighborsBlockedBinary = neighborsBlockedBinary | 1; + } + if( isBlocked( inX + tileW, inY ) ) { + // right + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1; + } + if( isBlocked( inX, inY + tileH ) ) { + // bottom + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2; + } + if( isBlocked( inX - tileW, inY ) ) { + // left + neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3; + } + + // skip empty tile, treat as tile index + neighborsBlockedBinary += 1; + + tileIndex = neighborsBlockedBinary; + } + + + // pick a tile set + int netWorldSpan = tileSetWorldSpan + tileSetWorldOverlap; + + int tileSet = inX / netWorldSpan; + int overhang = inX % netWorldSpan; + if( inX < 0 ) { + // fix to a constant tile set below 0 + overhang = 0; + tileSet = 0; + } + + // is there blending with next tile set? + int blendTileSet = tileSet + 1; + double blendWeight = 0; + + if( overhang > tileSetWorldSpan ) { + blendWeight = ( overhang - tileSetWorldSpan ) / + (double) tileSetWorldOverlap; + } + // else 100% blend of our first tile set + + + tileSet = tileSet % numTileSets; + blendTileSet = blendTileSet % numTileSets; + + // make sure not negative + if( tileSet < 0 ) { + tileSet += numTileSets; + } + if( blendTileSet < 0 ) { + blendTileSet += numTileSets; + } + + + + + // sample from tile image + int imageY = inY % tileH; + int imageX = inX % tileW; + + + if( imageX < 0 ) { + imageX += tileW; + } + if( imageY < 0 ) { + imageY += tileH; + } + + // offset to top left corner of tile + int tileImageOffset = tileIndex * ( tileH + 1 ) * imageW + + tileSet * (tileW + 1); + int blendTileImageOffset = tileIndex * ( tileH + 1 ) * imageW + + blendTileSet * (tileW + 1); + + + int imageIndex = tileImageOffset + imageY * imageW + imageX; + int blendImageIndex = blendTileImageOffset + imageY * imageW + imageX; + + unsigned char r = + (unsigned char)( + inWeight * ( + (1-blendWeight) * tileRed[ imageIndex ] + + blendWeight * tileRed[ blendImageIndex ] ) ); + + unsigned char g = + (unsigned char)( + inWeight * ( + (1-blendWeight) * tileGreen[ imageIndex ] + + blendWeight * tileGreen[ blendImageIndex ] ) ); + + unsigned char b = + (unsigned char)( + inWeight * ( + (1-blendWeight) * tileBlue[ imageIndex ] + + blendWeight * tileBlue[ blendImageIndex ] ) ); + + + return r << 16 | g << 8 | b; + } + + + +char getKeyDown( int inKeyCode ) { + SDL_PumpEvents(); + Uint8 *keys = SDL_GetKeyState( NULL ); + return keys[ inKeyCode ] == SDL_PRESSED; + } + + + +char getHatDown( Uint8 inHatPosition ) { + if( joystick == NULL ) { + return false; + } + + SDL_JoystickUpdate(); + + Uint8 hatPosition = SDL_JoystickGetHat( joystick, 1 ); + + if( hatPosition & inHatPosition ) { + return true; + } + else { + return false; + } + } + + + +Image *readTGA( char *inFileName ) { + File tileFile( NULL, inFileName ); + FileInputStream tileStream( &tileFile ); + + TGAImageConverter converter; + + return converter.deformatImage( &tileStream ); + } + + + + +int mainFunction() { + + // let catch_int handle interrupt (^c) + signal( SIGINT, catch_int ); + + + // read in map tile + tileImage = readTGA( "tileSet.tga" ); + + imageW = tileImage->getWidth(); + imageH = tileImage->getHeight(); + + numTileSets = (imageW + 1) / (tileW + 1); + + + int imagePixelCount = imageW * imageH; + + tileRed = new double[ imagePixelCount ]; + tileGreen = new double[ imagePixelCount ]; + tileBlue = new double[ imagePixelCount ]; + + for( int i=0; igetChannel(0)[ i ]; + tileGreen[i] = 255 * tileImage->getChannel(1)[ i ]; + tileBlue[i] = 255 * tileImage->getChannel(2)[ i ]; + } + + + // read in map tile + spriteImage = readTGA( "characterSprite.tga" ); + + imagePixelCount = spriteImage->getWidth() * spriteImage->getHeight(); + + spriteRed = new double[ imagePixelCount ]; + spriteGreen = new double[ imagePixelCount ]; + spriteBlue = new double[ imagePixelCount ]; + + for( int i=0; igetChannel(0)[ i ]; + spriteGreen[i] = 255 * spriteImage->getChannel(1)[ i ]; + spriteBlue[i] = 255 * spriteImage->getChannel(2)[ i ]; + } + + + + if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | + SDL_INIT_NOPARACHUTE ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_ShowCursor( SDL_DISABLE ); + + Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF; + if( fullScreen ) { + flags = flags | SDL_FULLSCREEN; + } + + + SDL_Surface *screen = SDL_SetVideoMode( screenWidth, screenHeight, + 32, + flags ); + + if ( screen == NULL ) { + printf( "Couldn't set %dx%dx32 video mode: %s\n", screenWidth, + screenHeight, + SDL_GetError() ); + return-1; + } + + // try to open joystick + int numJoysticks = SDL_NumJoysticks(); + printf( "Found %d joysticks\n", numJoysticks ); + + if( numJoysticks > 0 ) { + // open first one by default + joystick = SDL_JoystickOpen( 1 ); + + if( joystick == NULL ) { + printf( "Couldn't open joystick 1: %s\n", SDL_GetError() ); + } + int numHats = SDL_JoystickNumHats( joystick ); + + if( numHats <= 0 ) { + printf( "No d-pad found on joystick\n" ); + SDL_JoystickClose( joystick ); + joystick = NULL; + } + } + else { + joystick = NULL; + } + + + + Uint32 *pixels = (Uint32 *)( screen->pixels ); + + Uint32 *gameImage = new Uint32[ width * height ]; + + + // first, fill the whole thing with black + SDL_FillRect(screen, NULL, 0x00000000); + + // small area in center that we actually draw in, black around it + int yOffset = ( screenHeight - height * blowUpFactor ) / 2; + int xOffset = ( screenWidth - width * blowUpFactor ) / 2; + + + double dX = 0; + double dY = 0; + + int frameCount = 0; + unsigned long startTime = time( NULL ); + + char done = false; + + double maxWorldX = 0; + double minWorldX = 0; + + + // start player position + playerX = tileW; + //playerX = 0; + dX = tileW; + playerY = 0 + height/2; + dY = 0; + + while( !done ) { + + char someBlocksDrawn = false; + + for( int y=0; y maxDistance ) { + cappedDistanceFromPlayer = maxDistance; + } + if( cappedDistanceFromPlayer < -maxDistance ) { + cappedDistanceFromPlayer = -maxDistance; + } + + // the world position we will sample from + double worldX = x; + + // zone around player where no x compression happens + int noCompressZone = 10; + + + if( trueDistanceFromPlayer > noCompressZone ) { + + worldX = + x + + //(width/8) * + // use true distance as a factor so that compression + // continues at constant rate after we pass capped + // distance + // otherwise, compression stops after capped distance + // Still... constant rate looks weird, so avoid + // it by not letting it pass capped distance + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + // simpler formula + // actually, this does not approach 0 as + // cappedDistanceFromPlayer approaches 0, so use tan + // instead + // ( trueDistanceFromPlayer / 2 ) * 100 + // / pow( ( width - cappedDistanceFromPlayer ), 1.6 ); + } + else if( trueDistanceFromPlayer < - noCompressZone ) { + worldX = + x + + //(width/8) * + trueDistanceFromPlayer / 2 * + ( pow( tan( ( ( - cappedDistanceFromPlayer - + noCompressZone ) / + (double)( width - noCompressZone ) ) * + M_PI / 2 ), 2) ); + /* + trueDistanceFromPlayer / 2 * + ( tan( ( - cappedDistanceFromPlayer / + (double)( width - 0.5 ) ) * M_PI / 2 ) ); + */ + //( trueDistanceFromPlayer / 2 ) * 50 + /// ( width + cappedDistanceFromPlayer ); + } + else { + // inside no-compresison zone + worldX = x; + } + + + //int worldX = x; + + worldX += dX; + + if( worldX > maxWorldX ) { + maxWorldX = worldX; + } + if( worldX < minWorldX ) { + minWorldX = worldX; + } + + + int worldY = (int)floor( y + dY ); + + + // linear interpolation of two samples for worldX + int intWorldX = (int)floor( worldX ); + + double bWeight = worldX - intWorldX; + double aWeight = 1 - bWeight; + + + Uint32 sampleA = + sampleFromWorld( intWorldX, worldY, aWeight ); + Uint32 sampleB = + sampleFromWorld( intWorldX + 1, worldY, bWeight ); + + + + Uint32 combined = sampleB + sampleA; + + + gameImage[ y * width + x ] = combined; + + } + } + + + // check if we need to lock the screen + if( SDL_MUSTLOCK( screen ) ) { + if( SDL_LockSurface( screen ) < 0 ) { + printf( "Couldn't lock screen: %s\n", SDL_GetError() ); + } + } + + + blowupOntoScreen( gameImage, width, height, blowUpFactor, screen ); + + + // unlock the screen if necessary + if ( SDL_MUSTLOCK(screen) ) { + SDL_UnlockSurface(screen); + } + + if( ( screen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) { + // need to flip buffer + SDL_Flip( screen ); + printf( "double\n" ); + + } + else { + // just update center + //SDL_UpdateRect( screen, yOffset, xOffset, width, height ); + SDL_Flip( screen ); + } + + int moveDelta = 1; + if( getKeyDown( SDLK_LEFT ) || getHatDown( SDL_HAT_LEFT ) ) { + if( currentSpriteIndex == 6 ) { + currentSpriteIndex = 7; + } + else { + currentSpriteIndex = 6; + } + + if( !isBlocked( (int)( playerX - moveDelta ), (int)playerY ) ) { + + playerX -= moveDelta; + + if( playerX < 0 ) { + // undo + playerX += moveDelta; + } + else { + // update screen position + dX -= moveDelta; + } + } + } + else if( getKeyDown( SDLK_RIGHT ) || getHatDown( SDL_HAT_RIGHT )) { + if( currentSpriteIndex == 2 ) { + currentSpriteIndex = 3; + } + else { + currentSpriteIndex = 2; + } + + if( !isBlocked( (int)( playerX + moveDelta ), (int)playerY ) ) { + + dX += moveDelta; + + playerX += moveDelta; + } + } + else if( getKeyDown( SDLK_UP ) || getHatDown( SDL_HAT_UP ) ) { + if( currentSpriteIndex == 0 ) { + currentSpriteIndex = 1; + } + else { + currentSpriteIndex = 0; + } + + if( !isBlocked( (int)playerX, (int)( playerY - moveDelta ) ) ) { + + playerY -= moveDelta; + + if( playerY < 0 ) { + // undo + playerY += moveDelta; + } + else { + // update screen position + dY -= moveDelta; + } + } + } + else if( getKeyDown( SDLK_DOWN ) || getHatDown( SDL_HAT_DOWN )) { + if( currentSpriteIndex == 4 ) { + currentSpriteIndex = 5; + } + else { + currentSpriteIndex = 4; + } + + if( !isBlocked( (int)playerX, (int)( playerY + moveDelta ) ) ) { + + dY += moveDelta; + + playerY += moveDelta; + } + } + + + // check for events to quit + SDL_Event event; + while( SDL_PollEvent(&event) ) { + switch( event.type ) { + case SDL_KEYDOWN: + switch( event.key.keysym.sym ) { + case SDLK_q: + done = true; + break; + } + break; + case SDL_QUIT: + done = true; + break; + default: + break; + } + } + + //t +=0.25; + frameCount ++; + + // player position on screen inches forward + dX += timeDelta; + //dX -= 1; + // stop after player has gone off right end of screen + if( playerX - dX > width ) { + dX = playerX - width; + } + + + } + + unsigned long netTime = time( NULL ) - startTime; + double frameRate = frameCount / (double)netTime; + + printf( "Max world x = %f\n", maxWorldX ); + printf( "Min world x = %f\n", minWorldX ); + + printf( "Frame rate = %f fps (%d frames)\n", frameRate, frameCount ); + fflush( stdout ); + + + + delete tileImage; + delete [] tileRed; + delete [] tileGreen; + delete [] tileBlue; + + + delete spriteImage; + delete [] spriteRed; + delete [] spriteGreen; + delete [] spriteBlue; + + + if( joystick != NULL ) { + SDL_JoystickClose( joystick ); + } + + SDL_Quit(); + } diff --git a/gamma256/prototypes/screenCompress/sdlTest.cpp b/gamma256/prototypes/screenCompress/sdlTest.cpp new file mode 100644 index 0000000..7f72868 --- /dev/null +++ b/gamma256/prototypes/screenCompress/sdlTest.cpp @@ -0,0 +1,110 @@ +// FIXME: +// why does this end up with a blank, white screen after 5 seconds +// while the demo code (testsprite) works fine? +// Does it only happen on win32 + + + +#include +#include +#include + +// for memcpy +#include + +#include +#include + + +void catch_int(int sig_num) { + printf( "Quiting...\n" ); + //currentStep = numSteps; + exit( 0 ); + signal( SIGINT, catch_int ); + } + + +// if SDL.h is included before our main function, the linker complains +// about missing WinMain +// This fixes it +int mainFunction(); + +int main() { + mainFunction(); + } + + +#include + + +int mainFunction() { + + + // let catch_int handle interrupt (^c) + signal( SIGINT, catch_int ); + + int w = 640; + int h = 480; + + if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return -1; + } + + SDL_Surface *screen = SDL_SetVideoMode( w, h, + 32, + SDL_SWSURFACE ); + + if ( screen == NULL ) { + printf( "Couldn't set %dx%dx32 video mode: %s\n", w, h, + SDL_GetError() ); + return-1; + } + + Uint32 *buffer = (Uint32 *)( screen->pixels ); + + char white = false; + + char done = false; + + while( !done ) { + if( white ) { + SDL_FillRect(screen, NULL, 0x00FFFFFF); + } + else { + SDL_FillRect(screen, NULL, 0x00000000); + } + /* + for( int y=0; y0ssI2jj)xy00099Nkl z!I9fA3|wgRKNkTtaNkq_my#K)gL|a|c_yU@736yjkVV)PFb~;h$>bdY`Rq7)Xk=Ru zNGx|3qG=paJVX-3Soq<1Y~VVMN4&f=hr{XP;eh$|X8{1(&E|B+W^=iFx-JF)Xs_-l z+`S9{&=}`1|DraZETA@@QUCx-DZzQPs?_&<_3bdk3Df*}1Q0VvjnD~vOcy+G2mvut zr&lp4NDBZ8h+}N$TI=jAmY9#5#FVgEjBTE+o<|+?p>c$})+;b2W{!Ql)WCpG1OKs% zQG2Zc00H4NHnGJdNdOX(6`z0*HS(^H8UsR|3jjKFNmASoEKEucOtn`T4K+0 zq*Jx|X&(V39-#^mid&Ojr?7)6h6vRZb|!qN+d~9BLxj4$)laG00-(6{_^G0rA@h23 zi?C*0e;-fp=cjWq5N{2{$e;DKRx9lq@4z02=?#8bd|t&*3o2(?2Xmlg1%K4Lc6?*A ziE!4(>mzw;_S1*+v=&z4>6CD19Gj1)`h+|s<^oA4KSgQJfdIl#5S>Y{i3)Z!)9Z6U z+OeSqA3dlb{Qz(}e;IRzB{AcbfBlSj?TRa+rdKh3LL^V8O)1fTTE@4=BKm6qu1_z z3UjS9OvTcVs+DTlJMA;R%lsQZ6(c@*qg=;FR@8buPd(fCja{biz|*hvfThe+r(PT6 zw=KWdTm4kBKU*r=yB?qafv5NLQ#Vhq+DozURF(ni#Z;aL)4!^-_OgeR#Ag5i002ov JPDHLkV1fyYi5>s| literal 0 HcmV?d00001 diff --git a/gamma256/prototypes/screenCompress/tileSet.tga b/gamma256/prototypes/screenCompress/tileSet.tga new file mode 100644 index 0000000000000000000000000000000000000000..9e17d558e77980c9e3d6e5ec4aff427451dfaa20 GIT binary patch literal 7770 zcmb`L&2ii?423!AAh#6YW>QKHZXzY@DHXT`RpC^iV&q3W0t=0_1R#sU(ZCWuJp3%R zvu^E2)Ah6cYrk51xV=8@ZQJ}4Z(m0&z8W(X_|T0;2Ju-scBVpPFM$GM1fZtGhTMxF zT6JBYi#0(T2x&EUQE_wpZMCa0L)c!xbj~1NUj2d3R6Ig00Skymm>_R27Lav4*>8AI zU%RGj`A|kA&2jItZo9dZ5Z5aN2~;$4ms2}HoTpJSVp-)?fb6wWDix$YNuA3+w`^iA zw;0hzSrY;T{@gCN0HG(dQn@Trs>OuVn5*FFakXW^yiQN$DsPhNG>;Oz^IBy;eb)gX zT3H{L-_Jk%bmm`4ev~>F#7U`GRuUwIc)6sC%PN~#8bQw0b$yik+T&V$6>1|}KXGp# zk0d`2GsJj}-;vCBC2J99zRa^KPH2K$XfmQz*Qv_Kx&ZN{=f$N~W~+Ng+ruN|ul7{|&DFnnU2+ z{r~)>F;lULz$mHcP`XgQ7NtVu*kRn6H~HEq`^TQ!<%Uo=P4>haJq`Q2gqX7$DHS+# zi)f5!@IlE7HmJoANQfP)sHe5!UIfvq>p2%+j(qRO;%C9JUy6#G<4v&@u<3A$x3_wfrTM$q9${}uv*Yf9zO<75Bc z`W5?j6@Mxjzqft-t~@=J{Wn*2c@@OBg*f=3dOY~!RHDiIxu9u+Out@9IW~pJbnH!@ zLM%7YiV>5bk*VbxVu=YpNeqvw>*6T2WIs?ypB`lR;uF$my)L-gP%_is7WbKn=7RX$ zAOA#IR4eM(NDP!!ltfsIqbB8FXmWLq@Hdt9vD%TuO-bzZRN7oNAe;V67VN89c#z?Y zjh7e=v@S&Ytf5d*;Y5Ip&)Aw$dCpNzO(wB{=qbA$`OYYjjIDT-EWusD&PUm6hkpq% zXM*{;5ER8iVoJq`dX93UkENX#>oAOYldp}keSGx2EPbnuP@kVTl_&e?Oi)JIPN@-` j67EU_Dn}|rj+D>e+or;DywmT&ab*I>FBacD934st>$n#LF&&pkd-P5=dU5!a$cY_WUTQn~RLd$Q z#9wEAR>H#cR=nh?W#eX{voASV#9kl#@4Re=a#okvn!C|h| BPTc?i literal 0 HcmV?d00001 diff --git a/gamma256/prototypes/screenCompress/tileTemplate.tga b/gamma256/prototypes/screenCompress/tileTemplate.tga new file mode 100644 index 0000000000000000000000000000000000000000..9e7f987b8f5223b3e5d3858e6171d5c55e507824 GIT binary patch literal 3692 zcmdUyO9}!p5JU@C9>D{+)mzL!KotBj=W^vq=6Ykp=qgo#tlDa_sx(br-efVr2EW?k zjUDjvUW+IfuDvLMwNS2@;gY*^QC*m4#YOv-FDfxNbmvm{-^CSvsRiXRF?Z*pzS{50 zMGZY?!x!yO{IuwKLJd{c#kEeT=l@~u&P9DS-WTmxUcz8()gBXb<;#UjowJK;9a4Ky i9usqSF6t}723PDFukY{V{rVW5> ${MINOR_GEMS_DEPENDENCY_FILE}.temp + cat ${MINOR_GEMS_DEPENDENCY_FILE}.temp | ${MINOR_GEMS_SED_FIX_COMMAND_A} | ${MINOR_GEMS_SED_FIX_COMMAND_B} >> ${MINOR_GEMS_DEPENDENCY_FILE} + rm -f ${MINOR_GEMS_DEPENDENCY_FILE}.temp + +include ${MINOR_GEMS_DEPENDENCY_FILE} + + + + + + diff --git a/minorGems/common.h b/minorGems/common.h new file mode 100644 index 0000000..82cece0 --- /dev/null +++ b/minorGems/common.h @@ -0,0 +1,19 @@ +/* + * Modification History + * + * 2002-October-18 Jason Rohrer + * Created. + */ + + + +#ifndef MINOR_GEMS_COMMON_INCLUDED +#define MINOR_GEMS_COMMON_INCLUDED + + + +#include "minorGems/util/development/memory/debugMemory.h" + + + +#endif diff --git a/minorGems/crypto/hashes/sha1.cpp b/minorGems/crypto/hashes/sha1.cpp new file mode 100644 index 0000000..264df0d --- /dev/null +++ b/minorGems/crypto/hashes/sha1.cpp @@ -0,0 +1,265 @@ +/* + * Modification History + * + * 2002-May-25 Jason Rohrer + * Changed to use minorGems endian.h + * Added a function for hashing an entire string to a hex digest. + * Added a function for getting a raw digest. + * Fixed a deletion bug. + * + * 2003-August-24 Jason Rohrer + * Switched to use minorGems hex encoding. + * + * 2003-September-15 Jason Rohrer + * Added support for hashing raw (non-string) data. + * + * 2003-September-21 Jason Rohrer + * Fixed bug that was causing overwrite of input data. + * + * 2004-January-13 Jason Rohrer + * Fixed system includes. + */ + + + +/* + * sha1.c + * + * Originally witten by Steve Reid + * + * Modified by Aaron D. Gifford + * + * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN + * + * The original unmodified version is available at: + * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sha1.h" +#include +#include + +// for hex encoding +#include "minorGems/formats/encodingUtils.h" + + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&(sha1_quadbyte)0xFF00FF00) \ + |(rol(block->l[i],8)&(sha1_quadbyte)0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +typedef union _BYTE64QUAD16 { + sha1_byte c[64]; + sha1_quadbyte l[16]; +} BYTE64QUAD16; + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +void SHA1_Transform(sha1_quadbyte state[5], sha1_byte buffer[64]) { + sha1_quadbyte a, b, c, d, e; + BYTE64QUAD16 *block; + + block = (BYTE64QUAD16*)buffer; + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1_Init - Initialize new context */ +void SHA1_Init(SHA_CTX* context) { + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +void SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len) { + unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ +void SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX *context) { + sha1_quadbyte i, j; + sha1_byte finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (sha1_byte)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1_Update(context, (sha1_byte *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1_Update(context, (sha1_byte *)"\0", 1); + } + /* Should cause a SHA1_Transform() */ + SHA1_Update(context, finalcount, 8); + for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { + digest[i] = (sha1_byte) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, SHA1_BLOCK_LENGTH); + memset(context->state, 0, SHA1_DIGEST_LENGTH); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +} + + + +unsigned char *computeRawSHA1Digest( unsigned char *inData, + int inDataLength ) { + + SHA_CTX context; + + SHA1_Init( &context ); + + // copy into buffer, since this SHA1 implementation seems to overwrite + // parts of the data buffer. + unsigned char *buffer = new unsigned char[ inDataLength ]; + memcpy( (void *)buffer, (void *)inData, inDataLength ); + + SHA1_Update( &context, buffer, inDataLength ); + + delete [] buffer; + + unsigned char *digest = new unsigned char[ SHA1_DIGEST_LENGTH ]; + + SHA1_Final( digest, &context ); + + return digest; + } + + + +unsigned char *computeRawSHA1Digest( char *inString ) { + + SHA_CTX context; + + SHA1_Init( &context ); + + // copy into buffer, since this SHA1 implementation seems to overwrite + // parts of the data buffer. + int dataLength = strlen( inString ); + unsigned char *buffer = new unsigned char[ dataLength ]; + memcpy( (void *)buffer, (void *)inString, dataLength ); + + SHA1_Update( &context, buffer, strlen( inString ) ); + + delete [] buffer; + + unsigned char *digest = new unsigned char[ SHA1_DIGEST_LENGTH ]; + + SHA1_Final( digest, &context ); + + return digest; + } + + + +char *computeSHA1Digest( char *inString ) { + + unsigned char *digest = computeRawSHA1Digest( inString ); + + char *digestHexString = hexEncode( digest, SHA1_DIGEST_LENGTH ); + + delete [] digest; + + return digestHexString; + } + + + +char *computeSHA1Digest( unsigned char *inData, int inDataLength ) { + + unsigned char *digest = computeRawSHA1Digest( inData, inDataLength ); + + char *digestHexString = hexEncode( digest, SHA1_DIGEST_LENGTH ); + + delete [] digest; + + return digestHexString; + } + + + diff --git a/minorGems/crypto/hashes/sha1.h b/minorGems/crypto/hashes/sha1.h new file mode 100644 index 0000000..a51fa06 --- /dev/null +++ b/minorGems/crypto/hashes/sha1.h @@ -0,0 +1,141 @@ +/* + * Modification History + * + * 2002-May-25 Jason Rohrer + * Changed to use minorGems endian.h + * Added a function for hashing an entire string to a hex digest. + * Added a function for getting a raw digest. + * + * 2003-September-15 Jason Rohrer + * Added support for hashing raw (non-string) data. + * Removed legacy C code. + */ + + + +/* + * sha.h + * + * Originally taken from the public domain SHA1 implementation + * written by by Steve Reid + * + * Modified by Aaron D. Gifford + * + * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN + * + * The original unmodified version is available at: + * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __SHA1_H__ +#define __SHA1_H__ + + +#include "minorGems/system/endian.h" + + +/* Make sure you define these types for your architecture: */ +typedef unsigned int sha1_quadbyte; /* 4 byte type */ +typedef unsigned char sha1_byte; /* single byte type */ + +/* + * Be sure to get the above definitions right. For instance, on my + * x86 based FreeBSD box, I define LITTLE_ENDIAN and use the type + * "unsigned long" for the quadbyte. On FreeBSD on the Alpha, however, + * while I still use LITTLE_ENDIAN, I must define the quadbyte type + * as "unsigned int" instead. + */ + +#define SHA1_BLOCK_LENGTH 64 +#define SHA1_DIGEST_LENGTH 20 + +/* The SHA1 structure: */ +typedef struct _SHA_CTX { + sha1_quadbyte state[5]; + sha1_quadbyte count[2]; + sha1_byte buffer[SHA1_BLOCK_LENGTH]; +} SHA_CTX; + + + + +// BIG NOTE: +// These overwrite the data! +// copy your data to a temporary buffer before passing it through +// (the "nicer" functions below these 3 do not overwrite data) +void SHA1_Init(SHA_CTX *context); +void SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len); +void SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX* context); + + + +/** + * Computes a unencoded 20-byte digest from data. + * + * @param inData the data to hash. + * Must be destroyed by caller. + * @param inDataLength the length of the data to hash. + * Must be destroyed by caller. + * + * @return the digest as a byte array of length 20. + * Must be destroyed by caller. + */ +unsigned char *computeRawSHA1Digest( unsigned char *inData, int inDataLength ); + + + +/** + * Computes a unencoded 20-byte digest from an arbitrary string message. + * + * @param inString the message as a \0-terminated string. + * Must be destroyed by caller. + * + * @return the digest as a byte array of length 20. + * Must be destroyed by caller. + */ +unsigned char *computeRawSHA1Digest( char *inString ); + + + +/** + * Computes a hex-encoded string digest from data. + * + * @param inData the data to hash. + * Must be destroyed by caller. + * @param inDataLength the length of the data to hash. + * Must be destroyed by caller. + * + * @return the digest as a \0-terminated string. + * Must be destroyed by caller. + */ +char *computeSHA1Digest( unsigned char *inData, int inDataLength ); + + + +/** + * Computes a hex-encoded string digest from an arbitrary string message. + * + * @param inString the message as a \0-terminated string. + * Must be destroyed by caller. + * + * @return the digest as a \0-terminated string. + * Must be destroyed by caller. + */ +char *computeSHA1Digest( char *inString ); + + + +#endif + diff --git a/minorGems/crypto/hashes/sha1Test.cpp b/minorGems/crypto/hashes/sha1Test.cpp new file mode 100644 index 0000000..ec586c3 --- /dev/null +++ b/minorGems/crypto/hashes/sha1Test.cpp @@ -0,0 +1,109 @@ +/* + * Modification History + * + * 2002-May-25 Jason Rohrer + * Created. + * + * 2003-September-21 Jason Rohrer + * Added test of long string. + */ + + + +#include "sha1.h" + + + +#include +#include + + + +/** + * All parameters must be destroyed by caller. + */ +void checkHash( char *inString, char *inTestName, char *inCorrectHash ); + + + +int main() { + + + /* + * Test vectors: + * + * "abc" + * A999 3E36 4706 816A BA3E 2571 7850 C26C 9CD0 D89D + * + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 8498 3E44 1C3B D26E BAAE 4AA1 F951 29E5 E546 70F1 + * + * A million repetitions of "a" + * 34AA 973C D4C4 DAA4 F61E EB2B DBAD 2731 6534 016F + */ + + char *abc = "abc"; + char *correctABCHash = "A9993E364706816ABA3E25717850C26C9CD0D89D"; + char *mixedAlpha = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + char *correctMixedAlphaHash = "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"; + + + char *correctMillionHash = "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"; + + int oneMillion = 1000000; + char *millionAs = new char[ oneMillion + 1 ]; + for( int i=0; i +#include + + +/** + * Prints usage message and exits. + * + * @param inAppName the name of the app. + */ +void usage( char *inAppName ); + + + +int main( int inNumArgs, char **inArgs ) { + + + if( inNumArgs != 2 ) { + usage( inArgs[0] ); + } + + + FILE *file = fopen( inArgs[1], "rb" ); + + if( file == NULL ) { + printf( "Failed to open file %s for reading\n\n", inArgs[1] ); + + usage( inArgs[0] ); + } + + + SHA_CTX shaContext; + + SHA1_Init( &shaContext ); + + + int bufferSize = 5000; + unsigned char *buffer = new unsigned char[ bufferSize ]; + + int numRead = bufferSize; + + char error = false; + + // read bytes from file until we run out + while( numRead == bufferSize && !error ) { + + numRead = fread( buffer, 1, bufferSize, file ); + + if( numRead > 0 ) { + SHA1_Update( &shaContext, buffer, numRead ); + } + else{ + error = true; + } + } + + fclose( file ); + delete [] buffer; + + + if( error ) { + printf( "Error reading from file %s\n", inArgs[1] ); + } + else { + unsigned char *rawDigest = new unsigned char[ SHA1_DIGEST_LENGTH ]; + + SHA1_Final( rawDigest, &shaContext ); + + // else hash is correct + char *digestHexString = hexEncode( rawDigest, SHA1_DIGEST_LENGTH ); + + printf( "%s %s\n", digestHexString, inArgs[1] ); + + delete [] rawDigest; + delete [] digestHexString; + } + + return 0; + } + + + +void usage( char *inAppName ) { + + printf( "Usage:\n\n" ); + printf( "\t%s file_to_sum\n", inAppName ); + + printf( "example:\n" ); + + printf( "\t%s test.txt\n", inAppName ); + + exit( 1 ); + } diff --git a/minorGems/crypto/hashes/sha1sumCompile b/minorGems/crypto/hashes/sha1sumCompile new file mode 100755 index 0000000..c554878 --- /dev/null +++ b/minorGems/crypto/hashes/sha1sumCompile @@ -0,0 +1 @@ +g++ -I../../.. -o sha1sum sha1sum.cpp sha1.cpp ../../formats/encodingUtils.cpp \ No newline at end of file diff --git a/minorGems/formats/encodingUtils.cpp b/minorGems/formats/encodingUtils.cpp new file mode 100644 index 0000000..4c2f22a --- /dev/null +++ b/minorGems/formats/encodingUtils.cpp @@ -0,0 +1,453 @@ +/* + * Modification History + * + * 2003-August-22 Jason Rohrer + * Created. + * + * 2003-September-22 Jason Rohrer + * Added base64 encoding. + * + * 2004-March-21 Jason Rohrer + * Fixed a variable scoping and redefinition bug pointed out by Benjamin Meyer. + */ + + +#include "encodingUtils.h" + + +#include "minorGems/util/SimpleVector.h" + + +#include +#include + + + +char fourBitIntToHex( int inInt ) { + char outChar[2]; + + if( inInt < 10 ) { + sprintf( outChar, "%d", inInt ); + } + else { + switch( inInt ) { + case 10: + outChar[0] = 'A'; + break; + case 11: + outChar[0] = 'B'; + break; + case 12: + outChar[0] = 'C'; + break; + case 13: + outChar[0] = 'D'; + break; + case 14: + outChar[0] = 'E'; + break; + case 15: + outChar[0] = 'F'; + break; + default: + outChar[0] = '0'; + break; + } + } + + return outChar[0]; + } + + + +// returns -1 if inHex is not a valid hex character +int hexToFourBitInt( char inHex ) { + int returnInt; + + switch( inHex ) { + case '0': + returnInt = 0; + break; + case '1': + returnInt = 1; + break; + case '2': + returnInt = 2; + break; + case '3': + returnInt = 3; + break; + case '4': + returnInt = 4; + break; + case '5': + returnInt = 5; + break; + case '6': + returnInt = 6; + break; + case '7': + returnInt = 7; + break; + case '8': + returnInt = 8; + break; + case '9': + returnInt = 9; + break; + case 'A': + case 'a': + returnInt = 10; + break; + case 'B': + case 'b': + returnInt = 11; + break; + case 'C': + case 'c': + returnInt = 12; + break; + case 'D': + case 'd': + returnInt = 13; + break; + case 'E': + case 'e': + returnInt = 14; + break; + case 'F': + case 'f': + returnInt = 15; + break; + default: + returnInt = -1; + break; + } + + return returnInt; + } + + + +char *hexEncode( unsigned char *inData, int inDataLength ) { + + char *resultHexString = new char[ inDataLength * 2 + 1 ]; + int hexStringIndex = 0; + + for( int i=0; i> 4 ); + int lowBits = 0xF & ( currentByte ); + + resultHexString[ hexStringIndex ] = fourBitIntToHex( highBits ); + hexStringIndex++; + + resultHexString[ hexStringIndex ] = fourBitIntToHex( lowBits ); + hexStringIndex++; + } + + resultHexString[ hexStringIndex ] = '\0'; + + return resultHexString; + } + + + +unsigned char *hexDecode( char *inHexString ) { + + int hexLength = strlen( inHexString ); + + if( hexLength % 2 != 0 ) { + // hex strings must be even in length + return NULL; + } + + int dataLength = hexLength / 2; + + unsigned char *rawData = new unsigned char[ dataLength ]; + + + for( int i=0; i *encodingVector = new SimpleVector(); + + int numInLine = 0; + + // take groups of 3 data bytes and map them to 4 base64 digits + for( int i=0; i> 18 ); + unsigned int digitB = 0x3F & ( block >> 12 ); + unsigned int digitC = 0x3F & ( block >> 6 ); + unsigned int digitD = 0x3F & ( block ); + + encodingVector->push_back( binaryToAscii[ digitA ] ); + encodingVector->push_back( binaryToAscii[ digitB ] ); + encodingVector->push_back( binaryToAscii[ digitC ] ); + encodingVector->push_back( binaryToAscii[ digitD ] ); + numInLine += 4; + + if( inBreakLines && numInLine == 76 ) { + // break the line + encodingVector->push_back( '\r' ); + encodingVector->push_back( '\n' ); + numInLine = 0; + } + + } + else { + // at end + int numLeft = inDataLength - i; + + switch( numLeft ) { + case 0: + // no padding + break; + case 1: { + // two digits, two pads + unsigned int block = + inData[i] << 16 | + 0; + unsigned int digitA = 0x3F & ( block >> 18 ); + unsigned int digitB = 0x3F & ( block >> 12 ); + + encodingVector->push_back( binaryToAscii[ digitA ] ); + encodingVector->push_back( binaryToAscii[ digitB ] ); + + encodingVector->push_back( '=' ); + encodingVector->push_back( '=' ); + break; + } + case 2: { + // three digits, one pad + unsigned int block = + inData[i] << 16 | + inData[i+1] << 8 | + 0; + + // base64 digits, with digitA at left + unsigned int digitA = 0x3F & ( block >> 18 ); + unsigned int digitB = 0x3F & ( block >> 12 ); + unsigned int digitC = 0x3F & ( block >> 6 ); + + encodingVector->push_back( binaryToAscii[ digitA ] ); + encodingVector->push_back( binaryToAscii[ digitB ] ); + encodingVector->push_back( binaryToAscii[ digitC ] ); + encodingVector->push_back( '=' ); + break; + } + default: + break; + } + // done with all data + i = inDataLength; + } + } + + char *returnString = encodingVector->getElementString(); + + delete encodingVector; + + return returnString; + } + + + +unsigned char *base64Decode( char *inBase64String, + int *outDataLength ) { + + SimpleVector *decodedVector = + new SimpleVector(); + + + + int encodingLength = strlen( inBase64String ); + + SimpleVector *binaryEncodingVector = + new SimpleVector(); + + int i; + for( i=0; ipush_back( currentBinary ); + } + } + + int binaryEncodingLength = binaryEncodingVector->size(); + + unsigned char *binaryEncoding = binaryEncodingVector->getElementArray(); + delete binaryEncodingVector; + + int blockCount = binaryEncodingLength / 4; + + if( binaryEncodingLength % 4 != 0 ) { + // extra, 0-padded block + blockCount += 1; + } + + + + // take groups of 4 encoded digits and map them to 3 data bytes + for( i=0; i> 16 ); + unsigned int digitB = 0xFF & ( block >> 8 ); + unsigned int digitC = 0xFF & ( block ); + + decodedVector->push_back( digitA ); + decodedVector->push_back( digitB ); + decodedVector->push_back( digitC ); + } + else { + // at end + int numLeft = binaryEncodingLength - i; + + switch( numLeft ) { + case 0: + // no padding + break; + case 1: { + // impossible + break; + } + case 2: { + // two base64 digits, one data byte + unsigned int block = + binaryEncoding[i] << 18 | + binaryEncoding[i+1] << 12 | + 0; + + // data byte digits, with digitA at left + unsigned int digitA = 0xFF & ( block >> 16 ); + + decodedVector->push_back( digitA ); + break; + } + case 3: { + // three base64 digits, two data bytes + unsigned int block = + binaryEncoding[i] << 18 | + binaryEncoding[i+1] << 12 | + binaryEncoding[i+2] << 6 | + 0; + + // data byte digits, with digitA at left + unsigned int digitA = 0xFF & ( block >> 16 ); + unsigned int digitB = 0xFF & ( block >> 8 ); + + + decodedVector->push_back( digitA ); + decodedVector->push_back( digitB ); + break; + } + default: + break; + } + // done with all data + i = binaryEncodingLength; + } + } + + delete [] binaryEncoding; + + + *outDataLength = decodedVector->size(); + unsigned char* returnData = decodedVector->getElementArray(); + + delete decodedVector; + + return returnData; + } + + + + + diff --git a/minorGems/formats/encodingUtils.h b/minorGems/formats/encodingUtils.h new file mode 100644 index 0000000..3ae3542 --- /dev/null +++ b/minorGems/formats/encodingUtils.h @@ -0,0 +1,92 @@ +/* + * Modification History + * + * 2003-August-22 Jason Rohrer + * Created. + * + * 2003-September-22 Jason Rohrer + * Added base64 encoding. + */ + + + +#ifndef ENCODING_UTILS_INCLUDED +#define ENCODING_UTILS_INCLUDED + + + +/** + * A collection of functions for representing data in various encoding formats. + * + * @author Jason Rohrer + */ + + + +/** + * Encodes data as a ASCII hexidecimal string. + * + * @param inData the data to encode. + * Must be destroyed by caller. + * @param inDataLength the length of inData in bytes. + * + * @return a \0-terminated ASCII hexidecimal string containing + * characters in the range [0-9] and [A-F]. + * Will be twice as long as inData. + * Must be destroyed by caller. + */ +char *hexEncode( unsigned char *inData, int inDataLength ); + + + +/** + * Decodes raw data from an ASCII hexidecimal string. + * + * @param inHexString a \0-terminated hexidecimal string. + * Must be destroyed by caller. + * + * @return an array of raw data, or NULL if decoding fails. + * Will be half as long as inHexString. + * Must be destroyed by caller if non-NULL. + */ +unsigned char *hexDecode( char *inHexString ); + + + + +/** + * Encodes data as a ASCII base64 string. + * + * @param inData the data to encode. + * Must be destroyed by caller. + * @param inDataLength the length of inData in bytes. + * @param inBreakLines set to true to break lines every 76 characters, + * or false to produce an unbroken base64 string. + * + * @return a \0-terminated ASCII base64 string containing + * characters in the range [0-9], [A-Z], [a-z], and [+,/,=]. + * Must be destroyed by caller. + */ +char *base64Encode( unsigned char *inData, int inDataLength, + char inBreakLines = true ); + + + +/** + * Decodes raw data from an ASCII base64 string. + * + * @param inBase64String a \0-terminated base64 string. Can optionally contain + * linebreaks. + * Must be destroyed by caller. + * @param outDataLength pointer to where the length of the decoded data + * should be returned. + * + * @return an array of raw data, or NULL if decoding fails. + * Must be destroyed by caller if non-NULL. + */ +unsigned char *base64Decode( char *inBase64String, + int *outDataLength ); + + + +#endif diff --git a/minorGems/formats/encodingUtilsTest.cpp b/minorGems/formats/encodingUtilsTest.cpp new file mode 100644 index 0000000..b5ba03d --- /dev/null +++ b/minorGems/formats/encodingUtilsTest.cpp @@ -0,0 +1,57 @@ +/* + * Modification History + * + * 2003-September-22 Jason Rohrer + * Created. + */ + + +#include "encodingUtils.h" + +#include +#include + + + +int main() { + + const char *dataString = + "*#$&$(@KFI#*$(SDBM@#*!(@%" + "*#$&$(@KFI#*$(SDBM@#*!(@%" + "*#$&$(@KFI#*$(SDBM@#*!(@F" + "*#$&$(@KFI#*$(SDBM@#*!(@F" + "*#$&$(@KFI#*$(SDBM@#*!(@F" + "*#$&$(@KFI#*$(SDBM@#*!(@%a"; + + printf( "base64 encoding the string: %s\n", dataString ); + + char *encoding = base64Encode( (unsigned char *)dataString, + strlen( dataString ), + true ); + + printf( "Encoded as:\n%s\n", encoding ); + + + int decodedLength; + unsigned char *decoding = base64Decode( encoding, &decodedLength ); + + char *buffer = new char[ decodedLength + 1 ]; + memcpy( (void *)buffer, (void *)decoding, decodedLength ); + + buffer[ decodedLength ] = '\0'; + + printf( "Decoded as: %s\n", buffer ); + + if( strcmp( buffer, dataString ) == 0 ) { + printf( "Test passed\n" ); + } + else { + printf( "Test failed\n" ); + } + + delete [] buffer; + delete [] decoding; + delete [] encoding; + + return 0; + } diff --git a/minorGems/formats/encodingUtilsTestCompile b/minorGems/formats/encodingUtilsTestCompile new file mode 100755 index 0000000..4bcaf2d --- /dev/null +++ b/minorGems/formats/encodingUtilsTestCompile @@ -0,0 +1 @@ +g++ -g -I../.. -o encodingUtilsTest encodingUtilsTest.cpp encodingUtils.cpp ../util/stringUtils.cpp ../util/StringBufferOutputStream.cpp \ No newline at end of file diff --git a/minorGems/formats/html/HTMLUtils.cpp b/minorGems/formats/html/HTMLUtils.cpp new file mode 100644 index 0000000..5ebaf12 --- /dev/null +++ b/minorGems/formats/html/HTMLUtils.cpp @@ -0,0 +1,57 @@ +/* + * Modification History + * + * 2002-September-12 Jason Rohrer + * Created. + */ + + + +#include "HTMLUtils.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" + +#include + + + +char *HTMLUtils::removeAllTags( char *inString ) { + + SimpleVector *returnStringVector = new SimpleVector(); + + + int stringLength = strlen( inString ); + + int i = 0; + + while( i < stringLength ) { + if( inString[i] == '<' ) { + // the start of a tag + + // skip all until (and including) close of tag + while( i < stringLength && inString[i] != '>' ) { + // do nothing + i++; + } + } + else { + returnStringVector->push_back( inString[i] ); + } + i++; + } + + + int numChars = returnStringVector->size(); + char *returnString = new char[ numChars + 1 ]; + + for( i=0; igetElement( i ) ); + } + + returnString[ numChars ] = '\0'; + + delete returnStringVector; + + return returnString; + } diff --git a/minorGems/formats/html/HTMLUtils.h b/minorGems/formats/html/HTMLUtils.h new file mode 100644 index 0000000..344cdd1 --- /dev/null +++ b/minorGems/formats/html/HTMLUtils.h @@ -0,0 +1,46 @@ +/* + * Modification History + * + * 2002-September-12 Jason Rohrer + * Created. + */ + + + +#ifndef HTML_UTILS_INCLUDED +#define HTML_UTILS_INCLUDED + + + +/** + * Utilities for processing HTML. + * + * @author Jason Rohrer + */ +class HTMLUtils { + + + + public: + + + + /** + * Removes all HTML tags from an HTML string. + * + * @param the HTML data as a \0-terminated string. + * Must be destroyed by caller if non-const. + * + * @return data with all HTML tags removed as a newly allocated + * \0-terminated string. + * Must be destroyed by caller. + */ + static char *removeAllTags( char *inString ); + + + + }; + + + +#endif diff --git a/minorGems/formats/xml/XMLUtils.cpp b/minorGems/formats/xml/XMLUtils.cpp new file mode 100644 index 0000000..6c95d72 --- /dev/null +++ b/minorGems/formats/xml/XMLUtils.cpp @@ -0,0 +1,83 @@ +/* + * Modification History + * + * 2002-September-12 Jason Rohrer + * Created. + */ + + + +#include "XMLUtils.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" + +#include + + + +char *XMLUtils::escapeDisallowedCharacters( char *inString ) { + + SimpleVector *returnStringVector = new SimpleVector(); + + + int stringLength = strlen( inString ); + + int i; + for( i=0; ipush_back( '&' ); + returnStringVector->push_back( 'a' ); + returnStringVector->push_back( 'm' ); + returnStringVector->push_back( 'p' ); + returnStringVector->push_back( ';' ); + break; + case '<': + returnStringVector->push_back( '&' ); + returnStringVector->push_back( 'l' ); + returnStringVector->push_back( 't' ); + returnStringVector->push_back( ';' ); + break; + case '>': + returnStringVector->push_back( '&' ); + returnStringVector->push_back( 'g' ); + returnStringVector->push_back( 't' ); + returnStringVector->push_back( ';' ); + break; + case '\"': + returnStringVector->push_back( '&' ); + returnStringVector->push_back( 'q' ); + returnStringVector->push_back( 'u' ); + returnStringVector->push_back( 'o' ); + returnStringVector->push_back( 't' ); + returnStringVector->push_back( ';' ); + break; + case '\'': + returnStringVector->push_back( '&' ); + returnStringVector->push_back( 'a' ); + returnStringVector->push_back( 'p' ); + returnStringVector->push_back( 'o' ); + returnStringVector->push_back( 's' ); + returnStringVector->push_back( ';' ); + break; + default: + returnStringVector->push_back( inString[i] ); + break; + } + } + + int numChars = returnStringVector->size(); + char *returnString = new char[ numChars + 1 ]; + + for( i=0; igetElement( i ) ); + } + + returnString[ numChars ] = '\0'; + + delete returnStringVector; + + return returnString; + } diff --git a/minorGems/formats/xml/XMLUtils.h b/minorGems/formats/xml/XMLUtils.h new file mode 100644 index 0000000..7a31044 --- /dev/null +++ b/minorGems/formats/xml/XMLUtils.h @@ -0,0 +1,46 @@ +/* + * Modification History + * + * 2002-September-12 Jason Rohrer + * Created. + */ + + + +#ifndef XML_UTILS_INCLUDED +#define XML_UTILS_INCLUDED + + + +/** + * Utilities for processing XML. + * + * @author Jason Rohrer + */ +class XMLUtils { + + + + public: + + + + /** + * Escapes characters disallowed in XML character data. + * + * @param the string to escape as a \0-terminated string. + * Must be destroyed by caller if non-const. + * + * @return string with characters escaped as a newly allocated + * \0-terminated string. + * Must be destroyed by caller. + */ + static char *escapeDisallowedCharacters( char *inString ); + + + + }; + + + +#endif diff --git a/minorGems/graphics/3d/LandscapePrimitive3D.h b/minorGems/graphics/3d/LandscapePrimitive3D.h new file mode 100644 index 0000000..5e0a7d0 --- /dev/null +++ b/minorGems/graphics/3d/LandscapePrimitive3D.h @@ -0,0 +1,138 @@ +/* + * Modification History + * + * 2001-January-9 Jason Rohrer + * Created. Copied from LandscapePrimitiveGL, which this class will replace + * + * 2001-January-15 Jason Rohrer + * Fixed a bug in the constructor. + * + * 2001-January-17 Jason Rohrer + * Fliped how x and y in height map correspond to x and z in the terrain. + * + * 2001-January-19 Jason Rohrer + * Changed to support multi-texturing. + * + * 2001-January-30 Jason Rohrer + * Fixed a bug that occurs when the class defaults to no detail texture. + * + * 2001-March-11 Jason Rohrer + * Fixed a bug in the texture map anchor points. + */ + +#ifndef LANDSCAPE_PRIMITIVE_3D_INCLUDED +#define LANDSCAPE_PRIMITIVE_3D_INCLUDED + +#include "Primitive3D.h" + +/** + * Primitive 3D lanscape object. + * + * Made from a height map. Mesh generated spans both x and z between -1 and + * 1, and can vary in height (y) from 0 to 1. + * + * @author Jason Rohrer + */ +class LandscapePrimitive3D : public Primitive3D { + + public: + + + /** + * Constructs a LandscapePrimitive with a multi-layer texture. + * + * @param inWide width of mesh in number of vertices. + * Must be even and at least 2. + * @param inHigh height of mesh in number of vertices. + * @param inHeights array of heights for each vertex, each in + * [0,1]. Must be destroyed by caller. + * @param inTexture the texture to map onto the lanscape. + * Texture is anchored by its corners to the corners of the + * lanscape. inTexture is destroyed when primitive is destroyed. + * @param inDetailTexture the second layer in the multi-texture. + * The alpha channel of inDetailTexture will be used as + * the blending factor to mix the detail with the global texture. + * Set to NULL to use only a single layer. + * @param inDetailScale the scale factor of the detailed texture. + * Setting to 1.0 will anchor the detail at the corners of the + * mesh (just like inTexture). Setting to 0.25 will cause + * the detail texture to cycle 4 times across the surface of the + * mesh. + */ + LandscapePrimitive3D( int inWide, int inHigh, double *inHeights, + RGBAImage *inTexture, RGBAImage *inDetailTexture = NULL, + double inDetailScale = 1.0 ); + + }; + + + +inline LandscapePrimitive3D::LandscapePrimitive3D( int inWide, + int inHigh, double *inHeights, + RGBAImage *inTexture, RGBAImage *inDetailTexture, + double inDetailScale ) { + + // first, set Primitve3D members + mHigh = inHigh; + mWide = inWide; + mNumVertices = mHigh * mWide; + + if( inDetailTexture == NULL ) { + mNumTextures = 1; + mTexture = new RGBAImage*[1]; + mTexture[0] = inTexture; + + mAnchorX = new double*[1]; + mAnchorY = new double*[1]; + mAnchorX[0] = new double[mNumVertices]; + mAnchorY[0] = new double[mNumVertices]; + } + else { + mNumTextures = 2; + mTexture = new RGBAImage*[2]; + mTexture[0] = inTexture; + mTexture[1] = inDetailTexture; + + mAnchorX = new double*[2]; + mAnchorY = new double*[2]; + mAnchorX[0] = new double[mNumVertices]; + mAnchorY[0] = new double[mNumVertices]; + mAnchorX[1] = new double[mNumVertices]; + mAnchorY[1] = new double[mNumVertices]; + } + + + mVertices = new Vector3D*[mNumVertices]; + + // anchor at texture corners, and step linearly through texture + double anchorYStep = 1.0 / ( mHigh - 1 ); + double anchorXStep = 1.0 / ( mWide - 1 ); + + double detailAnchorYStep = 1.0 / ( ( mHigh - 1 ) * inDetailScale ); + double detailAnchorXStep = 1.0 / ( ( mWide - 1 ) * inDetailScale ); + + for( int y=0; ymZ = 0; + } + + mVertices = new Vector3D*[mNumVertices]; + + mAnchorX = new double*[1]; + mAnchorY = new double*[1]; + mAnchorX[0] = new double[mNumVertices]; + mAnchorY[0] = new double[mNumVertices]; + + // anchor at texture corners, and step linearly through texture + double anchorYStep = 1.0 / ( mHigh - 1 ); + double anchorXStep = 1.0 / ( mWide - 1 ); + + for( int y=0; yreverseRotate( latheRotation ); + + delete latheRotation; + } + + // cleanup as we go along + delete inCurvePoints[y]; + } + + delete [] inCurvePoints; + + generateNormals(); + } + +#endif diff --git a/minorGems/graphics/3d/Object3D.h b/minorGems/graphics/3d/Object3D.h new file mode 100644 index 0000000..d37a3a4 --- /dev/null +++ b/minorGems/graphics/3d/Object3D.h @@ -0,0 +1,349 @@ +/* + * Modification History + * + * 2001-January-9 Jason Rohrer + * Created. Copied from ObjectGL. + * + * 2001-January-10 Jason Rohrer + * Made class serializable. Added a parameterless constructor + * to facilitate deserialization. + * + * 2001-January-15 Jason Rohrer + * Fixed several bugs in the deserialize() function, as well as in the + * destructor. + * + * 2001-January-16 Jason Rohrer + * Changed to use a Transform3D instead of Vectors, Angles, and scales for + * each primitive in the object. + * + * 2001-January-24 Jason Rohrer + * Added a copy() function. + * Fixed a bug in deserialize(). + * Made mMembersAllocated public for copy function. + * + * 2001-January-26 Jason Rohrer + * Fixed a bug in copy(). + * + * 2001-January-31 Jason Rohrer + * Fixed bugs in serialize() and deserialize(). + * + * 2001-February-3 Jason Rohrer + * Updated serialization code to use new interfaces. + * + * 2001-March-11 Jason Rohrer + * Added support for paramatization and temporal animations. + * + * 2001-March-13 Jason Rohrer + * Added interface for getting the number of parameters and animations. + * + * 2001-March-14 Jason Rohrer + * Added use of Primitive3DFactory for typed subclass primitive + * de/serialization. + */ + + +#ifndef OBJECT_3D_INCLUDED +#define OBJECT_3D_INCLUDED + +#include "Primitive3D.h" +#include "minorGems/math/geometry/Transform3D.h" + +#include "minorGems/io/Serializable.h" + +#include "Primitive3DFactory.h" + +/** + * 3D object. + * + * Comprised of a collection of primitives. + * + * @author Jason Rohrer + */ +class Object3D : public Serializable { + + public: + + + /** + * Constructs an Object. + * + * No parameters are copied, so they should not be destroyed + * or re-accessed by caller. All are destroyed when the + * object is destroyed. + * + * @param inNumPrimitives the number of primitives in this object. + * @param inPrimitives the primitives comprising this object. + * @param inTransform a transform for each object. + */ + Object3D( long inNumPrimitives, Primitive3D **inPrimitives, + Transform3D **inTransform ); + + Object3D(); + + ~Object3D(); + + + /** + * Copies this object. + * + * @return a copy of this object, which must be destroyed + * by the caller. + */ + Object3D *copy(); + + + /** + * Gets the number of parameters associated with this object. + * + * @return the number of parameters. + */ + virtual int getNumParameters(); + + + /** + * Gets the number of animations associated with this object. + * + * @return the number of animations. + */ + virtual int getNumAnimations(); + + + /* + * Note that the default implementations for all the parameter + * and temporal animation functions do nothing. + */ + + /** + * Sets a parameter for this object. + * + * @param inParameterIndex the index of the parameter to set. + * If an index for a non-existing parameter is specified, + * this call has no effect. + * @param inValue the value to set the parameter to, in [-1, 1]. + * The default value for all parameters is 0. + */ + virtual void setParameter( int inParameterIndex, double inValue ); + + + /** + * Gets a parameter for this object. + * + * @param inParameterIndex the index of the parameter to get. + * If an index for a non-existing parameter is specified, + * 0 is returned. + * + * @return the value of the parameter, in [-1, 1]. + * The default value for all parameters is 0. + */ + virtual double getParameter( int inParameterIndex ); + + + /** + * Steps this object forward in time. + * + * @param inStepSize the size of the timestep to take. + */ + virtual void step( double inStepSize ); + + + /** + * Starts a temporal animation of this object. + * The animation progresses as step() is called repeatedly. + * If called for a non-existent animation or for one that is + * already running, this function has no effect. + */ + virtual void startAnimation( int inAnimationIndex ); + + + /** + * Stops a temporal animation of this object. If called + * for a non-existent animation or for one that is not currently + * running, this function has no effect. + */ + virtual void stopAnimation( int inAnimationIndex ); + + + long mNumPrimitives; + + Primitive3D **mPrimitives; + + Transform3D **mTransform; + + // implement the Serializable interface + virtual int serialize( OutputStream *inOutputStream ); + virtual int deserialize( InputStream *inInputStream ); + + char mMembersAllocated; + + }; + + + +inline Object3D::Object3D( long inNumPrimitives, Primitive3D **inPrimitives, + Transform3D **inTransform ) + : mNumPrimitives( inNumPrimitives ), mPrimitives( inPrimitives ), + mTransform( inTransform ), mMembersAllocated( true ) { + + } + + + +inline Object3D::Object3D() + : mMembersAllocated( false ) { + + } + + + +inline Object3D::~Object3D() { + if( mMembersAllocated ) { + for( int i=0; imNumPrimitives = mNumPrimitives; + + objCopy->mPrimitives = new Primitive3D*[ mNumPrimitives ]; + objCopy->mTransform = new Transform3D*[ mNumPrimitives ]; + + int i; + for( i=0; imPrimitives[i] = mPrimitives[i]->copy(); + objCopy->mTransform[i] = new Transform3D( mTransform[i] ); + } + + objCopy->mMembersAllocated = true; + + return objCopy; + } + + + +inline int Object3D::getNumParameters() { + return 0; + } + + + +inline int Object3D::getNumAnimations() { + return 0; + } + + + +// the base class versions of these functions do nothing + +inline void Object3D::setParameter( int inParameterIndex, double inValue ) { + + } + + + +inline double Object3D::getParameter( int inParameterIndex ) { + + return 0; + } + + + +inline void Object3D::step( double inStepSize ) { + + } + + + +inline void Object3D::startAnimation( int inAnimationIndex ) { + + } + + + +inline void Object3D::stopAnimation( int inAnimationIndex ) { + + } + + + +inline int Object3D::serialize( OutputStream *inOutputStream ) { + int i; + int numBytes = 0; + + numBytes += inOutputStream->writeLong( mNumPrimitives ); + + + // write each primitive + for( i=0; iwriteLong( typeFlag ); + + // write the primitive + numBytes += mPrimitives[i]->serialize( inOutputStream ); + } + + // write each primitive's transform + for( i=0; iserialize( inOutputStream ); + } + + return numBytes; + } + + + +inline int Object3D::deserialize( InputStream *inInputStream ) { + + if( mMembersAllocated ) { + // first, delete current contents of object + for( int i=0; ireadLong( &mNumPrimitives ); + + printf( "receiving %d primitives\n", mNumPrimitives ); + mPrimitives = new Primitive3D*[mNumPrimitives]; + + for( i=0; ireadLong( &typeFlag ); + + // construct a new object based on the type flag + mPrimitives[i] = + Primitive3DFactory::intToPrimitive3D( typeFlag ); + + // deserialize it + numBytes += mPrimitives[i]->deserialize( inInputStream ); + } + + mTransform = new Transform3D*[mNumPrimitives]; + for( i=0; ideserialize( inInputStream ); + } + + mMembersAllocated = true; + + return numBytes; + } + + +#endif diff --git a/minorGems/graphics/3d/Object3DFactory.h b/minorGems/graphics/3d/Object3DFactory.h new file mode 100644 index 0000000..4239060 --- /dev/null +++ b/minorGems/graphics/3d/Object3DFactory.h @@ -0,0 +1,129 @@ +/* + * Modification History + * + * 2001-March-14 Jason Rohrer + * Created. + * + * 2001-March-17 Jason Rohrer + * Finished implementation. + * + * 2001-April-1 Jason Rohrer + * Fixed flag name. + */ + +#ifndef OBJECT_3D_FACTORY_INCLUDED +#define OBJECT_3D_FACTORY_INCLUDED + +#include "Object3D.h" + +// Run-time type identification interface (RTTI) +#include + + +// include these objects only if we are part of subreal +#ifdef SUBREAL +#include "subreal/entity/EntityObject3D.h" +#define FACTORY_ENTITY_OBJECT_FLAG 1 + +#endif + +// the default flag +#define FACTORY_DEFAULT_OBJECT_FLAG 0 + + +/** + * Class that maps Object3D integer subclass type flags to Object3D + * subclass instances. + * + * Motivation for this class: + * We have Object3D instances that exist simultaneously on opposite + * sides of the network (at both the client and server ends). Initially, + * we just let the objects get sent to the server via the base class + * Object3D serialization function. At that time, subclasses of Object3D + * did nothing more than init an Object3D in a particular way. This + * worked fine when we didn't have animations associated with various + * Object3D subclasses, but doesn't work now that we have animation + * code in various subclasses. During serialization (if the object + * is sent as a generic Object3D), the animation code is lost on + * the client end. We need a way for the client to pick the correct + * subclass to construct for deserialization. We can encode the + * various subtypes by integers, and then this factory class can + * be used to construct a class of appropriate subtype before + * deserialization. + * + * @author Jason Rohrer + */ +class Object3DFactory { + public: + + /** + * Finds an integer subclass type flag for an object instance. + * + * @param inObject the object to determine a flag for. Must + * be destroyed by the caller. + * + * @return a type flag for inObject. 0 (the defautl Object3D + * baseclass flag) will be returned if subclass determination fails. + */ + static int object3DToInt( Object3D *inObject ); + + + /** + * Constructs a new, unitialized Object3D (in other words, + * an Object3D ready for deserialization) of a subclass + * type matching inTypeFlag. + * + * @param inTypeFlag the type flag specifying the class + * of the returned object. + * + * @return an (unitialized) Object3D class instance with a + * subclass type corresponding to inTypeFlag. If no + * matching class is found, a default Object3D baseclass + * instance is returned. Must be destroyed by the caller. + */ + static Object3D *intToObject3D( int inTypeFlag ); + + }; + + + +inline int Object3DFactory::object3DToInt( Object3D *inObject ) { + + // use RTTI to determine type of inObject + +#ifdef SUBREAL + if( typeid( *inObject ) == typeid( EntityObject3D ) ) { + return FACTORY_ENTITY_OBJECT_FLAG; + } +#endif + // else return the default flag + return FACTORY_DEFAULT_OBJECT_FLAG; + } + + + +inline Object3D *Object3DFactory::intToObject3D( int inTypeFlag ) { + switch( inTypeFlag ) { + case FACTORY_DEFAULT_OBJECT_FLAG: + return new Object3D(); + break; + + /* these objects are only defined if + * we are part of subreal + */ +#ifdef SUBREAL + case FACTORY_ENTITY_OBJECT_FLAG: + return new EntityObject3D(); + break; +#endif + + default: + // unknown object flag type + return new Object3D(); + break; + } + + } + + +#endif diff --git a/minorGems/graphics/3d/Primitive3D.h b/minorGems/graphics/3d/Primitive3D.h new file mode 100644 index 0000000..ec8a91a --- /dev/null +++ b/minorGems/graphics/3d/Primitive3D.h @@ -0,0 +1,676 @@ +/* + * Modification History + * + * 2001-January-9 Jason Rohrer + * Created. + * + * 2001-January-10 Jason Rohrer + * Made class serializable. + * Added a mMembersAllocated flag and made parameterless constructor + * public to help with deserialization. + * + * 2001-January-15 Jason Rohrer + * Fixed a bug in the deserialize() function. + * + * 2001-January-16 Jason Rohrer + * Fixed a bug in the anchor serialization code. + * + * 2001-January-19 Jason Rohrer + * Changed to support multi-texturing. + * + * 2001-January-24 Jason Rohrer + * Added a copy() function. + * Fixed a bug in deserialize(). + * Made mMembersAllocated public for copy function. + * + * 2001-February-3 Jason Rohrer + * Updated serialization code to use new interfaces. + * + * 2001-March-11 Jason Rohrer + * Added support for paramatization and temporal animations. + * + * 2001-March-13 Jason Rohrer + * Added interface for getting the number of parameters and animations. + * + * 2001-April-1 Jason Rohrer + * Made copy function virtual. Added a getNewInstance function + * to make derived-class-specific copying easier. + */ + + +#ifndef PRIMITIVE_3D_INCLUDED +#define PRIMITIVE_3D_INCLUDED + +#include + +#include "minorGems/graphics/RGBAImage.h" +#include "minorGems/math/geometry/Angle3D.h" + +#include "minorGems/math/geometry/Vector3D.h" +#include "minorGems/math/geometry/Angle3D.h" + +#include "minorGems/io/Serializable.h" + +/** + * 3D primitive object. + * + * Comprised of a triangle mesh, texture map, and anchor points. + * + * @author Jason Rohrer + */ +class Primitive3D : public Serializable { + + public: + + + /** + * Constructs a Primitive. + * + * No parameters are copied, so they should not be destroyed + * or re-accessed by caller. All are destroyed when the + * primitive is destroyed. + * + * @param inWide width of mesh in number of vertices. + * Must be even and at least 2. + * @param inHigh height of mesh in number of vertices. + * @param inVertices vertices in row-major order. + * @param inNumTextures number of multi-texture layers for + * this primitive. + * @param inTexture array of textures to map onto mesh. + * Note that no RGBAImage in this array should have a + * selection in it. + * @param inAnchorX x texture anchors for each texture and + * each vertex (indexed as inAnchorX[textNum][vertNum]), + * in range [0,1] (outside this range, texture will wrap). + * @param inAnchorY y texture anchors for each texture and + * each vertex (indexed as inAnchorY[textNum][vertNum]), + * in range [0,1] (outside this range, texture will wrap). + */ + Primitive3D( long inWide, long inHigh, Vector3D **inVertices, + long inNumTextures, RGBAImage **inTexture, + double **inAnchorX, double **inAnchorY ); + + + // construct without initializing any members + // for use by subclasses and for deserialization. + Primitive3D(); + char mMembersAllocated; + + + ~Primitive3D(); + + + /** + * Sets whether this primitive is transparent or not. Default + * is not transparent. + * + * @param inTransparent true if this primitive is transparent. + */ + void setTransparent( char inTransparent ); + + + /** + * Gets whether this primitive is transparent or not. + * + * @return true iff this primitive is transparent. + */ + char isTransparent(); + + + /** + * Sets whether this primitive's back face is visible. Default + * is not visible. + * + * @param inIsVisible true if the back face is visible. + */ + void setBackVisible( char inIsVisible ); + + + /** + * Gets whether this primitive's back face is visible or not. + * + * @return true iff the back face is visible. + */ + char isBackVisible(); + + + /** + * Gets a new instance of the derived class type. + * + * Should be equivalent to calling the default constructor + * for the derived class type. + * + * Should be overridden by all derived classes that + * have data members beyond those provided by Primitive3D. + * Note that if the extra data members require copying, + * then the copy() function should be overridden instead + * of simply overriding this function. + * + * Primitive3D::copy() will use this function to produce + * a class instance before doing a copy. + * + * Note that this functionality SHOULD be provided by + * the built-in RTTI, but it doesn't seem to be. + * + * @return an instance of the derived class. + */ + virtual Primitive3D *getNewInstance(); + + + /** + * Copies this primitive. + * + * @return a copy of this primitive, which must be destroyed + * by the caller. + */ + virtual Primitive3D *copy(); + + /** + * Gets the number of parameters associated with this object. + * + * @return the number of parameters. + */ + virtual int getNumParameters(); + + + /** + * Gets the number of animations associated with this object. + * + * @return the number of animations. + */ + virtual int getNumAnimations(); + + + /* + * Note that the default implementations for all the parameter + * and temporal animation functions do nothing. + */ + + /** + * Sets a parameter for this primative. + * + * @param inParameterIndex the index of the parameter to set. + * If an index for a non-existing parameter is specified, + * this call has no effect. + * @param inValue the value to set the parameter to, in [-1, 1]. + * The default value for all parameters is 0. + */ + virtual void setParameter( int inParameterIndex, double inValue ); + + + /** + * Gets a parameter for this primative. + * + * @param inParameterIndex the index of the parameter to get. + * If an index for a non-existing parameter is specified, + * 0 is returned. + * + * @return the value of the parameter, in [-1, 1]. + * The default value for all parameters is 0. + */ + virtual double getParameter( int inParameterIndex ); + + + /** + * Steps this primitive forward in time. + * + * @param inStepSize the size of the timestep to take. + */ + virtual void step( double inStepSize ); + + + /** + * Starts a temporal animation of this primitive. + * The animation progresses as step() is called repeatedly. + * If called for a non-existent animation or for one that is + * already running, this function has no effect. + */ + virtual void startAnimation( int inAnimationIndex ); + + /** + * Stops a temporal animation of this primitive. If called + * for a non-existent animation or for one that is not currently + * running, this function has no effect. + */ + virtual void stopAnimation( int inAnimationIndex ); + + + long mHigh, mWide; + long mNumVertices; + + Vector3D **mVertices; + Vector3D **mNormals; + + long mNumTextures; + + // Note that no RGBAImage in this array should have a + // selection in it. + RGBAImage **mTexture; + + double **mAnchorX; + double **mAnchorY; + + + // implement the Serializable interface + virtual int serialize( OutputStream *inOutputStream ); + virtual int deserialize( InputStream *inInputStream ); + + protected: + + + /** + * Generates standard normals from the vertices. + * + * If subclass is not generating normals, this + * must be called before primitive is drawn, but after + * the vertices have been initialized. + */ + void generateNormals(); + + + char mTransparent; + char mBackVisible; + + + + }; + + + +inline Primitive3D::Primitive3D( long inWide, long inHigh, + Vector3D **inVertices, long inNumTextures, RGBAImage **inTexture, + double **inAnchorX, double **inAnchorY ) + : mHigh( inHigh ), mWide( inWide ), mNumVertices( inHigh * inWide ), + mNumTextures( inNumTextures ), mVertices( inVertices ), + mTexture( inTexture ), mAnchorX( inAnchorX ), mAnchorY( inAnchorY ), + mTransparent( false ), mBackVisible( false ), + mMembersAllocated( true ) { + + generateNormals(); + } + + + +inline Primitive3D::Primitive3D() + : mTransparent( false ), mBackVisible( false ), + mMembersAllocated( false ) { + + } + + + +inline Primitive3D::~Primitive3D() { + if( mMembersAllocated ) { + int i; + for( i=0; isubtract( mVertices[ index ] ); + } + } + + // now cross and add into sum + for( e=0; e<4; e++ ) { + if( edges[e] != NULL && edges[ (e+1) % 4 ] != NULL ) { + // not that this order of crossing works + // because our cross product is right handed + Vector3D *normal = edges[e]->cross( edges[ (e+1) % 4 ] ); + normal->normalize(); + + // add this normal to our sum + normalSum->add( normal ); + + delete normal; + } + } + + // now delete edges + for( e=0; e<4; e++ ) { + if( edges[e] != NULL ) { + delete edges[e]; + } + } + delete [] edges; + + // save summed normal as normal for this point + normalSum->normalize(); + mNormals[index] = normalSum; + } + } + } + + + +inline void Primitive3D::setTransparent( char inTransparent ) { + mTransparent = inTransparent; + } + + + +inline char Primitive3D::isTransparent() { + return mTransparent; + } + + + +inline void Primitive3D::setBackVisible( char inIsVisible ) { + mBackVisible = inIsVisible; + } + + + +inline char Primitive3D::isBackVisible() { + return mBackVisible; + } + + + +inline Primitive3D *Primitive3D::getNewInstance() { + return new Primitive3D(); + } + + + +inline Primitive3D *Primitive3D::copy() { + // get an instance of the derived class, if they've + // overridden getNewInstance() + Primitive3D *primCopy = getNewInstance(); + + primCopy->mHigh = mHigh; + primCopy->mWide = mWide; + primCopy->mNumVertices = mNumVertices; + + primCopy->mVertices = new Vector3D*[mNumVertices]; + primCopy->mNormals = new Vector3D*[mNumVertices]; + + int i; + for( i=0; imVertices[i] = new Vector3D( mVertices[i] ); + primCopy->mNormals[i] = new Vector3D( mNormals[i] ); + } + primCopy->mNumTextures = mNumTextures; + + primCopy->mTexture = new RGBAImage*[mNumTextures]; + primCopy->mAnchorX = new double*[mNumTextures]; + primCopy->mAnchorY = new double*[mNumTextures]; + + for( i=0; imTexture[i] = mTexture[i]->copy(); + + primCopy->mAnchorX[i] = new double[mNumVertices]; + primCopy->mAnchorY[i] = new double[mNumVertices]; + + memcpy( primCopy->mAnchorX[i], mAnchorX[i], + sizeof( double ) * mNumVertices ); + memcpy( primCopy->mAnchorY[i], mAnchorY[i], + sizeof( double ) * mNumVertices ); + } + primCopy->mMembersAllocated = true; + + primCopy->setBackVisible( isBackVisible() ); + primCopy->setTransparent( isTransparent() ); + + return primCopy; + } + + + +inline int Primitive3D::getNumParameters() { + return 0; + } + + + +inline int Primitive3D::getNumAnimations() { + return 0; + } + + + +// the base class versions of these functions do nothing + +inline void Primitive3D::setParameter( int inParameterIndex, double inValue ) { + + } + + + +inline double Primitive3D::getParameter( int inParameterIndex ) { + + return 0; + } + + + +inline void Primitive3D::step( double inStepSize ) { + + } + + + +inline void Primitive3D::startAnimation( int inAnimationIndex ) { + + } + + + +inline void Primitive3D::stopAnimation( int inAnimationIndex ) { + + } + + + +inline int Primitive3D::serialize( OutputStream *inOutputStream ) { + int numBytes = 0; + + numBytes += inOutputStream->writeLong( mWide ); + numBytes += inOutputStream->writeLong( mHigh ); + + int i; + + // output vertices and normals + for( i=0; iserialize( inOutputStream ); + } + + for( i=0; iserialize( inOutputStream ); + } + + numBytes += inOutputStream->writeLong( mNumTextures ); + + // output textures + for( i=0; iserialize( inOutputStream ); + } + + // output anchor arrays + for( i=0; iwriteDouble( mAnchorX[i][p] ); + } + } + + for( i=0; iwriteDouble( mAnchorY[i][p] ); + } + } + + numBytes += + inOutputStream->write( (unsigned char *)&mTransparent, 1 ); + + numBytes += + inOutputStream->write( (unsigned char *)&mBackVisible, 1 ); + + return numBytes; + } + + + +inline int Primitive3D::deserialize( InputStream *inInputStream ) { + int numBytes = 0; + + int i; + if( mMembersAllocated ) { + // delete the old vertices, normals, and anchors + for( i=0; ireadLong( &mWide ); + numBytes += inInputStream->readLong( &mHigh ); + + mNumVertices = mHigh * mWide; + + mVertices = new Vector3D*[mNumVertices]; + mNormals = new Vector3D*[mNumVertices]; + + + // input vertices and normals + for( i=0; ideserialize( inInputStream ); + } + + for( i=0; ideserialize( inInputStream ); + } + + // input number of textures + numBytes += inInputStream->readLong( &mNumTextures ); + + mAnchorX = new double*[mNumTextures]; + mAnchorY = new double*[mNumTextures]; + mTexture = new RGBAImage*[mNumTextures]; + + + // input textures + for( i=0; ideserialize( inInputStream ); + } + + // input anchor arrays + for( i=0; ireadDouble( &( mAnchorX[i][p] ) ); + } + } + + for( i=0; ireadDouble( &( mAnchorY[i][p] ) ); + } + } + + numBytes += + inInputStream->read( (unsigned char *)&mTransparent, 1 ); + + numBytes += + inInputStream->read( (unsigned char *)&mBackVisible, 1 ); + + mMembersAllocated = true; + + return numBytesRead; + } + + + +#endif diff --git a/minorGems/graphics/3d/Primitive3DFactory.h b/minorGems/graphics/3d/Primitive3DFactory.h new file mode 100644 index 0000000..f53c50a --- /dev/null +++ b/minorGems/graphics/3d/Primitive3DFactory.h @@ -0,0 +1,129 @@ +/* + * Modification History + * + * 2001-March-14 Jason Rohrer + * Created. + * + * 2001-April-1 Jason Rohrer + * Added subreal entity vane class to factory. + */ + +#ifndef PRIMITIVE_3D_FACTORY_INCLUDED +#define PRIMITIVE_3D_FACTORY_INCLUDED + +#include "Primitive3D.h" + +// Run-time type identification interface (RTTI) +#include + + +// include these primitives only if we are part of subreal +#ifdef SUBREAL +#include "subreal/entity/EntityBodyPrimitive3D.h" +#define FACTORY_ENTITY_BODY_PRIMITIVE_FLAG 1 +#include "subreal/entity/EntityVanePrimitive3D.h" +#define FACTORY_ENTITY_VANE_PRIMITIVE_FLAG 2 + +#endif + +// the default flag +#define FACTORY_DEFAULT_PRIMITIVE_FLAG 0 + + +/** + * Class that maps Primitive3D integer subclass type flags to Primitive3D + * subclass instances. + * + * Motivation for this class: + * We want to extend Object3D to support subclass typed serialization + * and deserialization without placing too much of a burden on + * future Object3D subclasses. For instance, we don't want subclasses + * to have to write their own de/serialization functions so that + * particular Primitive3D subclasses are serialized correctly. + * We can avoid this burden by writing the base Object3D serialization + * code so that it uses this factory to transmit subclasses with + * type informaton. + * + * @author Jason Rohrer + */ +class Primitive3DFactory { + public: + + /** + * Finds an integer subclass type flag for a primitive instance. + * + * @param inPrimitive the primitive to determine a flag for. Must + * be destroyed by the caller. + * + * @return a type flag for inObject. 0 (the default Object3D + * baseclass flag) will be returned if subclass determination fails. + */ + static int primitive3DToInt( Primitive3D *inPrimitive ); + + + /** + * Constructs a new, unitialized Primitive3D (in other words, + * an Primitive3D ready for deserialization) of a subclass + * type matching inTypeFlag. + * + * @param inTypeFlag the type flag specifying the class + * of the returned primitive. + * + * @return an (unitialized) Primitive3D class instance with a + * subclass type corresponding to inTypeFlag. If no + * matching class is found, a default Primitive3D baseclass + * instance is returned. Must be destroyed by the caller. + */ + static Primitive3D *intToPrimitive3D( int inTypeFlag ); + + }; + + + +inline int Primitive3DFactory::primitive3DToInt( Primitive3D *inPrimitive ) { + + // use RTTI to determine type of inPrimitive + +#ifdef SUBREAL + if( typeid( *inPrimitive ) == typeid( EntityBodyPrimitive3D ) ) { + return FACTORY_ENTITY_BODY_PRIMITIVE_FLAG; + } + else if( typeid( *inPrimitive ) == typeid( EntityVanePrimitive3D ) ) { + return FACTORY_ENTITY_VANE_PRIMITIVE_FLAG; + } +#endif + + // else return the default flag + return FACTORY_DEFAULT_PRIMITIVE_FLAG; + } + + + +inline Primitive3D *Primitive3DFactory::intToPrimitive3D( int inTypeFlag ) { + switch( inTypeFlag ) { + case FACTORY_DEFAULT_PRIMITIVE_FLAG: + return new Primitive3D(); + break; + + /* these primitives are only defined if + * we are part of subreal + */ +#ifdef SUBREAL + case FACTORY_ENTITY_BODY_PRIMITIVE_FLAG: + return new EntityBodyPrimitive3D(); + break; + case FACTORY_ENTITY_VANE_PRIMITIVE_FLAG: + return new EntityVanePrimitive3D(); + break; +#endif + + default: + // unknown primitive flag type + return new Primitive3D(); + break; + } + + } + + +#endif diff --git a/minorGems/graphics/ChannelFilter.h b/minorGems/graphics/ChannelFilter.h new file mode 100644 index 0000000..cbe05f0 --- /dev/null +++ b/minorGems/graphics/ChannelFilter.h @@ -0,0 +1,33 @@ +/* + * Modification History + * + * 2000-December-21 Jason Rohrer + * Created. + * + * 2006-October-14 Jason Rohrer + * Added virtual destructor. + */ + + +#ifndef CHANNEL_FILTER_INCLUDED +#define CHANNEL_FILTER_INCLUDED + + +/** + * Interface for a class that can filter a 2D channel. + * + * @author Jason Rohrer + */ +class ChannelFilter { + + public: + virtual void apply( double *inChannel, int inWidth, int inHeight ) = 0; + + + // ensure proper destruction of subclasses + virtual ~ChannelFilter() { + } + + }; + +#endif diff --git a/minorGems/graphics/Color.h b/minorGems/graphics/Color.h new file mode 100644 index 0000000..f65c9b9 --- /dev/null +++ b/minorGems/graphics/Color.h @@ -0,0 +1,557 @@ +// Jason Rohrer +// Color.h + +/** +* +* Color object for RadiosGL +* +* Float components in RGB (0..1) +* Color of emitted energy can be greater than 1 +* +* +* Created 9-5-99 +* Mods: +* Jason Rohrer 11-6-99 Added 32-bit composite member +* Jason Rohrer 11-13-99 Added printing functionality +* Jason Rohrer 11-15-99 Added rebuildComposite function +* Added weightColor function +* Added getMax function +* Jason Rohrer 2004-June-12 Fixed a bug in copy function. +* Jason Rohrer 2004-June-12 Added a linear sum function. +* +* Jason Rohrer 2004-August-12 Optimized Color constructor. +* Jason Rohrer 2004-August-27 Added an equality test. +* Jason Rohrer 2005-February-4 Added weighting and setValue functions. +* Jason Rohrer 2005-February-10 Added invert and saturate functions. +* Jason Rohrer 2005-February-11 Added HSV conversion function. +* Jason Rohrer 2005-April-5 Disabled default building of composite. +* Jason Rohrer 2006-August-29 Changed alpha default to 1. +* Jason Rohrer 2006-October-3 Disabled building composite in +* default Color() constuctor. +* Jason Rohrer 2008-October-23 Added alpha to print(), and set alpha +* to 1 in default constructor. +* Jason Rohrer 2010-April-3 Added reverse HSV function. +*/ + +#ifndef COLOR_INCLUDED +#define COLOR_INCLUDED + +#include +#include + +class Color { + public : + /** + * Constructs a color. + * + * @param red, green, blue the components of the color, each in [0,1]. + * @param alpha the alpha value in [0,1]. Defaults to 1. + * @param inBuildComposite set to true to build the composite + * upon construction, or false to skip building composite (faster). + * Defaults to false. + */ + Color(float red, float green, float blue, float alpha=1, + char inBuildComposite=false ); + + + + /** + * Constructs a all-zero (except alpha set to 1) color with no + * composite built. + */ + Color(); + + + + /** + * Constructs an rgb color from HSV components. + * + * @param inHue, inSaturation, inValue the HSV components of the + * color, each in [0,1]. + * @param alpha the alpha value in [0,1]. Defaults to 1. + * @param inBuildComposite set to true to build the composite + * upon construction, or false to skip building composite (faster). + * Defaults to true. + * + * @return an RGBA color equivalent to the HSV color. + * Must be destroyed by caller. + */ + static Color *makeColorFromHSV( + float inHue, float inSaturation, float inValue, + float inAlpha=1, char inBuildComposite=false ); + + + /** + * Makes HSV values (each in range 0..1) from this RGB color. + * + * @param outH, outS, outV pointers to where values should be returned. + */ + void makeHSV( float *outH, float *outS, float *outV ); + + + + float r, g, b, a; + + char mCompositeBuilt; + unsigned long composite; // 32-bit composite color + + + Color *copy(); // make a copy of this color + + + + /** + * Sets the RGBA values of this color. + * + * @param red, green, blue, alpha the values to set. + * Alpha defaults to 0. + */ + void setValues( float red, float green, float blue, float alpha=1 ); + + + + /** + * Sets the RGBA values of this color using the values from + * another color. + * + * @param inOtherColor the color to copy values from. + * Must be destroyed by caller. + */ + void setValues( Color *inOtherColor ); + + + + /** + * Tests whether this color is equal to another color. + * + * @param inOtherColor the other color. + * Must be destroyed by caller. + * + * @return true if they are equal, or false otherwise. + */ + char equals( Color *inOtherColor ); + + + void print(); + + + /** + * Computes the linear weighted sum of two colors. + * + * @param inFirst the first color. + * @param inSecond the second color. + * @param inFirstWeight the weight given to the first color in the + * sum. The second color is weighted (1-inFirstWeight). + * + * @return the sum color. Must be destroyed by caller. + */ + static Color *linearSum( Color *inFirst, Color *inSecond, + float inFirstWeight ); + + + // after adjusting the r, g, b, a values exterally + // call this to remake the composite unsigned long + unsigned long rebuildComposite(); + + // get largest component of R,G,B + float getMax(); + + // alter color data by multiplying by weight + void weightColor( float weight ); + + + + /** + * Alters color data by multiplying by a weight color. + * + * @param inWeightColor the color to multiply this color by. + * Must be destroyed by caller. + */ + void weightColor( Color *inWeightColor ); + + + + /** + * Inverts this color. + * + * Ignores alpha channel. + */ + void invert(); + + + + /** + * Saturates this color, ensuring that at most 2 components are + * non-zero. + * + * Ignores alpha channel. + */ + void saturate(); + + + + // get component by component weighted 32-bit composite + // (returns alpha unweighted) + unsigned long getWeightedComposite( float weight ); // from this color + unsigned long getWeightedComposite(unsigned long c1, float weight ); // from composite + + unsigned long sumComposite(unsigned long c1, unsigned long c2); + + // access this color as a three vector + float &operator[](int rgbIndex); + + }; + + +inline Color::Color() { + r = 0; + g = 0; + b = 0; + a = 1; + + mCompositeBuilt = false; + composite = 0; + } + + + +inline Color::Color(float red, float green, float blue, float alpha, + char inBuildComposite ) { + r = red; + g = green; + b = blue; + a = alpha; + + if( inBuildComposite ) { + rebuildComposite(); + } + else { + composite = 0; + mCompositeBuilt = false; + } + } + + + +inline Color *Color::makeColorFromHSV( + float inHue, float inSaturation, float inValue, + float inAlpha, char inBuildComposite ) { + + // based on pseudocode from http://www.easyrgb.com/math.php + float r, g, b; + + if ( inSaturation == 0 ) { + r = inValue; + g = inValue; + b = inValue; + } + else { + float var_h = inHue * 6; + + // H must be < 1 + if( var_h == 6 ) { + var_h = 0; + } + + float var_i = int( var_h ); // Or var_i = floor( var_h ) + float var_1 = inValue * ( 1 - inSaturation ); + float var_2 = inValue * ( 1 - inSaturation * ( var_h - var_i ) ); + float var_3 = + inValue * ( 1 - inSaturation * ( 1 - ( var_h - var_i ) ) ); + + if( var_i == 0 ) { + r = inValue; + g = var_3; + b = var_1; + } + else if( var_i == 1 ) { + r = var_2; + g = inValue; + b = var_1; + } + else if( var_i == 2 ) { + r = var_1; + g = inValue; + b = var_3; + } + else if( var_i == 3 ) { + r = var_1; + g = var_2; + b = inValue; + } + else if( var_i == 4 ) { + r = var_3; + g = var_1; + b = inValue; + } + else { + r = inValue; + g = var_1; + b = var_2; + } + } + + return new Color( r, g, b, inAlpha, inBuildComposite ); + } + + + +inline void Color::makeHSV( float *outH, float *outS, float *outV ) { + + // based on pseudocode from http://www.easyrgb.com/math.php + + //Min. value of RGB + float var_Min = r; + if( g < var_Min ) { + var_Min = g; + } + if( b < var_Min ) { + var_Min = b; + } + + //Max. value of RGB + float var_Max = r; + if( g > var_Max ) { + var_Max = g; + } + if( b > var_Max ) { + var_Max = b; + } + + //Delta RGB value + float del_Max = var_Max - var_Min; + + + //HSV results from 0 to 1 + float H; + float S; + float V = var_Max; + + if ( del_Max == 0 ) { + //This is a gray, no chroma... + H = 0; + S = 0; + } + else { + //Chromatic data... + S = del_Max / var_Max; + + + float del_R = + ( ( ( var_Max - r ) / 6 ) + ( del_Max / 2 ) ) / del_Max; + + float del_G = + ( ( ( var_Max - g ) / 6 ) + ( del_Max / 2 ) ) / del_Max; + + float del_B = + ( ( ( var_Max - b ) / 6 ) + ( del_Max / 2 ) ) / del_Max; + + + if( r == var_Max ) { + H = del_B - del_G; + } + else if( g == var_Max ) { + H = ( 1.0f / 3.0f ) + del_R - del_B; + } + else if ( b == var_Max ) { + H = ( 2.0f / 3.0f ) + del_G - del_R; + } + + + if( H < 0 ) { + H += 1; + } + + if( H > 1 ) { + H -= 1; + } + } + + + *outH = H; + *outS = S; + *outV = V; + } + + + + +inline Color *Color::copy() { + Color *copyColor = new Color(r,g,b,a, mCompositeBuilt ); + return copyColor; + } + + + +inline void Color::setValues( float red, float green, + float blue, float alpha ) { + r = red; + g = green; + b = blue; + a = alpha; + + if( mCompositeBuilt ) { + rebuildComposite(); + } + } + + + +inline void Color::setValues( Color *inOtherColor ) { + setValues( inOtherColor->r, + inOtherColor->g, + inOtherColor->b, + inOtherColor->a ); + } + + + +inline char Color::equals( Color *inOtherColor ) { + if( r == inOtherColor->r && + g == inOtherColor->g && + b == inOtherColor->b && + a == inOtherColor->a ) { + return true; + } + else { + return false; + } + } + +inline void Color::print() { + printf( "(%f, %f, %f, %f)", r, g, b, a ); + } + + + +inline Color *Color::linearSum( Color *inFirst, Color *inSecond, + float inFirstWeight ) { + + float secondWeight = 1 - inFirstWeight; + float r = inFirstWeight * inFirst->r + secondWeight * inSecond->r; + float g = inFirstWeight * inFirst->g + secondWeight * inSecond->g; + float b = inFirstWeight * inFirst->b + secondWeight * inSecond->b; + float a = inFirstWeight * inFirst->a + secondWeight * inSecond->a; + + return new Color( r, g, b, a, + inFirst->mCompositeBuilt || inSecond->mCompositeBuilt ); + } + + + +inline unsigned long Color::rebuildComposite() { + composite = ((int)(b*255)) | ((int)(g*255)) << 8 | ((int)(r*255)) << 16 | + ((int)(a*255)) << 24; + mCompositeBuilt = true; + return composite; + } + +inline float Color::getMax() { + float max = -FLT_MAX; + if( r > max ) max = r; + if( g > max ) max = g; + if( b > max ) max = b; + return max; + } + + + +inline void Color::weightColor( float weight ) { + r = r * weight; + g = g * weight; + b = b * weight; + // for now, don't touch alpha + + if( mCompositeBuilt ) { + rebuildComposite(); + } + } + + + +inline void Color::invert() { + r = 1 - r; + g = 1 - g; + b = 1 - b; + } + + + +inline void Color::saturate() { + if( r < g && r < b ) { + r = 0; + } + else if( g < r && g < b ) { + g = 0; + } + else if( b < r && b < g ) { + b = 0; + } + else if( r != 0 ) { + // they are all equal, but non-zero + + // default to dropping red + r = 0; + } + //else + // they are all 0 + // leave as black + } + + + +inline void Color::weightColor( Color *inWeightColor ) { + r *= inWeightColor->r; + g *= inWeightColor->g; + b *= inWeightColor->b; + a *= inWeightColor->a; + + if( mCompositeBuilt ) { + rebuildComposite(); + } + } + + + +inline float &Color::operator[](int rgbIndex) { + if( rgbIndex == 0) return r; + else if( rgbIndex == 1) return g; + else if( rgbIndex == 2) return b; + else if( rgbIndex == 3) return a; + return r; // default, return r reference + } + + +inline unsigned long Color::getWeightedComposite( float weight ) { + return ((int)(b*255*weight)) | ((int)(g*255*weight)) << 8 | ((int)(r*255*weight)) << 16 | ((int)(a*255)) << 24; + } + + +inline unsigned long Color::getWeightedComposite( unsigned long c1, float weight ) { + int b = c1 & 0xFF; + + int g = (c1 >> 8) & 0xFF; + + int r = (c1 >> 16) & 0xFF; + + int a = c1 >> 24; + + return ((int)(b*weight)) | (((int)(g*weight)) << 8) | (((int)(r*weight)) << 16) | (((int)(a*weight)) << 24); + } + +inline unsigned long Color::sumComposite(unsigned long c1, unsigned long c2) { + int b = (c1 & 0xFF) + (c2 & 0xFF); + if( b > 255) b = 255; + + int g = ((c1 >> 8) & 0xFF) + ((c2 >> 8) & 0xFF); + if( g > 255) g = 255; + + int r = ((c1 >> 16) & 0xFF) + ((c2 >> 16) & 0xFF); + if( r > 255) r = 255; + + int a = (c1 >> 24) + (c2 >> 24); + if( a > 255) a = 255; + + return b | (g << 8) | (r << 16) | (a << 24); + } +#endif diff --git a/minorGems/graphics/GraphicBuffer.h b/minorGems/graphics/GraphicBuffer.h new file mode 100644 index 0000000..99d8b64 --- /dev/null +++ b/minorGems/graphics/GraphicBuffer.h @@ -0,0 +1,258 @@ +// Jason Rohrer +// GraphicBufffer.h + +/** +* +* Graphic buffer, with functions for manipulation +* +* +* Created 11-8-99 +* Mods: +* Jason Rohrer 12-12-99 Added support for drawing IconMap objects +* Jason Rohrer 11-18-2000 Added a getBuffer function. +*/ + + + +#ifndef GRAPHIC_BUFFER_INCLUDED +#define GRAPHIC_BUFFER_INCLUDED + + +#include "Color.h" +#include +#include "IconMap.h" + +class GraphicBuffer { + + + public: + GraphicBuffer( unsigned long *buff, int buffW, int buffH ); + ~GraphicBuffer(); + + // draw a solid image into the buffer + void drawImage( unsigned long *image, int *imageYOffset, int xPos, int yPos, int wide, int high ); + + // draw a transparent image into the buffer + void drawImageAlpha( unsigned long *image, int *imageYOffset, int xPos, int yPos, int wide, int high ); + + // replace a rectangle in the buffer with bgColor + void eraseImage( int xPos, int yPos, int wide, int high, Color &bgColor ); + + + // draw a solid IconMap into the buffer + void drawIconMap( IconMap *icon, int xPos, int yPos ); + + // draw a transparent image into the buffer + void drawIconMapAlpha( IconMap *icon, int xPos, int yPos ); + + // replace a rectangle (over icon) in the buffer with bgColor + void eraseIconMap( IconMap *icon, int xPos, int yPos, Color &bgColor ); + + + + // set buffer to a new buffer of the same size + void setBuffer( unsigned long *buff ); + + // get the pixel buffer + unsigned long *getBuffer(); + + int getWidth(); + int getHeight(); + + // fill buffer with a color + void fill( Color &fillC ); + + // take a screen shot, save to disc + void screenShot( FILE *f ); + + private: + unsigned long *buffer; + int bufferHigh; + int bufferWide; + + int *buffYOffset; + + float invChannelMax; + Color utilColor; // color for using composite functions + + }; + + +inline GraphicBuffer::GraphicBuffer( unsigned long *buff, int buffW, int buffH) : utilColor(0,0,0,0) { + + invChannelMax = 1 / 255.0; + + bufferHigh = buffH; + bufferWide = buffW; + + buffer = buff; + + // precalc'ed y contributions to linear indexing of buffer + buffYOffset = new int[bufferHigh]; + for( int y=0; y bufferHigh ) { + maxY = bufferHigh; + } + int minX = xPos; + if( minX < 0 ) { + minX = 0; + } + int maxX = xPos + wide; + if( maxX > bufferWide ) { + maxX = bufferWide; + } + + for( int y=minY; y bufferHigh ) { + maxY = bufferHigh; + } + int minX = xPos; + if( minX < 0 ) { + minX = 0; + } + int maxX = xPos + wide; + if( maxX > bufferWide ) { + maxX = bufferWide; + } + + unsigned long composite = bgColor.composite; + + for( int y=minY; y bufferHigh ) { + maxY = bufferHigh; + } + int minX = xPos; + if( minX < 0 ) { + minX = 0; + } + int maxX = xPos + wide; + if( maxX > bufferWide ) { + maxX = bufferWide; + } + + for( int y=minY; y> 24) * invChannelMax; + + float oneMinusAlpha = 1-alpha; + + unsigned long buffARGB = buffer[ buffYContrib + x ]; + + argb = utilColor.getWeightedComposite( argb, alpha ); + buffARGB = utilColor.getWeightedComposite( buffARGB, oneMinusAlpha ); + + unsigned long sum = utilColor.sumComposite( argb, buffARGB ); + + buffer[ buffYContrib + x ] = sum; + } + } + } + + +inline void GraphicBuffer::drawIconMap( IconMap *icon, int xPos, int yPos ) { + drawImage( icon->imageMap, icon->yOffset, xPos, yPos, icon->wide, icon->high ); + } + + +inline void GraphicBuffer::drawIconMapAlpha( IconMap *icon, int xPos, int yPos ) { + drawImageAlpha( icon->imageMap, icon->yOffset, xPos, yPos, icon->wide, icon->high ); + } + + +inline void GraphicBuffer::eraseIconMap( IconMap *icon, int xPos, int yPos, Color &bgColor ) { + eraseImage( xPos, yPos, icon->wide, icon->high, bgColor ); + } + + +inline void GraphicBuffer::fill( Color &fillC ) { + unsigned long composite = fillC.composite; + for( int y=0; y +#include "Color.h" + +class IconMap { + + + public: + + IconMap( int w, int h); // construct a map of a certain width and height + + IconMap( int w, int h, int *offset ); + // pass in precomputed y offsets into image map + + ~IconMap(); + + int wide; + int high; + int numPixels; + + int *yOffset; + + unsigned long *imageMap; + + + // draw a solid IconMap into this IconMap + void drawIconMap( IconMap *icon, int xPos, int yPos ); + + // draw a transparent IconMap into this IconMap + void drawIconMapAlpha( IconMap *icon, int xPos, int yPos ); + + void copy( IconMap *icon ); // copy contents of another icon map into this one + // does nothing if icon maps aren't the same size + + private: + char yOffsetExternal; // is the yOffset ptr external? + + float invChannelMax; + Color utilColor; + + }; + + +inline IconMap::IconMap( int w, int h ) { + invChannelMax = 1 / 255.0; + + wide = w; + high = h; + numPixels = wide * high; + + imageMap = new unsigned long[ wide * high ]; + + yOffset = new int[high]; + for( int y=0; yimageMap; + int *imageYOffset = icon->yOffset; + int imageWide = icon->wide; + int imageHigh = icon->high; + + // watch for buffer bounds + int minY = yPos; + if( minY < 0 ) { + minY = 0; + } + int maxY = yPos + imageHigh; + if( maxY > high ) { + maxY = high; + } + int minX = xPos; + if( minX < 0 ) { + minX = 0; + } + int maxX = xPos + imageWide; + if( maxX > wide ) { + maxX = wide; + } + + for( int y=minY; yimageMap; + int *imageYOffset = icon->yOffset; + int imageWide = icon->wide; + int imageHigh = icon->high; + + // watch for buffer bounds + int minY = yPos; + if( minY < 0 ) { + minY = 0; + } + int maxY = yPos + imageHigh; + if( maxY > high ) { + maxY = high; + } + int minX = xPos; + if( minX < 0 ) { + minX = 0; + } + int maxX = xPos + imageWide; + if( maxX > wide ) { + maxX = wide; + } + + for( int y=minY; y> 24) * invChannelMax; + + float oneMinusAlpha = 1-alpha; + + unsigned long buffARGB = imageMap[ buffYContrib + x ]; + + argb = utilColor.getWeightedComposite( argb, alpha ); + buffARGB = utilColor.getWeightedComposite( buffARGB, oneMinusAlpha ); + + unsigned long sum = utilColor.sumComposite( argb, buffARGB ); + + imageMap[ buffYContrib + x ] = sum; + } + } + } + + +inline void IconMap::copy( IconMap *icon ) { + // make sure they are the same size + if( numPixels != icon->numPixels ) { + return; + } + + // void * memcpy (void * dst, const void * src, size_t len); + + // each pixel is 4 bytes, so shift numPixels by 2 + memcpy( (void *)(imageMap), (void *)(icon->imageMap), numPixels << 2 ); + } + +#endif + diff --git a/minorGems/graphics/Image.h b/minorGems/graphics/Image.h new file mode 100644 index 0000000..633179c --- /dev/null +++ b/minorGems/graphics/Image.h @@ -0,0 +1,639 @@ +/* + * Modification History + * + * 2000-December-21 Jason Rohrer + * Created. + * + * 2001-January-6 Jason Rohrer + * Fixed a bug in filter( ChannelFilter * ). + * Set mSelection to NULL by default. + * + * 2001-January-10 Jason Rohrer + * Made class serializable. + * + * 2001-January-15 Jason Rohrer + * Made copy() not-virtual, so it can be overridden by subclasses + * while allowing pointer type to determine which function + * implementation is invoked. + * + * 2001-January-31 Jason Rohrer + * Fixed a bug in copy(). + * + * 2001-February-3 Jason Rohrer + * Updated serialization code to use new interfaces. + * + * 2001-February-4 Jason Rohrer + * Rewrote the serialization code to send the image across as a byte + * array with one byte per channel. This will reduce the transfer size by + * a factor of 8. Keeping images in double format is convennient for image + * creation, but the added quality never affects the end user anyway, so + * there's no point in sending the extra data to a stream. + * Removed an unused array allocation. + * + * 2005-February-21 Jason Rohrer + * Made destructor virtual to avoid compiler warnings. + * + * 2006-August-25 Jason Rohrer + * Made zero init of pixels optional (for speed). + * + * 2008-September-25 Jason Rohrer + * Added a sub-image function and setting/getting color functions. + */ + + +#ifndef IMAGE_INCLUDED +#define IMAGE_INCLUDED + +#include +#include + +#include "ChannelFilter.h" +#include "Color.h" + +#include "minorGems/io/Serializable.h" + + +/** + * A multi-channel, double-valued image. + * + * Is Serializable. Note that a serialized image doesn't have a selection. + * + * @author Jason Rohrer + */ +class Image : public Serializable { + + public: + + /** + * Constructs an image. + * + * @param inWidth width of image in pixels. + * @param inHeight height of image in pixels. + * @param inNumChannels number of channels in image. + * @param inStartPixelsAtZero true to initialize all pixels + * to zero, or false to leave default memory values (garbage) + * in place (pixels must be initialized by caller in this case). + * Defaults to true. + */ + Image( int inWidth, int inHeight, int inNumChannels, + char inStartPixelsAtZero = true ); + + + virtual ~Image(); + + + // gets the dimensions of this image. + virtual long getWidth(); + virtual long getHeight(); + virtual long getNumChannels(); + + /** + * Gets the values of a particular channel. + * + * Values are not copied. + * + * @param inChannel the channel to get. + * + * @return the values of the specified channel in row-major order. + */ + virtual double *getChannel( int inChannel ); + + + + /** + * Gets the 3- or 4-channel color value at a given location in the + * image. + * + * @param inIndex the image index. + * + * @return a color object. + */ + virtual Color getColor( int inIndex ); + + + + /** + * Sets the 3- or 4-channel color value at a given location in the + * image. + * + * @param inIndex the image index. + * @param inColor the new color to set. + */ + virtual void setColor( int inIndex, Color inColor ); + + + + /** + * Selects a region of the image. Default is a clear selection, + * which means all regions of image are affected by an applied + * filter. + * + * @param inSelection the image to use as the selection mask. + * Values of 0 indicate pixels that are not selection, and 1 + * indicate pixels that are selected, with selection amount + * varying linearly between 0 and 1. + * If inSelection is a single channel, then that channel is + * used as a selection mask for all channels in this image. + * If inSelection contains the same number of channels as this + * image, then the corresponding channels of inSelection are + * are used to mask each channel of this image. + * If inSelection contains a number of channels different + * from the number in this image, the first channel of inSelection + * is used to mask each channel in this image. + * + * Note that inSelection is not copied or destroyed by this class. + * Thus, modifying inSelection after calling setSelection will + * modify the selection in this image. + */ + virtual void setSelection( Image *inSelection ); + + + + /** + * Gets the selection for this image. + * + * @return the selection for this image. Returns NULL + * if there is no selection. Must not be destroyed + * by caller before calling clearSelection. + */ + virtual Image *getSelection(); + + + + /** + * Clears the selection. Effectively selects the entire image. + */ + virtual void clearSelection(); + + + /** + * Applies a filter to the selected region of + * a specified channel of this image. + * + * @param inFilter the filter to apply. + * @param inChannel the channel to filter. + */ + virtual void filter( ChannelFilter *inFilter, int inChannel ); + + + /** + * Applies a filter to the selected region of + * all channels of this image. + * + * @param inFilter the filter to apply. + */ + virtual void filter( ChannelFilter *inFilter ); + + + /** + * Copies the selected region of this image. Not virtual, + * so can be overridden by subclasses while allowing pointer + * type to determine which function implementation is invoked. + * + * @return a new image with the same number of channels + * as this image, each containing the selected region + * from each corresponding channel of this image. Unselected + * regions are set to black. Returned image has no selection. + */ + Image *copy(); + + + /** + * Pastes the selected region from another image into + * the selected region of this image. + * + * @param inImage the image to paste. Let c be the number + * of channels in this image, and cp be the number + * of channels in the image being pasted. + * If ccp, then only the first cp channels + * of this image are pasted into. + */ + virtual void paste( Image *inImage ); + + + /** + * Copies the data from the selected region of a channel. + * + * @param inChannel the channel to copy. + * + * @return a copy of the channel data. Must be destroyed + * by the caller. + */ + virtual double *copyChannel( int inChannel ); + + + + /** + * Pastes channel data into the selected region of a specified channel. + * + * @param inChannelData an array containing the channel + * data to be pasted. Must be destroyed by caller. + * @param inChannel the channel to paste into. + */ + virtual void pasteChannel( double *inChannelData, int inChannel ); + + + /** + * Gets the mask for a specified channel. + * + * @param inChannel the channel to get a mask for. + * + * @return the mask data for the specified channel. + * If selection has the same number of channels as this image + * then a different mask is returned for each channel. Otherwise, + * the first channel from the selection is returned as the + * mask for every channel. Returns NULL if there is no selection. + */ + virtual double *getChannelSelection( int inChannel ); + + + + /** + * Extracts a smaller sub-image from this image. + * + * Ignores current selection. + * + * @param inStartX, inStartY, inWidth, inHeight + * coordinates for the top left corner pixel of the sub-image + * and the width and height of the sub-image. + * + * @return the sub-image as a new image. Must be destoryed by caller. + */ + Image *getSubImage( int inStartX, int inStartY, + int inWidth, int inHeight ); + + + + // implement the Serializable interface + virtual int serialize( OutputStream *inOutputStream ); + virtual int deserialize( InputStream *inInputStream ); + + protected: + long mWide, mHigh, mNumPixels, mNumChannels; + + double **mChannels; + + // NULL if nothing selected. + Image *mSelection; + + + + + + /** + * Pastes masked channel data into the selected region of a + * specified channel. + * + * @param inChannelData an array containing the channel + * data to be pasted. Must be destroyed by caller. + * @param inMask the selection mask to use for passed-in channel. + * Set to NULL for no mask. + * @param inChannel the channel to paste into. + */ + virtual void pasteChannel( double *inChannelData, double *inMask, + int inChannel ); + + }; + + + +inline Image::Image( int inWidth, int inHeight, int inNumChannels, + char inStartPixelsAtZero ) + : mWide( inWidth ), mHigh( inHeight ), mNumPixels( inWidth * inHeight ), + mNumChannels( inNumChannels ), mChannels( new double*[inNumChannels] ), + mSelection( NULL ) { + + // initialize all channels + for( int i=0; iapply( mChannels[ inChannel ], mWide, mHigh ); + } + else { // part of image selected + // turn selection off and filter channel entirely + Image *tempSelection = mSelection; + mSelection = NULL; + // filter a copy of the channel + double *filteredChannel = copyChannel( inChannel ); + inFilter->apply( filteredChannel, mWide, mHigh ); + + // now paste filtered channel back into selected region + mSelection = tempSelection; + pasteChannel( filteredChannel, inChannel ); + } + } + + + +inline void Image::filter( ChannelFilter *inFilter ) { + for( int i=0; ipaste( this ); + + return copiedImage; + } + + + +inline void Image::paste( Image *inImage ) { + // copy paste in the min number of channels only + int numChannelsToPaste = mNumChannels; + if( numChannelsToPaste > inImage->getNumChannels() ) { + numChannelsToPaste = inImage->getNumChannels(); + } + + for( int i=0; igetChannel(i), + inImage->getChannelSelection(i), i ); + } + } + + + +inline double *Image::copyChannel( int inChannel ) { + + // first, copy the channel + double *copiedChannel = new double[mNumPixels]; + memcpy( copiedChannel, + mChannels[inChannel], sizeof( double ) * mNumPixels ); + + + if( mSelection != NULL ) { + // apply selection to copied channel + double *selection = getChannelSelection( inChannel ); + + // scale copied channel with selection + for( int i=0; igetNumChannels() == mNumChannels ) { + // use separate selection for each channel + return mSelection->getChannel( inChannel ); + } + else { + // use first channel of selection for all channels + return mSelection->getChannel( 0 ); + } + } + } + + + +inline void Image::pasteChannel( double *inChannelData, int inChannel ) { + pasteChannel( inChannelData, NULL, inChannel ); + } + + + +// We've abstracted away the complexity in the other fuctions, +// but it all seemed to filter down into this function, which +// is very messy. +inline void Image::pasteChannel( double *inChannelData, double *inMask, + int inChannel ) { + + double *thisChannel = mChannels[inChannel]; + if( mSelection != NULL ) { + // scale incoming data with this selection + double *selection = getChannelSelection(inChannel); + + if( inMask != NULL ) { + // scale incoming data with both masks + for( int i=0; igetChannel( c ); + double *sourceChannel = mChannels[c]; + + int destY=0; + for( int y=inStartY; ywriteLong( mWide ); + numBytes += inOutputStream->writeLong( mHigh ); + + // then output number of channels + numBytes += inOutputStream->writeLong( mNumChannels ); + + // now output each channel + for( int i=0; iwriteDouble( mChannels[i][p] ); + byteArray[p] = (unsigned char)( mChannels[i][p] * 255 ); + } + + numBytes += inOutputStream->write( byteArray, mNumPixels ); + + delete [] byteArray; + } + + return numBytes; + } + + + +inline int Image::deserialize( InputStream *inInputStream ) { + int i; + // first delete old image channels + for( i=0; ireadLong( &mWide ); + numBytes += inInputStream->readLong( &mHigh ); + + mNumPixels = mWide * mHigh; + + // then input number of channels + numBytes += inInputStream->readLong( &mNumChannels ); + + mChannels = new double*[mNumChannels]; + + // now input each channel + for( i=0; iread( byteArray, mNumPixels ); + + // convert each byte to an 8-bit double pixel + for( int p=0; preadDouble( &( mChannels[i][p] ) ); + mChannels[i][p] = (double)( byteArray[p] ) / 255.0; + } + + delete [] byteArray; + } + + return numBytes; + } + + + +#endif diff --git a/minorGems/graphics/ImageColorConverter.h b/minorGems/graphics/ImageColorConverter.h new file mode 100644 index 0000000..7ea8f47 --- /dev/null +++ b/minorGems/graphics/ImageColorConverter.h @@ -0,0 +1,573 @@ +/* + * Modification History + * + * 2001-February-26 Jason Rohrer + * Created. + * Added functions for converting to and from gray byte arrays. + * + * 2001-September-19 Jason Rohrer + * Added an RGB->HSB conversion function, which was copied + * from minorGems/ai/robotics/ImageStatistics. + * Added RGB<->YIQ functions. + * + * 2001-September-20 Jason Rohrer + * Fixed a bug in the YIQ conversion. + * Got rid of this bug fix, as it distorts the YIQ space, + * and there is no way to prevent colors from going out of the + * [0,1] range all of the time anyway. + * + * 2001-September-24 Jason Rohrer + * Added RGB<->YCbCr functions. + * Abstracted out a common coefficient multiplying function. + */ + +#ifndef IMAGE_COLOR_CONVERTER_INCLUDED +#define IMAGE_COLOR_CONVERTER_INCLUDED + +#include "Image.h" + + +/** + * A container class for static functions that convert + * images between various color spaces. + * + * @author Jason Rohrer + */ +class ImageColorConverter { + + public: + + /** + * Converts a 3-channel RGB image to a 1-channel grayscale + * image using an NTSC luminosity standard. + * + * @param inImage the RGB image to convert. Must be destroyed + * by caller. + * + * @return a new, grayscale version of inImage. Must be + * destroyed by the caller. Returns NULL if inImage + * is not a 3-channel image. + */ + static Image *RGBToGrayscale( Image *inImage ); + + /** + * Converts a 1-channel grayscae image to a 3-channel RGB + * image. + * + * @param inImage the grayscale image to convert. Must be destroyed + * by caller. + * + * @return a new, RGB version of inImage. Must be + * destroyed by the caller. Returns NULL if inImage + * is not a 1-channel image. + */ + static Image *grayscaleToRGB( Image *inImage ); + + + /** + * Converts a 1-channel grayscae image to a 1-channel byte array. + * + * @param inImage the grayscale image to convert. Must be destroyed + * by caller. + * @param inChannelNumber the channel number to use as the + * gray channel. Defaults to 0; + * + * @return a new byte array with one byte per image pixel, + * and image [0,1] values mapped to [0,255]. + */ + static unsigned char *grayscaleToByteArray( Image *inImage, + int inChannelNumber = 0 ); + + /** + * Converts a byte array to a 1-channel grayscale + * image. + * + * @param inBytes the byte array to convert. Must be destroyed + * by caller. + * @param inWidth the width of the image contained in the byte array. + * @param inHeight the height of the image contained in the byte array. + * + * @return a new, grayscale image version of the byte array. Must be + * destroyed by the caller. + */ + static Image *byteArrayToGrayscale( unsigned char *inBytes, + int inWidth, int inHeight ); + + + + /** + * Converts an RGB image to HSB. + * + * @param inRGBImage the rgb image to convert. + * Must be destroyed by caller. + * + * @return a new image that is the HSB conversion of the + * RGB image. Must be destroyed by caller. + */ + static Image *RGBToHSB( Image *inRGBImage ); + + + + /** + * Converts an RGB image to YIQ. + * + * Note that color values in the resulting YIQ + * image may lie outside of the range [0,1]. + * + * @param inRGBImage the rgb image to convert. + * Must be destroyed by caller. + * + * @return a new image that is the YIQ conversion of the + * RGB image. Must be destroyed by caller. + */ + static Image *RGBToYIQ( Image *inRGBImage ); + + + + /** + * Converts a YIQ image to RGB. + * + * + * Note that color values in the resulting RGB + * image may lie outside of the range [0,1]. + * + * @param inYIQImage the rgb image to convert. + * Must be destroyed by caller. + * + * @return a new image that is the RGB conversion of the + * YIQ image. Must be destroyed by caller. + */ + static Image *YIQToRGB( Image *inYIQImage ); + + + + /** + * Converts an RGB image to YCbCr. + * + * Note that in the YCbCr standard, Y is in the range + * [0,1], while Cb and Cr are both in the range [-0.5, 0.5]. + * This function returns Cb and Cr components shifted + * into the range [0,1]. + * + * @param inRGBImage the rgb image to convert. + * Must be destroyed by caller. + * + * @return a new image that is the YCbCr conversion of the + * RGB image. Must be destroyed by caller. + */ + static Image *RGBToYCbCr( Image *inRGBImage ); + + + + /** + * Converts a YCbCr image to RGB. + * + * + * Note that in the YCbCr standard, Y is in the range + * [0,1], while Cb and Cr are both in the range [-0.5, 0.5]. + * This function expects input Cb and Cr components to be shifted + * into the range [0,1]. + * + * @param inYCbCrImage the rgb image to convert. + * Must be destroyed by caller. + * + * @return a new image that is the RGB conversion of the + * YCbCr image. Must be destroyed by caller. + */ + static Image *YCbCrToRGB( Image *inYCbCrImage ); + + + + protected: + + /** + * Converts an 3-channel image to another 3-channel + * image using a matrix of conversion coefficients. + * + * The following formulae are used; + * outChan0 = inC00 * inChan0 + inC01 * inChan1 + inC02 * inChan2 + * outChan1 = inC10 * inChan0 + inC11 * inChan1 + inC12 * inChan2 + * outChan2 = inC20 * inChan0 + inC21 * inChan1 + inC22 * inChan2 + * + * @param inImage the image to convert. + * Must be destroyed by caller. + * + * @return a new image that is inImage converted, or NULL if + * conversion failed (usually because inImage does not + * contain 3 channels). + * Must be destroyed by caller. + */ + static Image *coefficientConvert( Image *inImage, + double inC00, + double inC01, + double inC02, + double inC10, + double inC11, + double inC12, + double inC20, + double inC21, + double inC22 ); + + }; + + + +inline Image *ImageColorConverter::RGBToGrayscale( Image *inImage ) { + int w = inImage->getWidth(); + int h = inImage->getHeight(); + int numPixels = w * h; + + Image *grayImage = new Image( w, h, 1 ); + + double *red = inImage->getChannel( 0 ); + double *green = inImage->getChannel( 1 ); + double *blue = inImage->getChannel( 2 ); + + double *gray = grayImage->getChannel( 0 ); + + for( int i=0; igetWidth(); + int h = inImage->getHeight(); + int numPixels = w * h; + + Image *rgbImage = new Image( w, h, 3 ); + + double *red = rgbImage->getChannel( 0 ); + double *green = rgbImage->getChannel( 1 ); + double *blue = rgbImage->getChannel( 2 ); + + double *gray = inImage->getChannel( 0 ); + + for( int i=0; igetWidth(); + int h = inImage->getHeight(); + int numPixels = w * h; + + unsigned char *bytes = new unsigned char[ numPixels ]; + + double *gray = inImage->getChannel( inChannelNumber ); + + for( int i=0; igetChannel( 0 ); + + for( int i=0; igetNumChannels() != 3 ) { + printf( + "RGBtoHSB requires a 3-channel image as input.\n" ); + return NULL; + } + + int w = inRGBImage->getWidth(); + int h = inRGBImage->getHeight(); + + int numPixels = w * h; + + Image *hsbImage = new Image( w, h, 3 ); + + double *redChannel = inRGBImage->getChannel( 0 ); + double *greenChannel = inRGBImage->getChannel( 1 ); + double *blueChannel = inRGBImage->getChannel( 2 ); + + double *hueChannel = hsbImage->getChannel( 0 ); + double *satChannel = hsbImage->getChannel( 1 ); + double *brightChannel = hsbImage->getChannel( 2 ); + + + for( int i=0; i g) ? r : g; + if (b > cmax) { + cmax = b; + } + + int cmin = (r < g) ? r : g; + if (b < cmin) { + cmin = b; + } + + bright = ( (double)cmax ) / 255.0; + if( cmax != 0 ) { + sat = ( (double)( cmax - cmin ) ) / ( (double) cmax ); + } + else { + sat = 0; + } + if( sat == 0 ) { + hue = 0; + } + else { + double redc = + ( (double)( cmax - r ) ) / ( (double)( cmax - cmin ) ); + double greenc = + ( (double) ( cmax - g ) ) / ( (double)( cmax - cmin ) ); + double bluec = + ( (double)( cmax - b ) ) / ( (double)( cmax - cmin ) ); + + if( r == cmax ) { + hue = bluec - greenc; + } + else if( g == cmax ) { + hue = 2.0 + redc - bluec; + } + else { + hue = 4.0 + greenc - redc; + } + hue = hue / 6.0; + + if( hue < 0 ) { + hue = hue + 1.0; + } + } + + hueChannel[i] = hue; + satChannel[i] = sat; + brightChannel[i] = bright; + } + + return hsbImage; + } + + + +inline Image *ImageColorConverter::RGBToYIQ( Image *inRGBImage ) { + if( inRGBImage->getNumChannels() != 3 ) { + printf( + "RGBtoYIQ requires a 3-channel image as input.\n" ); + return NULL; + } + + Image *yiqImage = coefficientConvert( inRGBImage, + 0.299, 0.587, 0.114, + 0.596, -0.274, -0.322, + 0.212, -0.523, 0.311 ); + + return yiqImage; + } + + + +inline Image *ImageColorConverter::YIQToRGB( Image *inYIQImage ) { + if( inYIQImage->getNumChannels() != 3 ) { + printf( + "YIQtoRGB requires a 3-channel image as input.\n" ); + return NULL; + } + + Image *rgbImage = coefficientConvert( inYIQImage, + 1.0, 0.956, 0.621, + 1.0, -0.272, -0.647, + 1.0, -1.105, 1.702 ); + return rgbImage; + } + + + +inline Image *ImageColorConverter::RGBToYCbCr( Image *inRGBImage ) { + if( inRGBImage->getNumChannels() != 3 ) { + printf( + "RGBtoYCbCr requires a 3-channel image as input.\n" ); + return NULL; + } + + // coefficients taken from the color space faq + /* + RGB -> YCbCr (with Rec 601-1 specs) + Y = 0.2989 * Red + 0.5866 * Green + 0.1145 * Blue + Cb = -0.1687 * Red - 0.3312 * Green + 0.5000 * Blue + Cr = 0.5000 * Red - 0.4183 * Green - 0.0816 * Blue + + YCbCr (with Rec 601-1 specs) -> RGB + Red = Y + 0.0000 * Cb + 1.4022 * Cr + Green = Y - 0.3456 * Cb - 0.7145 * Cr + Blue = Y + 1.7710 * Cb + 0.0000 * Cr + */ + + Image *ycbcrImage = coefficientConvert( inRGBImage, + 0.2989, 0.5866, 0.1145, + -0.1687, -0.3312, 0.5000, + 0.5000, -0.4183, -0.0816 ); + + // adjust the Cb and Cr channels so they are in the range [0,1] + int numPixels = ycbcrImage->getWidth() * ycbcrImage->getHeight(); + double *cbChannel = ycbcrImage->getChannel( 1 ); + double *crChannel = ycbcrImage->getChannel( 2 ); + for( int i=0; igetNumChannels() != 3 ) { + printf( + "YCbCrtoRGB requires a 3-channel image as input.\n" ); + return NULL; + } + + // adjust the normalized Cb and Cr channels + // so they are in the range [-0.5,0.5] + + int numPixels = inYCbCrImage->getWidth() * inYCbCrImage->getHeight(); + double *cbChannel = inYCbCrImage->getChannel( 1 ); + double *crChannel = inYCbCrImage->getChannel( 2 ); + for( int i=0; i YCbCr (with Rec 601-1 specs) + Y = 0.2989 * Red + 0.5866 * Green + 0.1145 * Blue + Cb = -0.1687 * Red - 0.3312 * Green + 0.5000 * Blue + Cr = 0.5000 * Red - 0.4183 * Green - 0.0816 * Blue + + YCbCr (with Rec 601-1 specs) -> RGB + Red = Y + 0.0000 * Cb + 1.4022 * Cr + Green = Y - 0.3456 * Cb - 0.7145 * Cr + Blue = Y + 1.7710 * Cb + 0.0000 * Cr + */ + + Image *rgbImage = coefficientConvert( inYCbCrImage, + 1.0, 0.0000, 1.4022, + 1.0, -0.3456, -0.7145, + 1.0, 1.7710, 0.0000 ); + + // clip r, g, and b channels to the range [0,1], since + // some YCbCr pixel values might map out of this range + // (in other words, some YCbCr values map outside of rgb space) + for( int c=0; c<3; c++ ) { + double *channel = rgbImage->getChannel( c ); + + for( int p=0; p 1 ) { + channel[p] = 1.0; + } + } + } + + return rgbImage; + } + + + +inline Image *ImageColorConverter::coefficientConvert( Image *inImage, + double inC00, + double inC01, + double inC02, + double inC10, + double inC11, + double inC12, + double inC20, + double inC21, + double inC22 ) { + if( inImage->getNumChannels() != 3 ) { + return NULL; + } + + + int w = inImage->getWidth(); + int h = inImage->getHeight(); + + int numPixels = w * h; + + Image *outImage = new Image( w, h, 3 ); + + double *outChannel0 = outImage->getChannel( 0 ); + double *outChannel1 = outImage->getChannel( 1 ); + double *outChannel2 = outImage->getChannel( 2 ); + + double *inChannel0 = inImage->getChannel( 0 ); + double *inChannel1 = inImage->getChannel( 1 ); + double *inChannel2 = inImage->getChannel( 2 ); + + for( int i=0; i +#include + +#include "Image.h" + +/** + * An RGBA extension of Image. + * + * @author Jason Rohrer + */ +class RGBAImage : public Image { + + public: + + /** + * Constructs an RGBA image. All channels are initialized to 0. + * + * @param inWidth width of image in pixels. + * @param inHeight height of image in pixels. + */ + RGBAImage( int inWidth, int inHeight ); + + + /** + * Constructs an RGBAImage by copying a given image. + * The image data is truncated or expaned (with black color channels + * and white alpha) to fit the 4 channel RGBA model, + * and any selection in inImage is ignored. + * + * @param inImage the image to copy. Copied internally, so must be + * destroyed by the caller. + */ + RGBAImage( Image *inImage ); + + + /** + * Gets the pixel data from this image as a byte array. + * + * @return a byte array containing pixel data for this image. + * Stored in row-major order, with each pixel represented + * by 4 bytes in the order RGBA. + * Must be destroyed by caller. + */ + virtual unsigned char *getRGBABytes(); + + // get RGBA a bytes from an image without converting it to an RGBA + // image first (faster---found with profiler) + // + // MUST be a 3- or 4-channel image + static unsigned char *getRGBABytes( Image *inImage ); + + + + // overrides the Image::filter function + virtual void filter( ChannelFilter *inFilter ); + virtual void filter( ChannelFilter *inFilter, int inChannel ); + + + // overrides the Image::copy function + // since the function is non-virtual in the parent class, + // the pointer type should determine which function gets called. + RGBAImage *copy(); + + }; + + + +inline RGBAImage::RGBAImage( int inWidth, int inHeight ) + : Image( inWidth, inHeight, 4 ) { + + } + + + +inline RGBAImage::RGBAImage( Image *inImage ) + // Only init our image's channels to black of inImage doesn't have enough + // channels. + // This saves time if inImage has 4 or more channels. + // Optimization found with profiler. + : Image( inImage->getWidth(), inImage->getHeight(), 4, + ( inImage->getNumChannels() < 4 ) ) { + + int minNumChannels = 4; + if( inImage->getNumChannels() < minNumChannels ) { + minNumChannels = inImage->getNumChannels(); + } + + // if inImage does not have at least 4 channels, + // leave our additional channels black + // if inImage has extra channels, skip them + + // copy channels from inImage + for( int c=0; cgetChannel( c ); + + memcpy( mChannels[c], inChannel, mNumPixels * sizeof( double ) ); + } + + if( minNumChannels < 4 ) { + // no alpha channel in inImage + + // set a white one + double *alpha = mChannels[3]; + + for( int i=0; igetWidth() * inImage->getHeight(); + + int numChannels = inImage->getNumChannels(); + + + int numBytes = numPixels * 4; + unsigned char *bytes = new unsigned char[ numBytes ]; + + double *channelZero = inImage->getChannel( 0 ); + double *channelOne = inImage->getChannel( 1 ); + double *channelTwo = inImage->getChannel( 2 ); + + // double-coded branch here to make it faster + // (avoid a switch inside the loop + if( numChannels > 3 ) { + double *channelThree = inImage->getChannel( 3 ); + + register int i = 0; + for( int p=0; ppaste( this ); + + return copiedImage; + } + + +#endif diff --git a/minorGems/graphics/ScreenGraphics.h b/minorGems/graphics/ScreenGraphics.h new file mode 100644 index 0000000..6f5558d --- /dev/null +++ b/minorGems/graphics/ScreenGraphics.h @@ -0,0 +1,127 @@ +/* + * Modification History + * + * 2000-November-18 Jason Rohrer + * Created. + * + * 2006-April-28 Jason Rohrer + * Added functions for more direct access to screen pixels. + * + * 2006-June-26 Jason Rohrer + * Added support for dirty rectangle. + * Added resize support. + */ + + +#ifndef SCREEN_GRAPHICS_INCLUDED +#define SCREEN_GRAPHICS_INCLUDED + + +#include "GraphicBuffer.h" + +/** + * Interface for accessing full-screen, 32-bit graphics. + */ +class ScreenGraphics { + + public: + + /** + * Constructs a ScreenGraphics. + * Adjusts screen resolution and fills screen with black. + * + * @param inWidth width of desired full screen. + * @param inHeight height of desired full screen. + */ + ScreenGraphics( int inWidth, int inHeight ); + + + + /** + * Desctructor restores screen to original state. + */ + ~ScreenGraphics(); + + + + /** + * Resizes the screen. + * + * @param inNewWidth the new width. + * @param inNewHeight the new height. + */ + void resize( int inNewWidth, int inNewHeight ); + + + + /** + * Gets the pixel buffer used by the screen. + * + * @return a graphics buffer containing a pointer to the screen + * pixels. Must be destroyed by caller. + */ + GraphicBuffer *getScreenBuffer(); + + + + /** + * Flips a dirty rectangle onto the screen. + * + * @param inTopLeftX the X coordinate, in pixels, of the top left + * point on the rectangle. + * @param inTopLeftY the Y coordinate, in pixels, of the top left + * point on the rectangle. + * @param inWidth the width of the rectangle. + * @param inHeight the height of the rectangle. + */ + void flipDirtyRectangle( int inTopLeftX, int inTopLeftY, + int inWidth, int inHeight ); + + + + /** + * Flips all the pixels stored in the location pointed to by + * getScreenBuffer() onto the screen. + */ + void flipScreen(); + + + + /** + * Swaps a buffer to the screen. + * The next buffer to be filled is returned in + * inOutBuffer. + * + * No guarentee is made about what will be in the next buffer returned. + * (On systems that support on-card double buffering, it may even + * be a buffer pointing directly into video memory.) + * + * @param inOutBuffer pointer to the buffer to swap to the screen, + * and pointer where the next buffer will be stored. + */ + void swapBuffers( GraphicBuffer *inOutBuffer ); + + + + /** + * Determines whether a particular screen setting is available. + * + * NOTE: Do not call after constructing a ScreenGraphics instance. + * + * @param inWidth width of desired full screen. + * @param inHeight height of desired full screen. + * + * @return true iff screen setting is available. + */ + static char isResolutionAvailable( int inWidth, int inHeight ); + + + private: + + int mWidth; + int mHeight; + + void *mNativeObjectPointer; + }; + +#endif diff --git a/minorGems/graphics/converters/BMPImageConverter.h b/minorGems/graphics/converters/BMPImageConverter.h new file mode 100644 index 0000000..3ba7bb0 --- /dev/null +++ b/minorGems/graphics/converters/BMPImageConverter.h @@ -0,0 +1,251 @@ +/* + * Modification History + * + * 2001-February-19 Jason Rohrer + * Created. + * + * 2001-February-19 Jason Rohrer + * Fixed some bugs in the raster formatting code. + * + * 2001-May-19 Jason Rohrer + * Fixed a bug in zero padding for each line. + * + * 2001-May-21 Jason Rohrer + * Fixed another bug in zero padding for each line. + * + * 2001-September-22 Jason Rohrer + * Changed to subclass LittleEndianImageConverter. + */ + + +#ifndef BMP_IMAGE_CONVERTER_INCLUDED +#define BMP_IMAGE_CONVERTER_INCLUDED + + +#include "LittleEndianImageConverter.h" + + +/** + * Windows BMP implementation of the image conversion interface. + * + * Note that it only supports 24-bit BMP files (and thus only 3-channel + * Images). + * + * BMP format information taken from: + * http://www.daubnet.com/formats/BMP.html + * Some information from this site is incorrect: + * 24-bit pixels are _not_ stored in 32-bit blocks. + * + * @author Jason Rohrer + */ +class BMPImageConverter : public LittleEndianImageConverter { + + public: + + // implement the ImageConverter interface + virtual void formatImage( Image *inImage, + OutputStream *inStream ); + + virtual Image *deformatImage( InputStream *inStream ); + + + }; + + + +inline void BMPImageConverter::formatImage( Image *inImage, + OutputStream *inStream ) { + + // make sure the image is in the right format + if( inImage->getNumChannels() != 3 ) { + printf( + "Only 3-channel images can be converted to the BMP format.\n" ); + return; + } + + long width = inImage->getWidth(); + long height = inImage->getHeight(); + + long numPixels = width * height; + + // each line should be padded with zeros to + // end on a 4-byte boundary + int numZeroPaddingBytes = ( 4 - ( width * 3 ) % 4 ) % 4; + + short bitCount = 24; + long rasterSize = numPixels * 3; + // zero padding bytes for each row + rasterSize += numZeroPaddingBytes * height; + + // offset past two headers + long offsetToRaster = 14 + 40; + + long fileSize = offsetToRaster + rasterSize; + + long compressionType = 0; + + + long pixelsPerMeter = 2834; + + // both are 0 since we have no color map + long colorsUsed = 0; + long colorsImportant = 0; + + // write the header + unsigned char *signature = new unsigned char[2]; + signature[0] = 'B'; + signature[1] = 'M'; + inStream->write( signature, 2 ); + delete [] signature; + writeLittleEndianLong( fileSize, inStream ); + writeLittleEndianLong( 0, inStream ); + writeLittleEndianLong( offsetToRaster, inStream ); + + // write the info header + // header size + writeLittleEndianLong( 40, inStream ); + writeLittleEndianLong( width, inStream ); + writeLittleEndianLong( height, inStream ); + // numPlanes + writeLittleEndianShort( 1, inStream ); + writeLittleEndianShort( bitCount, inStream ); + writeLittleEndianLong( compressionType, inStream ); + writeLittleEndianLong( rasterSize, inStream ); + writeLittleEndianLong( pixelsPerMeter, inStream ); + writeLittleEndianLong( pixelsPerMeter, inStream ); + writeLittleEndianLong( colorsUsed, inStream ); + writeLittleEndianLong( colorsImportant, inStream ); + + // no color table... + + // now write the raster + + unsigned char *raster = new unsigned char[ rasterSize ]; + double *red = inImage->getChannel( 0 ); + double *green = inImage->getChannel( 1 ); + double *blue = inImage->getChannel( 2 ); + + // pixels are stored bottom-up, left to right + // (row major order) + + long rasterIndex = 0; + for( int y=height-1; y>=0; y-- ) { + for( int x=0; xwrite( raster, rasterSize ); + + delete [] raster; + } + + + +inline Image *BMPImageConverter::deformatImage( InputStream *inStream ) { + // temp buffer used to skip data in the stream + unsigned char *temp = new unsigned char[ 100 ]; + + // skip signature + inStream->read( temp, 2 ); + + long fileSize = readLittleEndianLong( inStream ); + + // skip unused + inStream->read( temp, 4 ); + + long rasterOffset = readLittleEndianLong( inStream ); + long rasterSize = fileSize - rasterOffset; + + // skip size of header + inStream->read( temp, 4 ); + + long width = readLittleEndianLong( inStream ); + long height = readLittleEndianLong( inStream ); + + // skip planes + inStream->read( temp, 2 ); + + short bitCount = readLittleEndianShort( inStream ); + + char failing = false; + if( bitCount != 24 ) { + printf( "Only 24-bit BMP file formats supported.\n" ); + failing = true; + } + long compression = readLittleEndianLong( inStream ); + if( compression != 0 ) { + printf( "Only uncompressed BMP file formats supported.\n" ); + failing = true; + } + + // skip imageSize, resolution, and color usage information + inStream->read( temp, 20 ); + + // now we're at the raster. + + // each line should be padded with zeros to + // end on a 4-byte boundary + int numZeroPaddingBytes = ( 4 - ( width * 3 ) % 4 ) % 4; + + unsigned char *raster = new unsigned char[ rasterSize ]; + inStream->read( raster, rasterSize ); + Image *returnImage; + if( failing ) { + return NULL; + } + else { + + returnImage = new Image( width, height, 3 ); + + double *red = returnImage->getChannel( 0 ); + double *green = returnImage->getChannel( 1 ); + double *blue = returnImage->getChannel( 2 ); + + // pixels are stored bottom-up, left to right + // (row major order) + + long rasterIndex = 0; + for( int y=height-1; y>=0; y-- ) { + for( int x=0; x> 24 ) & 0xFF ); + buffer[1] = (unsigned char)( ( inLong >> 16 ) & 0xFF ); + buffer[2] = (unsigned char)( ( inLong >> 8 ) & 0xFF ); + buffer[3] = (unsigned char)( inLong & 0xFF ); + + inStream->write( buffer, 4 ); + } + + + +inline long BigEndianImageConverter::readBigEndianLong( + InputStream *inStream ) { + + unsigned char buffer[4]; + + inStream->read( buffer, 4 ); + + long outLong = + ( buffer[0] << 24 ) | + ( buffer[1] << 16 ) | + ( buffer[2] << 8 ) | + buffer[3]; + + return outLong; + } + + + +inline void BigEndianImageConverter::writeBigEndianShort( short inShort, + OutputStream *inStream ) { + + unsigned char buffer[2]; + + buffer[0] = (unsigned char)( ( inShort >> 8 ) & 0xFF ); + buffer[1] = (unsigned char)( inShort & 0xFF ); + + inStream->write( buffer, 2 ); + } + + + +inline short BigEndianImageConverter::readBigEndianShort( + InputStream *inStream ) { + + unsigned char buffer[2]; + + inStream->read( buffer, 2 ); + + long outShort = + ( buffer[0] << 8 ) | + buffer[1]; + + return outShort; + } + + + +#endif diff --git a/minorGems/graphics/converters/JPEGImageConverter.h b/minorGems/graphics/converters/JPEGImageConverter.h new file mode 100644 index 0000000..a875999 --- /dev/null +++ b/minorGems/graphics/converters/JPEGImageConverter.h @@ -0,0 +1,95 @@ +/* + * Modification History + * + * 2001-April-27 Jason Rohrer + * Created. + */ + + +#ifndef JPEG_IMAGE_CONVERTER_INCLUDED +#define JPEG_IMAGE_CONVERTER_INCLUDED + + +#include "minorGems/graphics/ImageConverter.h" + + +/** + * JPEG implementation of the image conversion interface. + * + * Implementations are platform dependent. + * + * @author Jason Rohrer + */ +class JPEGImageConverter : public ImageConverter { + + public: + + /** + * Constructs a JPEGImageConverter. + * + * @param inQuality a quality value in [0,100] for compression. + * 100 specifies highest quality. + */ + JPEGImageConverter( int inQuality ); + + + /** + * Sets the compression quality. + * + * @param inQuality a quality value in [0,100]. 100 + * specifies highest quality. + */ + void setQuality( int inQuality ); + + + /** + * Gets the compression quality. + * + * @return a quality value in [0,100]. 100 + * indicates highest quality. + */ + int getQuality(); + + + // implement the ImageConverter interface + virtual void formatImage( Image *inImage, + OutputStream *inStream ); + + virtual Image *deformatImage( InputStream *inStream ); + + + private: + int mQuality; + }; + + + +inline JPEGImageConverter::JPEGImageConverter( int inQuality ) + : mQuality( inQuality ) { + + if( mQuality > 100 || mQuality < 0 ) { + printf( "JPEG quality must be in [0,100]\n" ); + mQuality = 50; + } + } + + + +inline void JPEGImageConverter::setQuality( int inQuality ) { + mQuality = inQuality; + + if( mQuality > 100 || mQuality < 0 ) { + printf( "JPEG quality must be in [0,100]\n" ); + mQuality = 50; + } + } + + + +inline int JPEGImageConverter::getQuality() { + return mQuality; + } + + + +#endif diff --git a/minorGems/graphics/converters/LittleEndianImageConverter.h b/minorGems/graphics/converters/LittleEndianImageConverter.h new file mode 100644 index 0000000..872e780 --- /dev/null +++ b/minorGems/graphics/converters/LittleEndianImageConverter.h @@ -0,0 +1,137 @@ +/* + * Modification History + * + * 2001-September-22 Jason Rohrer + * Created. + */ + + +#ifndef LITTLE_ENDIAN_IMAGE_CONVERTER_INCLUDED +#define LITTLE_ENDIAN_IMAGE_CONVERTER_INCLUDED + + +#include "minorGems/graphics/ImageConverter.h" + + +/** + * A base class for converters that have little endian file formats. + * Basically includes little endian reading and writing functions. + * + * @author Jason Rohrer + */ +class LittleEndianImageConverter : public ImageConverter { + + public: + + // does not implement the ImageConverter interface, + // which makes this class abstract. + + + protected: + + /** + * Writes a long value in little endian format. + * + * @param inLong the long value to write. + * @param inStream the stream to write inLong to. + */ + void writeLittleEndianLong( long inLong, + OutputStream *inStream ); + + + + /** + * Writes a short value in little endian format. + * + * @param inShort the short value to write. + * @param inStream the stream to write inShort to. + */ + void writeLittleEndianShort( short inShort, + OutputStream *inStream ); + + + + /** + * Reads a long value in little endian format. + * + * @param inStream the stream to read the long value from. + * + * @return the long value. + */ + long readLittleEndianLong( InputStream *inStream ); + + + + /** + * Reads a short value in little endian format. + * + * @param inStream the stream to read the short value from. + * + * @return the short value. + */ + short readLittleEndianShort( InputStream *inStream ); + + + + }; + + + +inline void LittleEndianImageConverter::writeLittleEndianLong( long inLong, + OutputStream *inStream ) { + + unsigned char buffer[4]; + + buffer[0] = (unsigned char)( inLong & 0xFF ); + buffer[1] = (unsigned char)( ( inLong >> 8 ) & 0xFF ); + buffer[2] = (unsigned char)( ( inLong >> 16 ) & 0xFF ); + buffer[3] = (unsigned char)( ( inLong >> 24 ) & 0xFF ); + + inStream->write( buffer, 4 ); + } + + + +inline long LittleEndianImageConverter::readLittleEndianLong( + InputStream *inStream ) { + + unsigned char buffer[4]; + + inStream->read( buffer, 4 ); + + long outLong = buffer[0] | ( buffer[1] << 8 ) | ( buffer[2] << 16 ) + | ( buffer[3] << 24 ); + + return outLong; + } + + + +inline void LittleEndianImageConverter::writeLittleEndianShort( short inShort, + OutputStream *inStream ) { + + unsigned char buffer[2]; + + buffer[0] = (unsigned char)( inShort & 0xFF ); + buffer[1] = (unsigned char)( ( inShort >> 8 ) & 0xFF ); + + inStream->write( buffer, 2 ); + } + + + +inline short LittleEndianImageConverter::readLittleEndianShort( + InputStream *inStream ) { + + unsigned char buffer[2]; + + inStream->read( buffer, 2 ); + + long outShort = buffer[0] | ( buffer[1] << 8 ); + + return outShort; + } + + + +#endif diff --git a/minorGems/graphics/converters/PNGImageConverter.cpp b/minorGems/graphics/converters/PNGImageConverter.cpp new file mode 100644 index 0000000..285bc2d --- /dev/null +++ b/minorGems/graphics/converters/PNGImageConverter.cpp @@ -0,0 +1,643 @@ +/* + * Modification History + * + * 2006-November-21 Jason Rohrer + * Created. + * + * 2010-May-18 Jason Rohrer + * String parameters as const to fix warnings. + */ + + +#include "PNGImageConverter.h" + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/graphics/RGBAImage.h" + +//#include "lodepng.h" + +//#include + +#include + + + +PNGImageConverter::PNGImageConverter( int inCompressionLevel ) + : mCompressionLevel( inCompressionLevel ) { + + // set up the CRC table + + // code taken from the PNG spec: + // http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix + unsigned long c; + int n, k; + + for( n=0; n<256; n++ ) { + c = (unsigned long)n; + for( k=0; k<8; k++ ) { + if( c & 1 ) { + c = 0xedb88320L ^ (c >> 1); + } + else { + c = c >> 1; + } + } + mCRCTable[n] = c; + } + } + + + +unsigned long PNGImageConverter::updateCRC( + unsigned long inCRC, unsigned char *inData, int inLength ) { + + // code taken from the PNG spec: + // http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix + + unsigned long c = inCRC; + int n; + + for( n=0; n> 8); + } + return c; + } + + + +#define ADLER_BASE 65521 /* largest prime smaller than 65536 */ + +/** + * Updates an adler32 checksum. + * code found here http://www.ietf.org/rfc/rfc1950.txt + * + * New adlers should start with inAdler set to 1. + * + * @param inAdler the current state of the checksum. + * @param inData the data to add. Destroyed by caller. + * @param inLength the length of the data in bytes. + * + * @return the new checksum. + */ +unsigned long updateAdler32( unsigned long inAdler, + unsigned char *inData, int inLength ) { + unsigned long s1 = inAdler & 0xffff; + unsigned long s2 = (inAdler >> 16) & 0xffff; + int n; + + for (n = 0; n < inLength; n++) { + s1 = (s1 + inData[n]) % ADLER_BASE; + s2 = (s2 + s1) % ADLER_BASE; + } + return (s2 << 16) + s1; + } + + + +void PNGImageConverter::writeChunk( + const char inChunkType[4], unsigned char *inData, + unsigned long inNumBytes, OutputStream *inStream ) { + + // chunk layout: + + // 4-byte length + // 4-char type + // data + // 4-byte CRC (applied to type and data parts) + + + // write the length + writeBigEndianLong( inNumBytes, inStream ); + + inStream->write( (unsigned char *)inChunkType, 4 ); + + // start the crc + unsigned long crc = updateCRC( mStartCRC, + (unsigned char *)inChunkType, 4 ); + + if( inData != NULL ) { + // chunk has data + + inStream->write( inData, inNumBytes ); + + crc = updateCRC( crc, inData, inNumBytes ); + } + + // final step: invert the CRC + crc = crc ^ 0xffffffffL; + + // now write the CRC + writeBigEndianLong( crc, inStream ); + } + + + +// callbacks for libpng io +void libpngWriteCallback( png_structp png_ptr, + png_bytep data, png_size_t length ) { + + // unpack our extra parameter + void *write_io_ptr = png_get_io_ptr( png_ptr ); + + OutputStream *inStream = (OutputStream *)write_io_ptr; + + inStream->write( data, length ); + } + + + +void libpngFlushCallback( png_structp png_ptr ) { + // do nothing? + } + + + +void PNGImageConverter::formatImage( Image *inImage, + OutputStream *inStream ) { + + int numChannels = inImage->getNumChannels(); + + // make sure the image is in the right format + if( numChannels != 3 && + numChannels != 4 ) { + printf( "Only 3- and 4-channel images can be converted to " ); + printf( "the PNG format.\n" ); + return; + } + + int w = inImage->getWidth(); + int h = inImage->getHeight(); + + //RGBAImage rgbaImage( inImage ); + + unsigned char *imageBytes = RGBAImage::getRGBABytes( inImage ); + + + + // libpng implementation + + // adapted from this: + // http://zarb.org/~gc/html/libpng.html + + + // get pointers to rows + unsigned char **rows = new unsigned char *[h]; + + for( int y=0; ywrite( encodedBytes, encodedSize ); + + delete [] encodedBytes; + + if( true ) { + return; + } + + */ + + + + // same for all PNG images + // used to check for basic transmission errors, such as line-end flipping + unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47, + 0x0D, 0x0A, 0x1A, 0x0A }; + + inStream->write( pngSignature, 8 ); + + + // data for IHDR chunk + unsigned char headerData[13]; + + // width + headerData[0] = (w >> 24) & 0xff; + headerData[1] = (w >> 16) & 0xff; + headerData[2] = (w >> 8) & 0xff; + headerData[3] = w & 0xff; + + // height + headerData[4] = (h >> 24) & 0xff; + headerData[5] = (h >> 16) & 0xff; + headerData[6] = (h >> 8) & 0xff; + headerData[7] = h & 0xff; + + // bit depth + headerData[8] = 8; + + // color type + // 2 = truecolor (RGB) + headerData[9] = 2; + + // compression method + // method 0 (deflate) + headerData[10] = 0; + + // filter method + // method 0 supports 5 filter types + headerData[11] = 0; + + // no interlace + headerData[12] = 0; + + writeChunk( "IHDR", headerData, 13, inStream ); + + + int numRawBytes = w * h * 3; + // extra byte per scanline for filter type + numRawBytes += h; + + unsigned char *rawScanlineBytes = new unsigned char[ numRawBytes ]; + + // ignore alpha channel + double *channels[3]; + int i; + for( i=0; i<3; i++ ) { + channels[i] = inImage->getChannel( i ); + } + + int pixelNumber = 0; + for( int y=0; y zlibBlock; + + // compression method 8 (deflate) + // with a LZ77 window size parameter of w=7 + // LZ77 window size is then 2^( w + 8 ), or in this case 32768 + zlibBlock.push_back( 0x78 ); + + // flags + // compression level 0 (2 bytes = 00b) + // no preset dictionary (1 byte = 0b) + // check bits for compression method (5 bits) + // Should be such that if the 8-bit compression method, followed + // by the 8-bit flags field, is viewed as a 16-bit number, + // it is an even multiple of 31 + // For our settings, check bits of 00001 works + //zlibBlock.push_back( 0x01 ); + // hack: mimic zlib here + zlibBlock.push_back( 0xda ); + + + // now ready for compressed data blocks + + int rawDataIndex = 0; + + // length field is 16 bits + int maxUncompressedBlockLength = 65535; + + while( rawDataIndex < numRawBytes ) { + + // push another deflate block + + // first bit BFINAL + // only 1 for final block + // next two bits BTYPE + // BTYPE=00 is an uncompressed block + // remaining 5 bits ignored + // Thus, we put 0x00 for uncompressed blocks that are not final + // and 0x80 for final uncompressed block + + + int bytesLeft = numRawBytes - rawDataIndex; + + int bytesInBlock; + + if( bytesLeft <= maxUncompressedBlockLength ) { + // final + + // hack: when comparing output with zlib, noticed that it doesn't + // set 0x80 for the final block, instead it uses 0x01 + // For some reason, this was making the PNG unreadable + // zlibBlock.push_back( 0x80 ); + zlibBlock.push_back( 0x01 ); + + bytesInBlock = bytesLeft; + } + else { + // not final + zlibBlock.push_back( 0x00 ); + + bytesInBlock = maxUncompressedBlockLength; + } + + // length in least-significant-byte-first order + unsigned char firstLengthByte = bytesInBlock & 0xff; + unsigned char secondLengthByte = (bytesInBlock >> 8) & 0xff; + zlibBlock.push_back( firstLengthByte ); + zlibBlock.push_back( secondLengthByte ); + + // those same length bytes inverted + // (called "one's compliment" in the spec + zlibBlock.push_back( firstLengthByte ^ 0xff); + zlibBlock.push_back( secondLengthByte ^ 0xff ); + + // now the uncompressed data + + for( int b=0; b< bytesInBlock; b++ ) { + zlibBlock.push_back( rawScanlineBytes[ rawDataIndex ] ); + + rawDataIndex++; + } + } + + + // finally, adler32 of original data + unsigned long adler = updateAdler32( 1L, rawScanlineBytes, numRawBytes ); + + zlibBlock.push_back( (adler >> 24) & 0xff ); + zlibBlock.push_back( (adler >> 16) & 0xff ); + zlibBlock.push_back( (adler >> 8) & 0xff ); + zlibBlock.push_back( adler & 0xff ); + + + // the zlib block is now complete + + + + + /* + // check against real zlib implementation + z_stream zStream; + + zStream.next_in = rawScanlineBytes; + zStream.avail_in = numRawBytes; + zStream.total_in = 0; + + int outSize = 2 * numRawBytes + 100; + unsigned char *zlibOutBuffer = new unsigned char[ outSize ]; + zStream.next_out = zlibOutBuffer; + zStream.avail_out = outSize; + zStream.total_out = 0; + + zStream.data_type = Z_BINARY; + + zStream.zalloc = Z_NULL; + zStream.zfree = Z_NULL; + + // init the stream + // no compression + int result; + //result = deflateInit( &zStream, Z_DEFAULT_COMPRESSION); + result = deflateInit( &zStream, Z_NO_COMPRESSION); + + if( result != Z_OK ) { + printf( "zlib deflateInit error: %s\n", zStream.msg ); + } + + + // deflate and flush + result = deflate( &zStream, Z_FINISH ); + + if( result != Z_STREAM_END ) { + printf( "zlib deflate error (%d): %s\n", result, zStream.msg ); + } + printf( "Total in = %d, total out = %d\n", + zStream.total_in, zStream.total_out ); + + + + + printf( "Our raw bytes (%d):\n", numRawBytes ); + int b; + for( b=0; b zlibBlock.size() ) { + minBytes = zlibBlock.size(); + } + + for( b=0; bgetNumChannels(); + + // make sure the image is in the right format + if( numChannels != 3 && + numChannels != 4 ) { + printf( "Only 3- and 4-channel images can be converted to " ); + printf( "the TGA format.\n" ); + return; + } + + long width = inImage->getWidth(); + long height = inImage->getHeight(); + + long numPixels = width * height; + + + // a buffer for writing single bytes + unsigned char *byteBuffer = new unsigned char[1]; + + // write the identification field size + // (an empty identification field) + byteBuffer[0] = 0; + inStream->write( byteBuffer, 1 ); + + // write the color map type + // (no color map) + byteBuffer[0] = 0; + inStream->write( byteBuffer, 1 ); + + // write the image type code + // (type 2: unmapped RGB image) + byteBuffer[0] = 2; + inStream->write( byteBuffer, 1 ); + + // no color map spec + // (set to 0, though it will be ignored) + unsigned char *colorMapSpec = new unsigned char[5]; + int i; + for( i=0; i<5; i++ ) { + colorMapSpec[i] = 0; + } + inStream->write( colorMapSpec, 5 ); + delete [] colorMapSpec; + + // now for the image specification + + // x origin coordinate + writeLittleEndianShort( 0, inStream ); + // y origin coordinate + writeLittleEndianShort( 0, inStream ); + + writeLittleEndianShort( width, inStream ); + writeLittleEndianShort( height, inStream ); + + // number of bits in pixels + if( numChannels == 3 ) { + byteBuffer[0] = 24; + } + else { + byteBuffer[0] = 32; + } + inStream->write( byteBuffer, 1 ); + + + // image descriptor byte + if( numChannels == 3 ) { + // setting to 0 specifies: + // -- no attributes per pixel (for 24-bit) + // -- screen origin in lower left corner + // -- non-interleaved data storage + byteBuffer[0] = 0; + } + else { + // setting to 8 specifies: + // -- 8 attributes per pixel (for 32-bit) (attributes are alpha bits) + // -- screen origin in lower left corner + // -- non-interleaved data storage + byteBuffer[0] = 8; + } + + // set bit 5 to 1 to specify screen origin in upper left corner + byteBuffer[0] = byteBuffer[0] | ( 1 << 5 ); + inStream->write( byteBuffer, 1 ); + + + // We skip the image identification field, + // since we set its length to 0 above. + + // We also skip the color map data, + // since we have none (as specified above). + + + // now we write the pixels, in BGR(A) order + unsigned char *raster = new unsigned char[ numPixels * numChannels ]; + double *red = inImage->getChannel( 0 ); + double *green = inImage->getChannel( 1 ); + double *blue = inImage->getChannel( 2 ); + + long rasterIndex = 0; + + if( numChannels == 3 ) { + for( int i=0; iwrite( raster, numPixels * 3 ); + } + else { // numChannels == 4 + double *alpha = inImage->getChannel( 3 ); + + for( int i=0; iwrite( raster, numPixels * numChannels ); + + delete [] raster; + delete [] byteBuffer; + } + + + +inline Image *TGAImageConverter::deformatImage( InputStream *inStream ) { + + // a buffer for reading single bytes + unsigned char *byteBuffer = new unsigned char[1]; + + // read the identification field size + inStream->read( byteBuffer, 1 ); + + int identificationFieldSize = byteBuffer[0]; + + // read the color map type + // (only 0, or no color map, is supported) + inStream->read( byteBuffer, 1 ); + if( byteBuffer[0] != 0 ) { + printf( "Only TGA files without colormaps can be read.\n" ); + delete [] byteBuffer; + return NULL; + } + + // read the image type code + // (only type 2, unmapped RGB image, is supported) + inStream->read( byteBuffer, 1 ); + if( byteBuffer[0] != 2 ) { + printf( + "Only TGA files containing unmapped RGB images can be read.\n" ); + delete [] byteBuffer; + return NULL; + } + + + // ignore color map spec + // (skip all 5 bytes of it) + unsigned char *colorMapSpec = new unsigned char[5]; + inStream->read( colorMapSpec, 5 ); + delete [] colorMapSpec; + + + // now for the image specification + + // don't need either of these + // don't set to a variable for now to avoid unused variable warnings + // x origin coordinate + readLittleEndianShort( inStream ); + // y origin coordinate + readLittleEndianShort( inStream ); + + long width = readLittleEndianShort( inStream ); + long height = readLittleEndianShort( inStream ); + + long numPixels = width * height; + + + // number of bits in pixels + // only 24 bits per pixel supported + + inStream->read( byteBuffer, 1 ); + if( byteBuffer[0] != 24 && byteBuffer[0] != 32 ) { + printf( "Only 24- and 32-bit TGA files can be read.\n" ); + delete [] byteBuffer; + return NULL; + } + + int numChannels = 0; + if( byteBuffer[0] == 24 ) { + numChannels = 3; + } + else { + numChannels = 4; + } + + + // image descriptor byte + // setting to 0 specifies: + // -- no attributes per pixel (for 24-bit) + // -- screen origin in lower left corner + // -- non-interleaved data storage + // set bit 5 to 1 to specify screen origin in upper left corner + inStream->read( byteBuffer, 1 ); + char originAtTop = byteBuffer[0] & ( 1 << 5 ); + + + if( identificationFieldSize > 0 ) { + // We skip the image identification field + unsigned char *identificationField = + new unsigned char[ identificationFieldSize ]; + inStream->read( identificationField, identificationFieldSize ); + delete [] identificationField; + } + + + // We also skip the color map data, + // since we have none (as specified above). + + + + + // now we read the pixels, in BGR(A) order + unsigned char *raster = new unsigned char[ numPixels * numChannels ]; + inStream->read( raster, numPixels * numChannels ); + + // optimization: don't init channels to black (found with profiler) + Image *image = new Image( width, height, numChannels, false ); + + double *red = image->getChannel( 0 ); + double *green = image->getChannel( 1 ); + double *blue = image->getChannel( 2 ); + + long rasterIndex = 0; + double inv255 = 1.0 / 255.0; + + if( numChannels == 3 ) { + if( originAtTop ) { + for( int i=0; i=0; y-- ) { + for( int x=0; xgetChannel( 3 ); + + if( originAtTop ) { + for( int i=0; i=0; y-- ) { + int yOffset = y * width; + + for( int x=0; x + +#include "minorGems/graphics/Image.h" +#include "BMPImageConverter.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileOutputStream.h" +#include "minorGems/io/file/FileInputStream.h" + +// test function for the BMPImageConverter class +int main( char inNumArgs, char**inArgs ) { + if( inNumArgs != 2 ) { + printf( "must pass in a file name to write to\n" ); + return 1; + } + + int length = 0; + while( inArgs[1][length] != '\0' ) { + length++; + } + + File *file = new File( NULL, inArgs[1], length ); + + + // read image in + FileInputStream *stream = new FileInputStream( file ); + BMPImageConverter *converter = new BMPImageConverter(); + Image *image = converter->deformatImage( stream ); + + if( image != NULL ) { + // write image back out + File *fileOut = new File( NULL, "testOut.bmp", 11 ); + FileOutputStream *outStream = new FileOutputStream( fileOut ); + + converter->formatImage( image, outStream ); + + delete outStream; + delete image; + } + + delete stream; + delete file; + delete converter; + + + /* + FileOutputStream *stream = new FileOutputStream( file ); + + BMPImageConverter *converter = new BMPImageConverter(); + + Image *image = new Image( 256, 256, 3 ); + + double *red = image->getChannel( 0 ); + double *green = image->getChannel( 1 ); + + for( int y=0; ygetHeight(); y++ ) { + for( int x=0; xgetWidth(); x++ ) { + long index = y * image->getWidth() + x; + + red[index] = (double)y / (double)( image->getHeight() ); + green[index] = (double)x / (double)( image->getWidth() ); + //red[index] = 1.0; + } + } + + converter->formatImage( image, stream ); + + + delete stream; + // delete file explicitly + delete file; + delete converter; + delete image; + */ + return 0; + } diff --git a/minorGems/graphics/converters/bmpformat.txt b/minorGems/graphics/converters/bmpformat.txt new file mode 100644 index 0000000..7315499 --- /dev/null +++ b/minorGems/graphics/converters/bmpformat.txt @@ -0,0 +1,87 @@ + + Size + Description + Header + 14 bytes + Windows Structure: BITMAPFILEHEADER + + Signature + 2 bytes + 'BM' + FileSize + 4 bytes + File size in bytes + reserved + 4 bytes + unused (=0) + DataOffset + 4 bytes + File offset to Raster Data + InfoHeader + 40 bytes + Windows Structure: BITMAPINFOHEADER + + Size + 4 bytes + Size of InfoHeader =40 + Width + 4 bytes + Bitmap Width + Height + 4 bytes + Bitmap Height + Planes + 2 bytes + Number of Planes (=1) + BitCount + 2 bytes + Bits per Pixel + 1 = monochrome palette. NumColors = 1 + 4 = 4bit palletized. NumColors = 16 + 8 = 8bit palletized. NumColors = 256 + 16 = 16bit RGB. NumColors = 65536 (?) + 24 = 24bit RGB. NumColors = 16M + Compression + 4 bytes + Type of Compression + 0 = BI_RGB no compression + 1 = BI_RLE8 8bit RLE encoding + 2 = BI_RLE4 4bit RLE encoding + ImageSize + 4 bytes + (compressed) Size of Image + It is valid to set this =0 if Compression = 0 + XpixelsPerM + 4 bytes + horizontal resolution: Pixels/meter + YpixelsPerM + 4 bytes + vertical resolution: Pixels/meter + ColorsUsed + 4 bytes + Number of actually used colors + ColorsImportant + 4 bytes + Number of important colors + 0 = all + ColorTable + 4 * NumColors bytes + present only if Info.BitsPerPixel <= 8 + colors should be ordered by importance + + Red + 1 byte + Red intensity + Green + 1 byte + Green intensity + Blue + 1 byte + Blue intensity + reserved + 1 byte + unused (=0) + repeated NumColors times + Raster Data + Info.ImageSize bytes + The pixel data diff --git a/minorGems/graphics/converters/compileTestPNG b/minorGems/graphics/converters/compileTestPNG new file mode 100755 index 0000000..b95ee15 --- /dev/null +++ b/minorGems/graphics/converters/compileTestPNG @@ -0,0 +1 @@ +g++ -g -Wall -o testPNG -I../../.. testPNG.cpp PNGImageConverter.cpp ../../io/file/linux/PathLinux.cpp ../../system/unix/TimeUnix.cpp -lz -lpng diff --git a/minorGems/graphics/converters/jpegConverterTest.cpp b/minorGems/graphics/converters/jpegConverterTest.cpp new file mode 100644 index 0000000..63326dc --- /dev/null +++ b/minorGems/graphics/converters/jpegConverterTest.cpp @@ -0,0 +1,90 @@ +/* + * Modification History + * + * 2001-April-27 Jason Rohrer + * Created. + * + * 2001-April-29 Jason Rohrer + * Completed initial version and used to test JPEGImageConverter + * successfully. + */ + + +#include + +#include "minorGems/graphics/Image.h" +#include "JPEGImageConverter.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileOutputStream.h" +#include "minorGems/io/file/FileInputStream.h" + +// test function for the BMPImageConverter class +int main( char inNumArgs, char**inArgs ) { + if( inNumArgs != 2 ) { + printf( "must pass in a file name to write to\n" ); + return 1; + } + + int length = 0; + while( inArgs[1][length] != '\0' ) { + length++; + } + + File *file = new File( NULL, inArgs[1], length ); + + + // read image in + FileInputStream *stream = new FileInputStream( file ); + JPEGImageConverter *converter = new JPEGImageConverter( 50 ); + Image *image = converter->deformatImage( stream ); + + if( image != NULL ) { + // write image back out + File *fileOut = new File( NULL, "testOut.jpg", 11 ); + FileOutputStream *outStream = new FileOutputStream( fileOut ); + + converter->formatImage( image, outStream ); + + delete outStream; + delete fileOut; + delete image; + } + + + delete stream; + delete converter; + delete file; + + /* + FileOutputStream *stream = new FileOutputStream( file ); + + JPEGImageConverter *converter = new JPEGImageConverteonverter( 50 ); + + Image *image = new Image( 256, 256, 3 ); + + double *red = image->getChannel( 0 ); + double *green = image->getChannel( 1 ); + + for( int y=0; ygetHeight(); y++ ) { + for( int x=0; xgetWidth(); x++ ) { + long index = y * image->getWidth() + x; + + red[index] = (double)y / (double)( image->getHeight() ); + green[index] = (double)x / (double)( image->getWidth() ); + //red[index] = 1.0; + } + } + + converter->formatImage( image, stream ); + + + delete stream; + // delete file explicitly + delete file; + delete converter; + delete image; + */ + + return 0; + } diff --git a/minorGems/graphics/converters/jpegConverterTestCompile b/minorGems/graphics/converters/jpegConverterTestCompile new file mode 100755 index 0000000..836a641 --- /dev/null +++ b/minorGems/graphics/converters/jpegConverterTestCompile @@ -0,0 +1 @@ +g++ -I../../.. -ljpeg -o jpegConverterTest jpegConverterTest.cpp unix/JPEGImageConverterUnix.cpp ../../io/file/linux/PathLinux.cpp diff --git a/minorGems/graphics/converters/libpngSample.cpp b/minorGems/graphics/converters/libpngSample.cpp new file mode 100644 index 0000000..f5a93cc --- /dev/null +++ b/minorGems/graphics/converters/libpngSample.cpp @@ -0,0 +1,108 @@ + + + + +#include +#include + +#include +#include +#include +#include + + +void abort_(const char * s, ...) +{ + va_list args; + va_start(args, s); + vfprintf(stderr, s, args); + fprintf(stderr, "\n"); + va_end(args); + abort(); +} + +int main() { + + int w = 100; + int h = 100; + + unsigned int *data = new unsigned int[ w * h ]; + + // red fades toward bottom + // green fades toward right + + unsigned int **rows = new unsigned int *[ h ]; + + for( int y=0; y nothing done*/ +{ + if(size * p->typesize > p->allocsize) + { + size_t newsize = size * p->typesize * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = data; + p->size = size; + } + else return 0; + } + else p->size = size; + return 1; +} + +static unsigned vector_resized(vector* p, size_t size, void dtor(void*)) /*resize and use destructor on elements if it gets smaller*/ +{ + size_t i; + if(size < p->size) for(i = size; i < p->size; i++) dtor(&((char*)(p->data))[i * p->typesize]); + return vector_resize(p, size); +} + +static void vector_cleanup(void* p) +{ + ((vector*)p)->size = ((vector*)p)->allocsize = 0; + free(((vector*)p)->data); + ((vector*)p)->data = NULL; +} + +static void vector_cleanupd(vector* p, void dtor(void*)) /*clear and use destructor on elements*/ +{ + vector_resized(p, 0, dtor); + vector_cleanup(p); +} + +static void vector_init(vector* p, unsigned typesize) +{ + p->data = NULL; + p->size = p->allocsize = 0; + p->typesize = typesize; +} + +static void vector_swap(vector* p, vector* q) /*they're supposed to have the same typesize*/ +{ + size_t tmp; + void* tmpp; + tmp = p->size; p->size = q->size; q->size = tmp; + tmp = p->allocsize; p->allocsize = q->allocsize; q->allocsize = tmp; + tmpp = p->data; p->data = q->data; q->data = tmpp; +} + +static void* vector_get(vector* p, size_t index) +{ + return &((char*)p->data)[index * p->typesize]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +static unsigned uivector_resize(uivector* p, size_t size) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + if(size * sizeof(unsigned) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned) * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + p->size = size; + } + else return 0; + } + else p->size = size; + return 1; +} + +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) /*resize and give all new elements the value*/ +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned uivector_push_back(uivector* p, unsigned c) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} + +static unsigned uivector_copy(uivector* p, const uivector* q) /*copy q to p, returns 1 if success, 0 if failure ==> nothing done*/ +{ + size_t i; + if(!uivector_resize(p, q->size)) return 0; + for(i = 0; i < q->size; i++) p->data[i] = q->data[i]; + return 1; +} + +static void uivector_swap(uivector* p, uivector* q) +{ + size_t tmp; + unsigned* tmpp; + tmp = p->size; p->size = q->size; q->size = tmp; + tmp = p->allocsize; p->allocsize = q->allocsize; q->allocsize = tmp; + tmpp = p->data; p->data = q->data; q->data = tmpp; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +static unsigned ucvector_resize(ucvector* p, size_t size) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + if(size * sizeof(unsigned) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned) * 2; + void* data = realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + p->size = size; + } + else return 0; /*error: not enough memory*/ + } + else p->size = size; + return 1; +} + +#ifdef LODEPNG_COMPILE_DECODER +#ifdef LODEPNG_COMPILE_PNG +static unsigned ucvector_resizev(ucvector* p, size_t size, unsigned char value) /*resize and give all new elements the value*/ +{ + size_t oldsize = p->size, i; + if(!ucvector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} +#endif /*LODEPNG_COMPILE_PNG*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +static unsigned ucvector_push_back(ucvector* p, unsigned char c) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned string_resize(char** out, size_t size) /*returns 1 if success, 0 if failure ==> nothing done*/ +{ + char* data = (char*)realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +static void string_init(char** out) /*init a {char*, size_t} pair for use as string*/ +{ + *out = NULL; + string_resize(out, 0); +} + +static void string_cleanup(char** out) /*free the above pair again*/ +{ + free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i = 0; + if(string_resize(out, insize)) for(i = 0; i < insize; i++) (*out)[i] = in[i]; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for Deflate / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER +static void addBitToStream(size_t* bitpointer, ucvector* bitstream, unsigned char bit) +{ + if((*bitpointer) % 8 == 0) ucvector_push_back(bitstream, 0); /*add a new byte at the end*/ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7)); /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + (*bitpointer)++; +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> ((*bitpointer) & 0x7)) & 1); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i < nbits; i++) result += ((unsigned)readBitFromStream(bitpointer, bitstream)) << i; + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +#define NUM_DEFLATE_CODE_SYMBOLS 288 /*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DISTANCE_SYMBOLS 32 /*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_CODE_LENGTH_CODES 19 /*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ + +static const unsigned LENGTHBASE[29] /*the base lengths represented by codes 257-285*/ + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; +static const unsigned LENGTHEXTRA[29] /*the extra bits used by codes 257-285 (added to base length)*/ + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; +static const unsigned DISTANCEBASE[30] /*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; +static const unsigned DISTANCEEXTRA[30] /*the extra bits of backwards distances (added to base)*/ + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; +static const unsigned CLCL[NUM_CODE_LENGTH_CODES] /*the order in which "code length alphabet code lengths" are stored, out of this the huffman tree of the dynamic huffman tree lengths is generated*/ + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* /////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*terminology used for the package-merge algorithm and the coin collector's problem*/ +typedef struct Coin /*a coin can be multiple coins (when they're merged)*/ +{ + uivector symbols; + float weight; /*the sum of all weights in this coin*/ +} Coin; + +static void Coin_init(Coin* c) +{ + uivector_init(&c->symbols); +} + +static void Coin_cleanup(void* c) /*void* so that this dtor can be given as function pointer to the vector resize function*/ +{ + uivector_cleanup(&((Coin*)c)->symbols); +} + +static void Coin_copy(Coin* c1, const Coin* c2) +{ + c1->weight = c2->weight; + uivector_copy(&c1->symbols, &c2->symbols); +} + +static void addCoins(Coin* c1, const Coin* c2) +{ + unsigned i; + for(i = 0; i < c2->symbols.size; i++) uivector_push_back(&c1->symbols, c2->symbols.data[i]); + c1->weight += c2->weight; +} + +static void Coin_sort(Coin* data, size_t amount) /*combsort*/ +{ + size_t gap = amount; + unsigned char swapped = 0; + while(gap > 1 || swapped) + { + size_t i; + gap = (gap * 10) / 13; /*shrink factor 1.3*/ + if(gap == 9 || gap == 10) gap = 11; /*combsort11*/ + if(gap < 1) gap = 1; + swapped = 0; + for(i = 0; i < amount - gap; i++) + { + size_t j = i + gap; + if(data[j].weight < data[i].weight) + { + float temp = data[j].weight; data[j].weight = data[i].weight; data[i].weight = temp; + uivector_swap(&data[i].symbols, &data[j].symbols); + swapped = 1; + } + } + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +typedef struct HuffmanTree +{ + uivector tree2d; + uivector tree1d; + uivector lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes*/ +/*#include +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i < tree->tree1d.size; i++) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + uivector_init(&tree->tree2d); + uivector_init(&tree->tree1d); + uivector_init(&tree->lengths); +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + uivector_cleanup(&tree->tree2d); + uivector_cleanup(&tree->tree1d); + uivector_cleanup(&tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + if(!uivector_resize(&tree->tree2d, tree->numcodes * 2)) return 9901; /*if failed return not enough memory error*/ + /*convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means uninited, a value >= numcodes is an address to another bit, a value < numcodes is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as many columns as codes - 1 + a good huffmann tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. Here, the internal nodes are stored (what their 0 and 1 option point to). There is only memory for such good tree currently, if there are more nodes (due to too long length codes), error 55 will happen*/ + for(n = 0; n < tree->numcodes * 2; n++) tree->tree2d.data[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + + for(n = 0; n < tree->numcodes; n++) /*the codes*/ + for(i = 0; i < tree->lengths.data[n]; i++) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d.data[n] >> (tree->lengths.data[n] - i - 1)) & 1); + if(treepos > tree->numcodes - 2) return 55; /*error 55: oversubscribed; see description in header*/ + if(tree->tree2d.data[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths.data[n]) /*last bit*/ + { + tree->tree2d.data[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else /*put address of the next step in here, first that address has to be found of course (it's just nodefilled + 1)...*/ + { + nodefilled++; + tree->tree2d.data[2 * treepos + bit] = nodefilled + tree->numcodes; /*addresses encoded with numcodes added to it*/ + treepos = nodefilled; + } + } + else treepos = tree->tree2d.data[2 * treepos + bit] - tree->numcodes; + } + for(n = 0; n < tree->numcodes * 2; n++) if(tree->tree2d.data[n] == 32767) tree->tree2d.data[n] = 0; /*remove possible remaining 32767's*/ + + return 0; +} + +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) /*given that numcodes, lengths and maxbitlen are already filled in correctly. return value is error.*/ +{ + uivector blcount; + uivector nextcode; + unsigned bits, n, error = 0; + + uivector_init(&blcount); + uivector_init(&nextcode); + if(!uivector_resize(&tree->tree1d, tree->numcodes) + || !uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 9902; + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits < tree->numcodes; bits++) blcount.data[tree->lengths.data[bits]]++; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; bits++) nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + /*step 3: generate all the codes*/ + for(n = 0; n < tree->numcodes; n++) if(tree->lengths.data[n] != 0) tree->tree1d.data[n] = nextcode.data[tree->lengths.data[n]]++; + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/*given the code lengths (as stored in the PNG file), generate the tree as defined by Deflate. maxbitlen is the maximum bits that a code in the tree can have. return value is error.*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + if(!uivector_resize(&tree->lengths, numcodes)) return 9903; + for(i = 0; i < numcodes; i++) tree->lengths.data[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned HuffmanTree_fillInCoins(vector* coins, const unsigned* frequencies, unsigned numcodes, size_t sum) +{ + unsigned i; + for(i = 0; i < numcodes; i++) + { + Coin* coin; + if(frequencies[i] == 0) continue; /*it's important to exclude symbols that aren't present*/ + if(!vector_resize(coins, coins->size + 1)) { vector_cleanup(coins); return 9904; } + coin = (Coin*)(vector_get(coins, coins->size - 1)); + Coin_init(coin); + coin->weight = frequencies[i] / (float)sum; + uivector_push_back(&coin->symbols, i); + } + if(coins->size) Coin_sort((Coin*)coins->data, coins->size); + return 0; +} + +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, size_t numcodes, unsigned maxbitlen) +{ + unsigned i, j; + size_t sum = 0, numpresent = 0; + unsigned error = 0; + + vector prev_row; /*type Coin, the previous row of coins*/ + vector coins; /*type Coin, the coins of the currently calculated row*/ + + tree->maxbitlen = maxbitlen; + + for(i = 0; i < numcodes; i++) + { + if(frequencies[i] > 0) + { + numpresent++; + sum += frequencies[i]; + } + } + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + uivector_resize(&tree->lengths, 0); + if(!uivector_resizev(&tree->lengths, tree->numcodes, 0)) return 9905; + + if(numpresent == 0) /*there are no symbols at all, in that case add one symbol of value 0 to the tree (see RFC 1951 section 3.2.7) */ + { + tree->lengths.data[0] = 1; + return HuffmanTree_makeFromLengths2(tree); + } + else if(numpresent == 1) /*the package merge algorithm gives wrong results if there's only one symbol (theoretically 0 bits would then suffice, but we need a proper symbol for zlib)*/ + { + for(i = 0; i < numcodes; i++) if(frequencies[i]) tree->lengths.data[i] = 1; + return HuffmanTree_makeFromLengths2(tree); + } + + vector_init(&coins, sizeof(Coin)); + vector_init(&prev_row, sizeof(Coin)); + + /*Package-Merge algorithm represented by coin collector's problem + For every symbol, maxbitlen coins will be created*/ + + /*first row, lowest denominator*/ + error = HuffmanTree_fillInCoins(&coins, frequencies, tree->numcodes, sum); + if(!error) + { + for(j = 1; j <= maxbitlen && !error; j++) /*each of the remaining rows*/ + { + vector_swap(&coins, &prev_row); /*swap instead of copying*/ + if(!vector_resized(&coins, 0, Coin_cleanup)) { error = 9906; break; } + + for(i = 0; i + 1 < prev_row.size; i += 2) + { + if(!vector_resize(&coins, coins.size + 1)) { error = 9907; break; } + Coin_init((Coin*)vector_get(&coins, coins.size - 1)); + Coin_copy((Coin*)vector_get(&coins, coins.size - 1), (Coin*)vector_get(&prev_row, i)); + addCoins((Coin*)vector_get(&coins, coins.size - 1), (Coin*)vector_get(&prev_row, i + 1)); /*merge the coins into packages*/ + } + if(j < maxbitlen) + { + error = HuffmanTree_fillInCoins(&coins, frequencies, tree->numcodes, sum); + } + } + } + + if(!error) + { + /*keep the coins with lowest weight, so that they add up to the amount of symbols - 1*/ + vector_resized(&coins, numpresent - 1, Coin_cleanup); + + /*calculate the lenghts of each symbol, as the amount of times a coin of each symbol is used*/ + for(i = 0; i < coins.size; i++) + { + Coin* coin = (Coin*)vector_get(&coins, i); + for(j = 0; j < coin->symbols.size; j++) tree->lengths.data[coin->symbols.data[j]]++; + } + + error = HuffmanTree_makeFromLengths2(tree); + } + + vector_cleanupd(&coins, Coin_cleanup); + vector_cleanupd(&prev_row, Coin_cleanup); + + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) { return tree->tree1d.data[index]; } +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) { return tree->lengths.data[index]; } +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + uivector bitlen; + uivector_init(&bitlen); + if(!uivector_resize(&bitlen, NUM_DEFLATE_CODE_SYMBOLS)) error = 9909; + + if(!error) + { + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; i++) bitlen.data[i] = 8; + for(i = 144; i <= 255; i++) bitlen.data[i] = 9; + for(i = 256; i <= 279; i++) bitlen.data[i] = 7; + for(i = 280; i <= 287; i++) bitlen.data[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen.data, NUM_DEFLATE_CODE_SYMBOLS, 15); + } + + uivector_cleanup(&bitlen); + return error; +} + +static unsigned generateDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + uivector bitlen; + uivector_init(&bitlen); + if(!uivector_resize(&bitlen, NUM_DISTANCE_SYMBOLS)) error = 9910; + + /*there are 32 distance codes, but 30-31 are unused*/ + if(!error) + { + for(i = 0; i < NUM_DISTANCE_SYMBOLS; i++) bitlen.data[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen.data, NUM_DISTANCE_SYMBOLS, 15); + } + uivector_cleanup(&bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Decodes a symbol from the tree +if decoded is true, then result contains the symbol, otherwise it contains something unspecified (because the symbol isn't fully decoded yet) +bit is the bit that was just read from the stream +you have to decode a full symbol (let the decode function return true) before you can try to decode another one, otherwise the state isn't reset +return value is error.*/ +static unsigned HuffmanTree_decode(const HuffmanTree* tree, unsigned* decoded, unsigned* result, unsigned* treepos, unsigned char bit) +{ + if((*treepos) >= tree->numcodes) return 11; /*error: it appeared outside the codetree*/ + + (*result) = tree->tree2d.data[2 * (*treepos) + bit]; + (*decoded) = ((*result) < tree->numcodes); + + if(*decoded) (*treepos) = 0; + else (*treepos) = (*result) - tree->numcodes; + + return 0; +} + +static unsigned huffmanDecodeSymbol(unsigned int* error, const unsigned char* in, size_t* bp, const HuffmanTree* codetree, size_t inlength) +{ + unsigned treepos = 0, decoded, ct; + for(;;) + { + unsigned char bit; + if(((*bp) & 0x07) == 0 && ((*bp) >> 3) > inlength) { *error = 10; return 0; } /*error: end of input memory reached without endcode*/ + bit = readBitFromStream(bp, in); + *error = HuffmanTree_decode(codetree, &decoded, &ct, &treepos, bit); + if(*error) return 0; /*stop, an error happened*/ + if(decoded) return ct; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree, HuffmanTree* treeD) +{ + /*error checking not done, this is fixed stuff, it works, it doesn't depend on the image*/ + generateFixedTree(tree); + generateDistanceTree(treeD); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* codetree, HuffmanTree* codetreeD, HuffmanTree* codelengthcodetree, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + /*C-code note: use no "return" between ctor and dtor of an uivector!*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + uivector bitlen; + uivector bitlenD; + uivector codelengthcode; + + if((*bp) >> 3 >= inlength - 2) { return 49; } /*the bit pointer is or will go past the memory*/ + + HLIT = readBitsFromStream(bp, in, 5) + 257; /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + uivector_init(&codelengthcode); + if(!uivector_resize(&codelengthcode, NUM_CODE_LENGTH_CODES)) error = 9911; + + if(!error) + { + for(i = 0; i < NUM_CODE_LENGTH_CODES; i++) + { + if(i < HCLEN) codelengthcode.data[CLCL[i]] = readBitsFromStream(bp, in, 3); + else codelengthcode.data[CLCL[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(codelengthcodetree, codelengthcode.data, codelengthcode.size, 7); + } + + uivector_cleanup(&codelengthcode); + if(error) return error; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + uivector_init(&bitlen); + uivector_resizev(&bitlen, NUM_DEFLATE_CODE_SYMBOLS, 0); + uivector_init(&bitlenD); + uivector_resizev(&bitlenD, NUM_DISTANCE_SYMBOLS, 0); + i = 0; + if(!bitlen.data || !bitlenD.data) error = 9912; + else while(i < HLIT + HDIST) /*i is the current symbol we're reading in the part that contains the code lengths of lit/len codes and dist codes*/ + { + unsigned code = huffmanDecodeSymbol(&error, in, bp, codelengthcodetree, inlength); + if(error) break; + + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen.data[i] = code; + else bitlenD.data[i - HLIT] = code; + i++; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if((*bp) >> 3 >= inlength) { error = 50; break; } /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 2); + + if((i - 1) < HLIT) value = bitlen.data[i - 1]; + else value = bitlenD.data[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) { error = 13; break; } /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen.data[i] = value; + else bitlenD.data[i - HLIT] = value; + i++; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if((*bp) >> 3 >= inlength) { error = 50; break; } /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) { error = 14; break; } /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen.data[i] = 0; + else bitlenD.data[i - HLIT] = 0; + i++; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if((*bp) >> 3 >= inlength) { error = 50; break; } /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) { error = 15; break; } /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen.data[i] = 0; + else bitlenD.data[i - HLIT] = 0; + i++; + } + } + else { error = 16; break; } /*error: somehow an unexisting code appeared. This can never happen.*/ + } + + if(!error && bitlen.data[256] == 0) { error = 64; } /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + if(!error) error = HuffmanTree_makeFromLengths(codetree, &bitlen.data[0], bitlen.size, 15); + if(!error) error = HuffmanTree_makeFromLengths(codetreeD, &bitlenD.data[0], bitlenD.size, 15); + + uivector_cleanup(&bitlen); + uivector_cleanup(&bitlenD); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength, unsigned btype) +{ + unsigned endreached = 0, error = 0; + HuffmanTree codetree; /*287, the code tree for Huffman codes*/ + HuffmanTree codetreeD; /*31, the code tree for distance codes*/ + + HuffmanTree_init(&codetree); + HuffmanTree_init(&codetreeD); + + if(btype == 1) getTreeInflateFixed(&codetree, &codetreeD); + else if(btype == 2) + { + HuffmanTree codelengthcodetree; /*18, the code tree for code length codes*/ + HuffmanTree_init(&codelengthcodetree); + error = getTreeInflateDynamic(&codetree, &codetreeD, &codelengthcodetree, in, bp, inlength); + HuffmanTree_cleanup(&codelengthcodetree); + } + + while(!endreached && !error) + { + unsigned code = huffmanDecodeSymbol(&error, in, bp, &codetree, inlength); + if(error) break; /*some error happened in the above function*/ + if(code == 256) endreached = 1; /*end code*/ + else if(code <= 255) /*literal symbol*/ + { + if((*pos) >= out->size) ucvector_resize(out, ((*pos) + 1) * 2); /*reserve more room at once*/ + if((*pos) >= out->size) { error = 9913; break; } /*not enough memory*/ + out->data[(*pos)] = (unsigned char)(code); + (*pos)++; + } + else if(code >= FIRST_LENGTH_CODE_INDEX && code <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + /*part 1: get length base*/ + size_t length = LENGTHBASE[code - FIRST_LENGTH_CODE_INDEX]; + unsigned codeD, distance, numextrabitsD; + size_t start, forward, backward, numextrabits; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits = LENGTHEXTRA[code - FIRST_LENGTH_CODE_INDEX]; + if(((*bp) >> 3) >= inlength) { error = 51; break; } /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits); + + /*part 3: get distance code*/ + codeD = huffmanDecodeSymbol(&error, in, bp, &codetreeD, inlength); + if(error) break; + if(codeD > 29) { error = 18; break; } /*error: invalid distance code (30-31 are never used)*/ + distance = DISTANCEBASE[codeD]; + + /*part 4: get extra bits from distance*/ + numextrabitsD = DISTANCEEXTRA[codeD]; + if(((*bp) >> 3) >= inlength) { error = 51; break; } /*error, bit pointer will jump past memory*/ + distance += readBitsFromStream(bp, in, numextrabitsD); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + backward = start - distance; + if((*pos) + length >= out->size) ucvector_resize(out, ((*pos) + length) * 2); /*reserve more room at once*/ + if((*pos) + length >= out->size) { error = 9914; break; } /*not enough memory*/ + + for(forward = 0; forward < length; forward++) + { + out->data[(*pos)] = out->data[backward]; + (*pos)++; + backward++; + if(backward >= start) backward = start - distance; + } + } + } + + HuffmanTree_cleanup(&codetree); + HuffmanTree_cleanup(&codetreeD); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + /*go to first boundary of byte*/ + size_t p; + unsigned LEN, NLEN, n, error = 0; + while(((*bp) & 0x7) != 0) (*bp)++; + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p >= inlength - 4) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256 * in[p + 1]; p += 2; + NLEN = in[p] + 256 * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if((*pos) + LEN >= out->size) { if(!ucvector_resize(out, (*pos) + LEN)) return 9915; } + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; n++) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +/*inflate the deflated data (cfr. deflate spec); return value is the error*/ +unsigned LodeFlate_inflate(ucvector* out, const unsigned char* in, size_t insize, size_t inpos) +{ + size_t bp = 0; /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + + unsigned error = 0; + + while(!BFINAL) + { + unsigned BTYPE; + if((bp >> 3) >= insize) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, &in[inpos]); + BTYPE = 1 * readBitFromStream(&bp, &in[inpos]); BTYPE += 2 * readBitFromStream(&bp, &in[inpos]); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &in[inpos], &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, &in[inpos], &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + if(error) return error; + } + + if(!ucvector_resize(out, pos)) error = 9916; /*Only now we know the true size of out, resize it to that*/ + + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*linear search implementation*/ + /*for(size_t i = 1; i < array_size; i++) if(array[i] > value) return i - 1; + return array_size - 1;*/ + + /*binary search implementation (not that much faster) (precondition: array_size > 0)*/ + size_t left = 1; + size_t right = array_size - 1; + while(left <= right) + { + size_t mid = (left + right) / 2; + if(array[mid] <= value) left = mid + 1; /*the value to find is more to the right*/ + else if(array[mid - 1] > value) right = mid - 1; /*the value to find is more to the left*/ + else return mid - 1; + } + return array_size - 1; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +#if 0 +/*the "brute force" version of the encodeLZ7 algorithm, not used anymore, kept here for reference*/ +static void encodeLZ77_brute(uivector* out, const unsigned char* in, size_t size, unsigned windowSize) +{ + size_t pos; + /*using pointer instead of vector for input makes it faster when NOT using optimization when compiling; no influence if optimization is used*/ + for(pos = 0; pos < size; pos++) + { + size_t length = 0, offset = 0; /*the length and offset found for the current position*/ + size_t max_offset = pos < windowSize ? pos : windowSize; /*how far back to test*/ + size_t current_offset; + + /**search for the longest string**/ + for(current_offset = 1; current_offset < max_offset; current_offset++) /*search backwards through all possible distances (=offsets)*/ + { + size_t backpos = pos - current_offset; + if(in[backpos] == in[pos]) + { + /*test the next characters*/ + size_t current_length = 1; + size_t backtest = backpos + 1; + size_t foretest = pos + 1; + while(foretest < size && in[backtest] == in[foretest] && current_length < MAX_SUPPORTED_DEFLATE_LENGTH) /*maximum supporte length by deflate is max length*/ + { + if(backpos >= pos) backpos -= current_offset; /*continue as if we work on the decoded bytes after pos by jumping back before pos*/ + current_length++; + backtest++; + foretest++; + } + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + if(current_length == MAX_SUPPORTED_DEFLATE_LENGTH) break; /*you can jump out of this for loop once a length of max length is found (gives significant speed gain)*/ + } + } + } + + /**encode it as length/distance pair or literal value**/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + uivector_push_back(out, in[pos]); + } + else + { + addLengthDistance(out, length, offset); + pos += (length - 1); + } + } /*end of the loop through each character of input*/ +} +#endif + +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_NUM_CHARACTERS = 6; +static const unsigned HASH_SHIFT = 2; +/* +Good and fast values: HASH_NUM_VALUES=65536, HASH_NUM_CHARACTERS=6, HASH_SHIFT=2 +making HASH_NUM_CHARACTERS larger (like 8), makes the file size larger but is a bit faster +making HASH_NUM_CHARACTERS smaller (like 3), makes the file size smaller but is slower +*/ + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) +{ + unsigned result = 0; + size_t amount, i; + if(pos >= size) return 0; + amount = HASH_NUM_CHARACTERS; if(pos + amount >= size) amount = size - pos; + for(i = 0; i < amount; i++) result ^= (data[pos + i] << (i * HASH_SHIFT)); + return result % HASH_NUM_VALUES; +} + +/*LZ77-encode the data using a hash table technique to let it encode faster. Return value is error code*/ +static unsigned encodeLZ77(uivector* out, const unsigned char* in, size_t size, unsigned windowSize) +{ + /**generate hash table**/ + vector table; /*HASH_NUM_VALUES uivectors; this represents what would be an std::vector > in C++*/ + uivector tablepos1, tablepos2; + unsigned pos, i, error = 0; + + vector_init(&table, sizeof(uivector)); + if(!vector_resize(&table, HASH_NUM_VALUES)) return 9917; + for(i = 0; i < HASH_NUM_VALUES; i++) + { + uivector* v = (uivector*)vector_get(&table, i); + uivector_init(v); + } + + /*remember start and end positions in the tables to searching in*/ + uivector_init(&tablepos1); + uivector_init(&tablepos2); + if(!uivector_resizev(&tablepos1, HASH_NUM_VALUES, 0)) error = 9918; + if(!uivector_resizev(&tablepos2, HASH_NUM_VALUES, 0)) error = 9919; + + if(!error) + { + for(pos = 0; pos < size; pos++) + { + unsigned length = 0, offset = 0; /*the length and offset found for the current position*/ + unsigned max_offset = pos < windowSize ? pos : windowSize; /*how far back to test*/ + unsigned tablepos; + + /*/search for the longest string*/ + /*first find out where in the table to start (the first value that is in the range from "pos - max_offset" to "pos")*/ + unsigned hash = getHash(in, size, pos); + if(!uivector_push_back((uivector*)vector_get(&table, hash), pos)) { error = 9920; break; } + + while(((uivector*)vector_get(&table, hash))->data[tablepos1.data[hash]] < pos - max_offset) tablepos1.data[hash]++; /*it now points to the first value in the table for which the index is larger than or equal to pos - max_offset*/ + while(((uivector*)vector_get(&table, hash))->data[tablepos2.data[hash]] < pos) tablepos2.data[hash]++; /*it now points to the first value in the table for which the index is larger than or equal to pos*/ + + for(tablepos = tablepos2.data[hash] - 1; tablepos >= tablepos1.data[hash] && tablepos < tablepos2.data[hash]; tablepos--) + { + unsigned backpos = ((uivector*)vector_get(&table, hash))->data[tablepos]; + unsigned current_offset = pos - backpos; + + /*test the next characters*/ + unsigned current_length = 0; + unsigned backtest = backpos; + unsigned foretest = pos; + while(foretest < size && in[backtest] == in[foretest] && current_length < MAX_SUPPORTED_DEFLATE_LENGTH) /*maximum supporte length by deflate is max length*/ + { + if(backpos >= pos) backpos -= current_offset; /*continue as if we work on the decoded bytes after pos by jumping back before pos*/ + current_length++; + backtest++; + foretest++; + } + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + if(current_length == MAX_SUPPORTED_DEFLATE_LENGTH) break; /*you can jump out of this for loop once a length of max length is found (gives significant speed gain)*/ + } + } + + /**encode it as length/distance pair or literal value**/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) { error = 9921; break; } + } + else + { + unsigned j; + addLengthDistance(out, length, offset); + for(j = 0; j < length - 1; j++) + { + pos++; + if(!uivector_push_back((uivector*)vector_get(&table, getHash(in, size, pos)), pos)) { error = 9922; break; } + } + } + } /*end of the loop through each character of input*/ + } /*end of "if(!error)"*/ + + /*cleanup*/ + for(i = 0; i < table.size; i++) + { + uivector* v = (uivector*)vector_get(&table, i); + uivector_cleanup(v); + } + vector_cleanup(&table); + uivector_cleanup(&tablepos1); + uivector_cleanup(&tablepos2); + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = datasize / 65536 + 1; + unsigned datapos = 0; + for(i = 0; i < numdeflateblocks; i++) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN % 256)); + ucvector_push_back(out, (unsigned char)(LEN / 256)); + ucvector_push_back(out, (unsigned char)(NLEN % 256)); + ucvector_push_back(out, (unsigned char)(NLEN / 256)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; j++) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/*write the encoded data, using lit/len as well as distance codes*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, const HuffmanTree* codes, const HuffmanTree* codesD) +{ + size_t i = 0; + for(i = 0; i < lz77_encoded->size; i++) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(codes, val), HuffmanTree_getLength(codes, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(codesD, distance_code), HuffmanTree_getLength(codesD, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +static unsigned deflateDynamic(ucvector* out, const unsigned char* data, size_t datasize, const LodeZlib_DeflateSettings* settings) +{ + /* + after the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + unsigned error = 0; + + uivector lz77_encoded; + HuffmanTree codes; /*tree for literal values and length codes*/ + HuffmanTree codesD; /*tree for distance codes*/ + HuffmanTree codelengthcodes; + uivector frequencies; + uivector frequenciesD; + uivector amounts; /*the amounts in the "normal" order*/ + uivector lldl; + uivector lldll; /*lit/len & dist code lenghts*/ + uivector clcls; + + unsigned BFINAL = 1; /*make only one block... the first and final one*/ + size_t numcodes, numcodesD, i, bp = 0; /*the bit pointer*/ + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&codes); + HuffmanTree_init(&codesD); + HuffmanTree_init(&codelengthcodes); + uivector_init(&frequencies); + uivector_init(&frequenciesD); + uivector_init(&amounts); + uivector_init(&lldl); + uivector_init(&lldll); + uivector_init(&clcls); + + while(!error) /*the goto-avoiding while construct: break out to go to the cleanup phase, a break at the end makes sure the while is never repeated*/ + { + if(settings->useLZ77) + { + error = encodeLZ77(&lz77_encoded, data, datasize, settings->windowSize); /*LZ77 encoded*/ + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) { error = 9923; break; } + for(i = 0; i < datasize; i++) lz77_encoded.data[i] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies, 286, 0)) { error = 9924; break; } + if(!uivector_resizev(&frequenciesD, 30, 0)) { error = 9925; break; } + for(i = 0; i < lz77_encoded.size; i++) + { + unsigned symbol = lz77_encoded.data[i]; + frequencies.data[symbol]++; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + frequenciesD.data[dist]++; + i += 3; + } + } + frequencies.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + error = HuffmanTree_makeFromFrequencies(&codes, frequencies.data, frequencies.size, 15); + if(error) break; + error = HuffmanTree_makeFromFrequencies(&codesD, frequenciesD.data, frequenciesD.size, 15); + if(error) break; + + addBitToStream(&bp, out, BFINAL); + addBitToStream(&bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(&bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + numcodes = codes.numcodes; if(numcodes > 286) numcodes = 286; + numcodesD = codesD.numcodes; if(numcodesD > 30) numcodesD = 30; + for(i = 0; i < numcodes; i++) uivector_push_back(&lldll, HuffmanTree_getLength(&codes, (unsigned)i)); + for(i = 0; i < numcodesD; i++) uivector_push_back(&lldll, HuffmanTree_getLength(&codesD, (unsigned)i)); + + /*make lldl smaller by using repeat codes 16 (copy length 3-6 times), 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i < (unsigned)lldll.size; i++) + { + unsigned j = 0; + while(i + j + 1 < (unsigned)lldll.size && lldll.data[i + j + 1] == lldll.data[i]) j++; + + if(lldll.data[i] == 0 && j >= 2) + { + j++; /*include the first zero*/ + if(j <= 10) { uivector_push_back(&lldl, 17); uivector_push_back(&lldl, j - 3); } + else + { + if(j > 138) j = 138; + uivector_push_back(&lldl, 18); uivector_push_back(&lldl, j - 11); + } + i += (j - 1); + } + else if(j >= 3) + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&lldl, lldll.data[i]); + for(k = 0; k < num; k++) { uivector_push_back(&lldl, 16); uivector_push_back(&lldl, 6 - 3); } + if(rest >= 3) { uivector_push_back(&lldl, 16); uivector_push_back(&lldl, rest - 3); } + else j -= rest; + i += j; + } + else uivector_push_back(&lldl, lldll.data[i]); + } + + /*generate huffmantree for the length codes of lit/len and dist codes*/ + if(!uivector_resizev(&amounts, 19, 0)) { error = 9926; break; } /*16 possible lengths (0-15) and 3 repeat codes (16, 17 and 18)*/ + for(i = 0; i < lldl.size; i++) + { + amounts.data[lldl.data[i]]++; + if(lldl.data[i] >= 16) i++; /*after a repeat code come the bits that specify the amount, those don't need to be in the amounts calculation*/ + } + + error = HuffmanTree_makeFromFrequencies(&codelengthcodes, amounts.data, amounts.size, 7); + if(error) break; + + if(!uivector_resize(&clcls, 19)) { error = 9927; break; } + for(i = 0; i < 19; i++) clcls.data[i] = HuffmanTree_getLength(&codelengthcodes, CLCL[i]); /*lenghts of code length tree is in the order as specified by deflate*/ + while(clcls.data[clcls.size - 1] == 0 && clcls.size > 4) + { + if(!uivector_resize(&clcls, clcls.size - 1)) { error = 9928; break; } /*remove zeros at the end, but minimum size must be 4*/ + } + if(error) break; + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes - 257); + HDIST = (unsigned)(numcodesD - 1); + HCLEN = (unsigned)clcls.size - 4; + addBitsToStream(&bp, out, HLIT, 5); + addBitsToStream(&bp, out, HDIST, 5); + addBitsToStream(&bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i < HCLEN + 4; i++) addBitsToStream(&bp, out, clcls.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i < lldl.size; i++) + { + addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&codelengthcodes, lldl.data[i]), HuffmanTree_getLength(&codelengthcodes, lldl.data[i])); + /*extra bits of repeat codes*/ + if(lldl.data[i] == 16) addBitsToStream(&bp, out, lldl.data[++i], 2); + else if(lldl.data[i] == 17) addBitsToStream(&bp, out, lldl.data[++i], 3); + else if(lldl.data[i] == 18) addBitsToStream(&bp, out, lldl.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(&bp, out, &lz77_encoded, &codes, &codesD); + if(HuffmanTree_getLength(&codes, 256) == 0) { error = 64; break; } /*the length of the end code 256 must be larger than 0*/ + addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&codes, 256), HuffmanTree_getLength(&codes, 256)); /*end code*/ + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&codes); + HuffmanTree_cleanup(&codesD); + HuffmanTree_cleanup(&codelengthcodes); + uivector_cleanup(&frequencies); + uivector_cleanup(&frequenciesD); + uivector_cleanup(&amounts); + uivector_cleanup(&lldl); + uivector_cleanup(&lldll); + uivector_cleanup(&clcls); + + return error; +} + +static unsigned deflateFixed(ucvector* out, const unsigned char* data, size_t datasize, const LodeZlib_DeflateSettings* settings) +{ + HuffmanTree codes; /*tree for literal values and length codes*/ + HuffmanTree codesD; /*tree for distance codes*/ + + unsigned BFINAL = 1; /*make only one block... the first and final one*/ + unsigned error = 0; + size_t i, bp = 0; /*the bit pointer*/ + + HuffmanTree_init(&codes); + HuffmanTree_init(&codesD); + + generateFixedTree(&codes); + generateDistanceTree(&codesD); + + addBitToStream(&bp, out, BFINAL); + addBitToStream(&bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(&bp, out, 0); /*second bit of BTYPE*/ + + if(settings->useLZ77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, data, datasize, settings->windowSize); + if(!error) writeLZ77data(&bp, out, &lz77_encoded, &codes, &codesD); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = 0; i < datasize; i++) addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&codes, data[i]), HuffmanTree_getLength(&codes, data[i])); + } + if(!error) addHuffmanSymbol(&bp, out, HuffmanTree_getCode(&codes, 256), HuffmanTree_getLength(&codes, 256)); /*"end" code*/ + + /*cleanup*/ + HuffmanTree_cleanup(&codes); + HuffmanTree_cleanup(&codesD); + + return error; +} + +unsigned LodeFlate_deflate(ucvector* out, const unsigned char* data, size_t datasize, const LodeZlib_DeflateSettings* settings) +{ + unsigned error = 0; + if(settings->btype == 0) error = deflateNoCompression(out, data, datasize); + else if(settings->btype == 1) error = deflateFixed(out, data, datasize, settings); + else if(settings->btype == 2) error = deflateDynamic(out, data, datasize, settings); + else error = 61; + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5550 sums can be done before the sums overflow, saving us from a lot of module divisions*/ + unsigned amount = len > 5550 ? 5550 : len; + len -= amount; + while(amount > 0) + { + s1 = (s1 + *data++); + s2 = (s2 + s1); + amount--; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER +void LodeZlib_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_push_back(buffer, (unsigned char)((value >> 24) & 0xff)); + ucvector_push_back(buffer, (unsigned char)((value >> 16) & 0xff)); + ucvector_push_back(buffer, (unsigned char)((value >> 8) & 0xff)); + ucvector_push_back(buffer, (unsigned char)((value ) & 0xff)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +unsigned LodeZlib_read32bitInt(const unsigned char* buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned LodeZlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + ucvector outv; + + if(insize < 2) { error = 53; return error; } /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { error = 24; return error; } /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31; //FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3; //not really important, all it does it to give a compiler warning about unused variable, we don't care what encoding setting the encoder used*/ + + if(CM != 8 || CINFO > 7) { error = 25; return error; } /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + if(FDICT != 0) { error = 26; return error; } /*error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary."*/ + + ucvector_init_buffer(&outv, *out, *outsize); /*ucvector-controlled version of the output buffer, for dynamic array*/ + error = LodeFlate_inflate(&outv, in, insize, 2); + *out = outv.data; + *outsize = outv.size; + if(error) return error; + + if(!settings->ignoreAdler32) + { + unsigned ADLER32 = LodeZlib_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(outv.data, (unsigned)outv.size); + if(checksum != ADLER32) { error = 58; return error; } + } + + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned LodeZlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DeflateSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out that's pointing to a non allocated buffer, this'll crash*/ + ucvector deflatedata, outv; + size_t i; + unsigned error; + + unsigned ADLER32; + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + ucvector_init_buffer(&outv, *out, *outsize); /*ucvector-controlled version of the output buffer, for dynamic array*/ + + ucvector_push_back(&outv, (unsigned char)(CMFFLG / 256)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG % 256)); + + ucvector_init(&deflatedata); + error = LodeFlate_deflate(&deflatedata, in, insize, settings); + + if(!error) + { + ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i < deflatedata.size; i++) ucvector_push_back(&outv, deflatedata.data[i]); + ucvector_cleanup(&deflatedata); + LodeZlib_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +void LodeZlib_DeflateSettings_init(LodeZlib_DeflateSettings* settings) +{ + settings->btype = 2; /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->useLZ77 = 1; + settings->windowSize = 2048; /*this is a good tradeoff between speed and compression ratio*/ +} + +const LodeZlib_DeflateSettings LodeZlib_defaultDeflateSettings = {2, 1, 2048}; + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void LodeZlib_DecompressSettings_init(LodeZlib_DecompressSettings* settings) +{ + settings->ignoreAdler32 = 0; +} + +const LodeZlib_DecompressSettings LodeZlib_defaultDecompressSettings = {0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code, now comes the PNG related code that uses it// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* +The two functions below (LodePNG_decompress and LodePNG_compress) directly call the +LodeZlib_decompress and LodeZlib_compress functions. The only purpose of the functions +below, is to provide the ability to let LodePNG use a different Zlib encoder by only +changing the two functions below, instead of changing it inside the vareous places +in the other LodePNG functions. + +*out must be NULL and *outsize must be 0 initially, and after the function is done, +*out must point to the decompressed data, *outsize must be the size of it, and must +be the size of the useful data in bytes, not the alloc size. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned LodePNG_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DecompressSettings* settings) +{ + return LodeZlib_decompress(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned LodePNG_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DeflateSettings* settings) +{ + return LodeZlib_compress(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned Crc32_crc_table_computed = 0; +static unsigned Crc32_crc_table[256]; + +/*Make the table for a fast CRC.*/ +static void Crc32_make_crc_table(void) +{ + unsigned c, k, n; + for(n = 0; n < 256; n++) + { + c = n; + for(k = 0; k < 8; k++) + { + if(c & 1) c = 0xedb88320L ^ (c >> 1); + else c = c >> 1; + } + Crc32_crc_table[n] = c; + } + Crc32_crc_table_computed = 1; +} + +/*Update a running CRC with the bytes buf[0..len-1]--the CRC should be +initialized to all 1's, and the transmitted value is the 1's complement of the +final running CRC (see the crc() routine below).*/ +static unsigned Crc32_update_crc(const unsigned char* buf, unsigned crc, size_t len) +{ + unsigned c = crc; + size_t n; + + if(!Crc32_crc_table_computed) Crc32_make_crc_table(); + for(n = 0; n < len; n++) + { + c = Crc32_crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c; +} + +/*Return the CRC of the bytes buf[0..len-1].*/ +static unsigned Crc32_crc(const unsigned char* buf, size_t len) +{ + return Crc32_update_crc(buf, 0xffffffffL, len) ^ 0xffffffffL; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = nbits - 1; i < nbits; i--) result += (unsigned)readBitFromReversedStream(bitpointer, bitstream) << i; + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + (*bitpointer)++; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + (*bitpointer)++; +} + +static unsigned LodePNG_read32bitInt(const unsigned char* buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +static void LodePNG_set32bitInt(unsigned char* buffer, unsigned value) /*buffer must have at least 4 allocated bytes available*/ +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} + +#ifdef LODEPNG_COMPILE_ENCODER +static void LodePNG_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); + LodePNG_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned LodePNG_chunk_length(const unsigned char* chunk) /*get the length of the data of the chunk. Total chunk length has 12 bytes more.*/ +{ + return LodePNG_read32bitInt(&chunk[0]); +} + +void LodePNG_chunk_type(char type[5], const unsigned char* chunk) /*puts the 4-byte type in null terminated string*/ +{ + unsigned i; + for(i = 0; i < 4; i++) type[i] = chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type) /*check if the type is the given type*/ +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +/*properties of PNG chunks gotten from capitalization of chunk type name, as defined by the standard*/ +unsigned char LodePNG_chunk_critical(const unsigned char* chunk) /*0: ancillary chunk, 1: it's one of the critical chunk types*/ +{ + return((chunk[4] & 32) == 0); +} + +unsigned char LodePNG_chunk_private(const unsigned char* chunk) /*0: public, 1: private*/ +{ + return((chunk[6] & 32) != 0); +} + +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk) /*0: the chunk is unsafe to copy, 1: the chunk is safe to copy*/ +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* LodePNG_chunk_data(unsigned char* chunk) /*get pointer to the data of the chunk*/ +{ + return &chunk[8]; +} + +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk) /*get pointer to the data of the chunk*/ +{ + return &chunk[8]; +} + +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk) /*returns 0 if the crc is correct, error code if it's incorrect*/ +{ + unsigned length = LodePNG_chunk_length(chunk); + unsigned CRC = LodePNG_read32bitInt(&chunk[length + 8]); + unsigned checksum = Crc32_crc(&chunk[4], length + 4); /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + if(CRC != checksum) return 1; + else return 0; +} + +void LodePNG_chunk_generate_crc(unsigned char* chunk) /*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +{ + unsigned length = LodePNG_chunk_length(chunk); + unsigned CRC = Crc32_crc(&chunk[4], length + 4); + LodePNG_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* LodePNG_chunk_next(unsigned char* chunk) /*don't use on IEND chunk, as there is no next chunk then*/ +{ + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk) /*don't use on IEND chunk, as there is no next chunk then*/ +{ + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk) /*appends chunk that was already created, to the data. Returns error code.*/ +{ + unsigned i; + unsigned total_chunk_length = LodePNG_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)realloc(*out, new_length); + if(!new_buffer) return 9929; + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i < total_chunk_length; i++) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, const char* type, const unsigned char* data) /*appends new chunk to out. Returns error code; may change memory address of out buffer*/ +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)realloc(*out, new_length); + if(!new_buffer) return 9930; + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + LodePNG_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = type[0]; + chunk[5] = type[1]; + chunk[6] = type[2]; + chunk[7] = type[3]; + + /*3: the data*/ + for(i = 0; i < length; i++) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + LodePNG_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(unsigned colorType, unsigned bd) /*bd = bitDepth*/ +{ + switch(colorType) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(unsigned colorType) +{ + switch(colorType) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned getBpp(unsigned colorType, unsigned bitDepth) +{ + return getNumColorChannels(colorType) * bitDepth; /*bits per pixel is amount of channels * bits per channel*/ +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void LodePNG_InfoColor_init(LodePNG_InfoColor* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colorType = 6; + info->bitDepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void LodePNG_InfoColor_cleanup(LodePNG_InfoColor* info) +{ + LodePNG_InfoColor_clearPalette(info); +} + +void LodePNG_InfoColor_clearPalette(LodePNG_InfoColor* info) +{ + if(info->palette) free(info->palette); + info->palettesize = 0; +} + +unsigned LodePNG_InfoColor_addPalette(LodePNG_InfoColor* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with the max of 256 colors, it'll have the exact alloc size*/ + if(!(info->palettesize & (info->palettesize - 1))) /*if palettesize is 0 or a power of two*/ + { + /*allocated data must be at least 4* palettesize (for 4 color bytes)*/ + size_t alloc_size = info->palettesize == 0 ? 4 : info->palettesize * 4 * 2; + data = (unsigned char*)realloc(info->palette, alloc_size); + if(!data) return 9931; + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + info->palettesize++; + return 0; +} + +unsigned LodePNG_InfoColor_getBpp(const LodePNG_InfoColor* info) { return getBpp(info->colorType, info->bitDepth); } /*calculate bits per pixel out of colorType and bitDepth*/ +unsigned LodePNG_InfoColor_getChannels(const LodePNG_InfoColor* info) { return getNumColorChannels(info->colorType); } +unsigned LodePNG_InfoColor_isGreyscaleType(const LodePNG_InfoColor* info) { return info->colorType == 0 || info->colorType == 4; } +unsigned LodePNG_InfoColor_isAlphaType(const LodePNG_InfoColor* info) { return (info->colorType & 4) != 0; } + +unsigned LodePNG_InfoColor_equal(const LodePNG_InfoColor* info1, const LodePNG_InfoColor* info2) +{ + return info1->colorType == info2->colorType + && info1->bitDepth == info2->bitDepth; /*palette and color key not compared*/ +} + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + +void LodePNG_UnknownChunks_init(LodePNG_UnknownChunks* chunks) +{ + unsigned i; + for(i = 0; i < 3; i++) chunks->data[i] = 0; + for(i = 0; i < 3; i++) chunks->datasize[i] = 0; +} + +void LodePNG_UnknownChunks_cleanup(LodePNG_UnknownChunks* chunks) +{ + unsigned i; + for(i = 0; i < 3; i++) free(chunks->data[i]); +} + +unsigned LodePNG_UnknownChunks_copy(LodePNG_UnknownChunks* dest, const LodePNG_UnknownChunks* src) +{ + unsigned i; + + LodePNG_UnknownChunks_cleanup(dest); + + for(i = 0; i < 3; i++) + { + size_t j; + dest->datasize[i] = src->datasize[i]; + dest->data[i] = (unsigned char*)malloc(src->datasize[i]); + if(!dest->data[i] && dest->datasize[i]) return 9932; + for(j = 0; j < src->datasize[i]; j++) dest->data[i][j] = src->data[i][j]; + } + + return 0; +} + +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +void LodePNG_Text_init(LodePNG_Text* text) +{ + text->num = 0; + text->keys = NULL; + text->strings = NULL; +} + +void LodePNG_Text_cleanup(LodePNG_Text* text) +{ + LodePNG_Text_clear(text); +} + +unsigned LodePNG_Text_copy(LodePNG_Text* dest, const LodePNG_Text* source) +{ + size_t i = 0; + dest->keys = 0; + dest->strings = 0; + dest->num = 0; + for(i = 0; i < source->num; i++) + { + unsigned error = LodePNG_Text_add(dest, source->keys[i], source->strings[i]); + if(error) return error; + } + return 0; +} + +void LodePNG_Text_clear(LodePNG_Text* text) +{ + size_t i; + for(i = 0; i < text->num; i++) + { + string_cleanup(&text->keys[i]); + string_cleanup(&text->strings[i]); + } + free(text->keys); + free(text->strings); +} + +unsigned LodePNG_Text_add(LodePNG_Text* text, const char* key, const char* str) +{ + char** new_keys = (char**)(realloc(text->keys, sizeof(char*) * (text->num + 1))); + char** new_strings = (char**)(realloc(text->strings, sizeof(char*) * (text->num + 1))); + if(!new_keys || !new_strings) + { + free(new_keys); + free(new_strings); + return 9933; + } + + text->num++; + text->keys = new_keys; + text->strings = new_strings; + + string_init(&text->keys[text->num - 1]); + string_set(&text->keys[text->num - 1], key); + + string_init(&text->strings[text->num - 1]); + string_set(&text->strings[text->num - 1], str); + + return 0; +} + +/******************************************************************************/ + +void LodePNG_IText_init(LodePNG_IText* text) +{ + text->num = 0; + text->keys = NULL; + text->langtags = NULL; + text->transkeys = NULL; + text->strings = NULL; +} + +void LodePNG_IText_cleanup(LodePNG_IText* text) +{ + LodePNG_IText_clear(text); +} + +unsigned LodePNG_IText_copy(LodePNG_IText* dest, const LodePNG_IText* source) +{ + size_t i = 0; + dest->keys = 0; + dest->langtags = 0; + dest->transkeys = 0; + dest->strings = 0; + dest->num = 0; + for(i = 0; i < source->num; i++) + { + unsigned error = LodePNG_IText_add(dest, source->keys[i], source->langtags[i], source->transkeys[i], source->strings[i]); + if(error) return error; + } + return 0; +} + +void LodePNG_IText_clear(LodePNG_IText* text) +{ + size_t i; + for(i = 0; i < text->num; i++) + { + string_cleanup(&text->keys[i]); + string_cleanup(&text->langtags[i]); + string_cleanup(&text->transkeys[i]); + string_cleanup(&text->strings[i]); + } + free(text->keys); + free(text->langtags); + free(text->transkeys); + free(text->strings); +} + +unsigned LodePNG_IText_add(LodePNG_IText* text, const char* key, const char* langtag, const char* transkey, const char* str) +{ + char** new_keys = (char**)(realloc(text->keys, sizeof(char*) * (text->num + 1))); + char** new_langtags = (char**)(realloc(text->langtags, sizeof(char*) * (text->num + 1))); + char** new_transkeys = (char**)(realloc(text->transkeys, sizeof(char*) * (text->num + 1))); + char** new_strings = (char**)(realloc(text->strings, sizeof(char*) * (text->num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + free(new_keys); + free(new_langtags); + free(new_transkeys); + free(new_strings); + return 9934; + } + + text->num++; + text->keys = new_keys; + text->langtags = new_langtags; + text->transkeys = new_transkeys; + text->strings = new_strings; + + string_init(&text->keys[text->num - 1]); + string_set(&text->keys[text->num - 1], key); + + string_init(&text->langtags[text->num - 1]); + string_set(&text->langtags[text->num - 1], langtag); + + string_init(&text->transkeys[text->num - 1]); + string_set(&text->transkeys[text->num - 1], transkey); + + string_init(&text->strings[text->num - 1]); + string_set(&text->strings[text->num - 1], str); + + return 0; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void LodePNG_InfoPng_init(LodePNG_InfoPng* info) +{ + info->width = info->height = 0; + LodePNG_InfoColor_init(&info->color); + info->interlaceMethod = 0; + info->compressionMethod = 0; + info->filterMethod = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNG_Text_init(&info->text); + LodePNG_IText_init(&info->itext); + + info->time_defined = 0; + info->phys_defined = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_init(&info->unknown_chunks); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} + +void LodePNG_InfoPng_cleanup(LodePNG_InfoPng* info) +{ + LodePNG_InfoColor_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNG_Text_cleanup(&info->text); + LodePNG_IText_cleanup(&info->itext); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_cleanup(&info->unknown_chunks); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} + +unsigned LodePNG_InfoPng_copy(LodePNG_InfoPng* dest, const LodePNG_InfoPng* source) +{ + unsigned error = 0; + LodePNG_InfoPng_cleanup(dest); + *dest = *source; + LodePNG_InfoColor_init(&dest->color); + error = LodePNG_InfoColor_copy(&dest->color, &source->color); if(error) return error; + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + error = LodePNG_Text_copy(&dest->text, &source->text); if(error) return error; + error = LodePNG_IText_copy(&dest->itext, &source->itext); if(error) return error; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + LodePNG_UnknownChunks_init(&dest->unknown_chunks); + error = LodePNG_UnknownChunks_copy(&dest->unknown_chunks, &source->unknown_chunks); if(error) return error; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + return error; +} + +void LodePNG_InfoPng_swap(LodePNG_InfoPng* a, LodePNG_InfoPng* b) +{ + LodePNG_InfoPng temp = *a; + *a = *b; + *b = temp; +} + +unsigned LodePNG_InfoColor_copy(LodePNG_InfoColor* dest, const LodePNG_InfoColor* source) +{ + size_t i; + LodePNG_InfoColor_cleanup(dest); + *dest = *source; + dest->palette = (unsigned char*)malloc(source->palettesize * 4); + if(!dest->palette && source->palettesize) return 9935; + for(i = 0; i < source->palettesize * 4; i++) dest->palette[i] = source->palette[i]; + return 0; +} + +void LodePNG_InfoRaw_init(LodePNG_InfoRaw* info) +{ + LodePNG_InfoColor_init(&info->color); +} + +void LodePNG_InfoRaw_cleanup(LodePNG_InfoRaw* info) +{ + LodePNG_InfoColor_cleanup(&info->color); +} + +unsigned LodePNG_InfoRaw_copy(LodePNG_InfoRaw* dest, const LodePNG_InfoRaw* source) +{ + unsigned error = 0; + LodePNG_InfoRaw_cleanup(dest); + *dest = *source; + LodePNG_InfoColor_init(&dest->color); + error = LodePNG_InfoColor_copy(&dest->color, &source->color); if(error) return error; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +converts from any color type to 24-bit or 32-bit (later maybe more supported). return value = LodePNG error code +the out buffer must have (w * h * bpp + 7) / 8 bytes, where bpp is the bits per pixel of the output color type (LodePNG_InfoColor_getBpp) +for < 8 bpp images, there may _not_ be padding bits at the end of scanlines. +*/ +unsigned LodePNG_convert(unsigned char* out, const unsigned char* in, LodePNG_InfoColor* infoOut, LodePNG_InfoColor* infoIn, unsigned w, unsigned h) +{ + const size_t numpixels = w * h; /*amount of pixels*/ + const unsigned OUT_BYTES = LodePNG_InfoColor_getBpp(infoOut) / 8; /*bytes per pixel in the output image*/ + const unsigned OUT_ALPHA = LodePNG_InfoColor_isAlphaType(infoOut); /*use 8-bit alpha channel*/ + size_t i, c, bp = 0; /*bitpointer, used by less-than-8-bit color types*/ + + /*cases where in and out already have the same format*/ + if(LodePNG_InfoColor_equal(infoIn, infoOut)) + { + size_t i, size = (w * h * LodePNG_InfoColor_getBpp(infoIn) + 7) / 8; + for(i = 0; i < size; i++) out[i] = in[i]; + return 0; + } + + if((infoOut->colorType == 2 || infoOut->colorType == 6) && infoOut->bitDepth == 8) + { + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + out[OUT_BYTES * i + 0] = out[OUT_BYTES * i + 1] = out[OUT_BYTES * i + 2] = in[i]; + if(OUT_ALPHA && infoIn->key_defined && in[i] == infoIn->key_r) out[OUT_BYTES * i + 3] = 0; + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + for(c = 0; c < 3; c++) out[OUT_BYTES * i + c] = in[3 * i + c]; + if(OUT_ALPHA && infoIn->key_defined == 1 && in[3 * i + 0] == infoIn->key_r && in[3 * i + 1] == infoIn->key_g && in[3 * i + 2] == infoIn->key_b) out[OUT_BYTES * i + 3] = 0; + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + if(in[i] >= infoIn->palettesize) return 46; + for(c = 0; c < OUT_BYTES; c++) out[OUT_BYTES * i + c] = infoIn->palette[4 * in[i] + c]; /*get rgb colors from the palette*/ + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[OUT_BYTES * i + 0] = out[OUT_BYTES * i + 1] = out[OUT_BYTES * i + 2] = in[2 * i + 0]; + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = in[2 * i + 1]; + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < OUT_BYTES; c++) out[OUT_BYTES * i + c] = in[4 * i + c]; + } + break; + default: break; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + out[OUT_BYTES * i + 0] = out[OUT_BYTES * i + 1] = out[OUT_BYTES * i + 2] = in[2 * i]; + if(OUT_ALPHA && infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r) out[OUT_BYTES * i + 3] = 0; + } + break; + case 2: /*RGB color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + for(c = 0; c < 3; c++) out[OUT_BYTES * i + c] = in[6 * i + 2 * c]; + if(OUT_ALPHA && infoIn->key_defined && 256U * in[6 * i + 0] + in[6 * i + 1] == infoIn->key_r && 256U * in[6 * i + 2] + in[6 * i + 3] == infoIn->key_g && 256U * in[6 * i + 4] + in[6 * i + 5] == infoIn->key_b) out[OUT_BYTES * i + 3] = 0; + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[OUT_BYTES * i + 0] = out[OUT_BYTES * i + 1] = out[OUT_BYTES * i + 2] = in[4 * i]; /*most significant byte*/ + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = in[4 * i + 2]; + } + break; + case 6: /*RGB with alpha*/ + for(i = 0; i < numpixels; i++) + { + for(c = 0; c < OUT_BYTES; c++) out[OUT_BYTES * i + c] = in[8 * i + 2 * c]; + } + break; + default: break; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + if(OUT_ALPHA && infoIn->key_defined && value && ((1U << infoIn->bitDepth) - 1U) == infoIn->key_r && ((1U << infoIn->bitDepth) - 1U)) out[OUT_BYTES * i + 3] = 0; + value = (value * 255) / ((1 << infoIn->bitDepth) - 1); /*scale value from 0 to 255*/ + out[OUT_BYTES * i + 0] = out[OUT_BYTES * i + 1] = out[OUT_BYTES * i + 2] = (unsigned char)(value); + } + break; + case 3: /*indexed color (palette)*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + if(OUT_ALPHA) out[OUT_BYTES * i + 3] = 255; + if(value >= infoIn->palettesize) return 47; + for(c = 0; c < OUT_BYTES; c++) out[OUT_BYTES * i + c] = infoIn->palette[4 * value + c]; /*get rgb colors from the palette*/ + } + break; + default: break; + } + } + } + else if(LodePNG_InfoColor_isGreyscaleType(infoOut) && infoOut->bitDepth == 8) /*conversion from greyscale to greyscale*/ + { + if(!LodePNG_InfoColor_isGreyscaleType(infoIn)) return 62; + if(infoIn->bitDepth == 8) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 1] = 255; + out[OUT_BYTES * i] = in[i]; + if(OUT_ALPHA && infoIn->key_defined && in[i] == infoIn->key_r) out[OUT_BYTES * i + 1] = 0; + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[OUT_BYTES * i + 0] = in[2 * i + 0]; + if(OUT_ALPHA) out[OUT_BYTES * i + 1] = in[2 * i + 1]; + } + break; + default: return 31; + } + } + else if(infoIn->bitDepth == 16) + { + switch(infoIn->colorType) + { + case 0: /*greyscale color*/ + for(i = 0; i < numpixels; i++) + { + if(OUT_ALPHA) out[OUT_BYTES * i + 1] = 255; + out[OUT_BYTES * i] = in[2 * i]; + if(OUT_ALPHA && infoIn->key_defined && 256U * in[i] + in[i + 1] == infoIn->key_r) out[OUT_BYTES * i + 1] = 0; + } + break; + case 4: /*greyscale with alpha*/ + for(i = 0; i < numpixels; i++) + { + out[OUT_BYTES * i] = in[4 * i]; /*most significant byte*/ + if(OUT_ALPHA) out[OUT_BYTES * i + 1] = in[4 * i + 2]; /*most significant byte*/ + } + break; + default: return 31; + } + } + else /*infoIn->bitDepth is less than 8 bit per channel*/ + { + if(infoIn->colorType != 0) return 31; /*colorType 0 is the only greyscale type with < 8 bits per channel*/ + for(i = 0; i < numpixels; i++) + { + unsigned value = readBitsFromReversedStream(&bp, in, infoIn->bitDepth); + if(OUT_ALPHA) out[OUT_BYTES * i + 1] = 255; + if(OUT_ALPHA && infoIn->key_defined && value && ((1U << infoIn->bitDepth) - 1U) == infoIn->key_r && ((1U << infoIn->bitDepth) - 1U)) out[OUT_BYTES * i + 1] = 0; + value = (value * 255) / ((1 << infoIn->bitDepth) - 1); /*scale value from 0 to 255*/ + out[OUT_BYTES * i] = (unsigned char)(value); + } + } + } + else return 59; + + return 0; +} + +/*Paeth predicter, used by PNG filter type 4*/ +static int paethPredictor(int a, int b, int c) +{ + int p = a + b - c; + int pa = p > a ? p - a : a - p; + int pb = p > b ? p - b : b - p; + int pc = p > c ? p - c : c - p; + + if(pa <= pb && pa <= pc) return a; + else if(pb <= pc) return b; + else return c; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one actually indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i < 7; i++) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i < 7; i++) + { + filter_passstart[i + 1] = filter_passstart[i] + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); /*bits padded if needed to fill full byte at end of each scanline*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; /*only padded at end of reduced image*/ + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNG_Info. return value is error*/ +void LodePNG_inspect(LodePNG_Decoder* decoder, const unsigned char* in, size_t inlength) +{ + if(inlength == 0 || in == 0) { decoder->error = 48; return; } /*the given data is empty*/ + if(inlength < 29) { decoder->error = 27; return; } /*error: the data length is smaller than the length of the header*/ + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + LodePNG_InfoPng_cleanup(&decoder->infoPng); + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 0; + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { decoder->error = 28; return; } /*error: the first 8 bytes are not the correct PNG signature*/ + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { decoder->error = 29; return; } /*error: it doesn't start with a IHDR chunk!*/ + + /*read the values given in the header*/ + decoder->infoPng.width = LodePNG_read32bitInt(&in[16]); + decoder->infoPng.height = LodePNG_read32bitInt(&in[20]); + decoder->infoPng.color.bitDepth = in[24]; + decoder->infoPng.color.colorType = in[25]; + decoder->infoPng.compressionMethod = in[26]; + decoder->infoPng.filterMethod = in[27]; + decoder->infoPng.interlaceMethod = in[28]; + + if(!decoder->settings.ignoreCrc) + { + unsigned CRC = LodePNG_read32bitInt(&in[29]); + unsigned checksum = Crc32_crc(&in[12], 17); + if(CRC != checksum) { decoder->error = 57; return; } + } + + if(decoder->infoPng.compressionMethod != 0) { decoder->error = 32; return; } /*error: only compression method 0 is allowed in the specification*/ + if(decoder->infoPng.filterMethod != 0) { decoder->error = 33; return; } /*error: only filter method 0 is allowed in the specification*/ + if(decoder->infoPng.interlaceMethod > 1) { decoder->error = 34; return; } /*error: only interlace methods 0 and 1 exist in the specification*/ + + decoder->error = checkColorValidity(decoder->infoPng.color.colorType, decoder->infoPng.color.bitDepth); +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) for(i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; + else for(i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 3: + if(precon) + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(i = 0; i < bytewidth; i++) recon[i] = (unsigned char)(scanline[i] + paethPredictor(0, precon[i], 0)); + for(i = bytewidth; i < length; i++) recon[i] = (unsigned char)(scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + else + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = (unsigned char)(scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0)); + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 it's called 7 times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address! + */ + + unsigned y; + unsigned char* prevline = 0; + + size_t bytewidth = (bpp + 7) / 8; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; y++) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + unsigned error = unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes); + if(error) return error; + + prevline = &out[outindex]; + } + + return 0; +} + +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /*Note: this function works on image buffers WITHOUT padding bits at end of scanlines with non-multiple-of-8 bit amounts, only between reduced images is padding + out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation (because that's likely a little bit faster)*/ + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream0(&obp, out, bit); /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < olinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from the IDAT chunks*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, const LodePNG_InfoPng* infoPng) /*return value is error*/ +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = LodePNG_InfoColor_getBpp(&infoPng->color); + unsigned w = infoPng->width; + unsigned h = infoPng->height; + unsigned error = 0; + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(infoPng->interlaceMethod == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + error = unfilter(in, in, w, h, bpp); + if(error) return error; + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + else error = unfilter(out, in, w, h, bpp); /*we can immediatly filter into the out buffer, no other steps needed*/ + } + else /*interlaceMethod is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i < 7; i++) + { + error = unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp); + if(error) return error; + if(bpp < 8) /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, move bytes instead of bits or move not at all*/ + { + /*remove padding bits in scanlines; after this there still may be padding bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, const unsigned char* in, size_t size) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + + /*for unknown chunk order*/ + unsigned unknown = 0; + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + if(size == 0 || in == 0) { decoder->error = 48; return; } /*the given data is empty*/ + + LodePNG_inspect(decoder, in, size); /*reads header and resets other parameters in decoder->infoPng*/ + if(decoder->error) return; + + ucvector_init(&idat); + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + while(!IEND) /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer*/ + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + if((size_t)((chunk - in) + 12) > size || chunk < in) { decoder->error = 30; break; } /*error: size of the in buffer too small to contain next chunk*/ + chunkLength = LodePNG_chunk_length(chunk); /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + if(chunkLength > 2147483647) { decoder->error = 63; break; } + if((size_t)((chunk - in) + chunkLength + 12) > size || (chunk + chunkLength + 12) < in) { decoder->error = 35; break; } /*error: size of the in buffer too small to contain next chunk*/ + data = LodePNG_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(LodePNG_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) { decoder->error = 9936; break; } + for(i = 0; i < chunkLength; i++) idat.data[oldsize + i] = data[i]; + critical_pos = 3; + } + /*IEND chunk*/ + else if(LodePNG_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(LodePNG_chunk_type_equals(chunk, "PLTE")) + { + unsigned pos = 0; + if(decoder->infoPng.color.palette) free(decoder->infoPng.color.palette); + decoder->infoPng.color.palettesize = chunkLength / 3; + decoder->infoPng.color.palette = (unsigned char*)malloc(4 * decoder->infoPng.color.palettesize); + if(!decoder->infoPng.color.palette && decoder->infoPng.color.palettesize) { decoder->error = 9937; break; } + if(!decoder->infoPng.color.palette) decoder->infoPng.color.palettesize = 0; /*malloc failed...*/ + if(decoder->infoPng.color.palettesize > 256) { decoder->error = 38; break; } /*error: palette too big*/ + for(i = 0; i < decoder->infoPng.color.palettesize; i++) + { + decoder->infoPng.color.palette[4 * i + 0] = data[pos++]; /*R*/ + decoder->infoPng.color.palette[4 * i + 1] = data[pos++]; /*G*/ + decoder->infoPng.color.palette[4 * i + 2] = data[pos++]; /*B*/ + decoder->infoPng.color.palette[4 * i + 3] = 255; /*alpha*/ + } + critical_pos = 2; + } + /*palette transparency chunk (tRNS)*/ + else if(LodePNG_chunk_type_equals(chunk, "tRNS")) + { + if(decoder->infoPng.color.colorType == 3) + { + if(chunkLength > decoder->infoPng.color.palettesize) { decoder->error = 39; break; } /*error: more alpha values given than there are palette entries*/ + for(i = 0; i < chunkLength; i++) decoder->infoPng.color.palette[4 * i + 3] = data[i]; + } + else if(decoder->infoPng.color.colorType == 0) + { + if(chunkLength != 2) { decoder->error = 40; break; } /*error: this chunk must be 2 bytes for greyscale image*/ + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = decoder->infoPng.color.key_g = decoder->infoPng.color.key_b = 256 * data[0] + data[1]; + } + else if(decoder->infoPng.color.colorType == 2) + { + if(chunkLength != 6) { decoder->error = 41; break; } /*error: this chunk must be 6 bytes for RGB image*/ + decoder->infoPng.color.key_defined = 1; + decoder->infoPng.color.key_r = 256 * data[0] + data[1]; + decoder->infoPng.color.key_g = 256 * data[2] + data[3]; + decoder->infoPng.color.key_b = 256 * data[4] + data[5]; + } + else { decoder->error = 42; break; } /*error: tRNS chunk not allowed for other color models*/ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(LodePNG_chunk_type_equals(chunk, "bKGD")) + { + if(decoder->infoPng.color.colorType == 3) + { + if(chunkLength != 1) { decoder->error = 43; break; } /*error: this chunk must be 1 byte for indexed color image*/ + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = decoder->infoPng.background_g = decoder->infoPng.background_g = data[0]; + } + else if(decoder->infoPng.color.colorType == 0 || decoder->infoPng.color.colorType == 4) + { + if(chunkLength != 2) { decoder->error = 44; break; } /*error: this chunk must be 2 bytes for greyscale image*/ + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = decoder->infoPng.background_g = decoder->infoPng.background_b = 256 * data[0] + data[1]; + } + else if(decoder->infoPng.color.colorType == 2 || decoder->infoPng.color.colorType == 6) + { + if(chunkLength != 6) { decoder->error = 45; break; } /*error: this chunk must be 6 bytes for greyscale image*/ + decoder->infoPng.background_defined = 1; + decoder->infoPng.background_r = 256 * data[0] + data[1]; + decoder->infoPng.background_g = 256 * data[2] + data[3]; + decoder->infoPng.background_b = 256 * data[4] + data[5]; + } + } + /*text chunk (tEXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "tEXt")) + { + if(decoder->settings.readTextChunks) + { + char *key = 0, *str = 0; + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 1 >= chunkLength) { decoder->error = 75; break; } + key = (char*)malloc(length + 1); + if(!key) { decoder->error = 9938; break; } + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + string2_begin = length + 1; + if(string2_begin > chunkLength) { decoder->error = 75; break; } + length = chunkLength - string2_begin; + str = (char*)malloc(length + 1); + if(!str) { decoder->error = 9939; break; } + str[length] = 0; + for(i = 0; i < length; i++) str[i] = data[string2_begin + i]; + + decoder->error = LodePNG_Text_add(&decoder->infoPng.text, key, str); + + break; + } + + free(key); + free(str); + } + } + /*compressed text chunk (zTXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "zTXt")) + { + if(decoder->settings.readTextChunks) + { + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 2 >= chunkLength) { decoder->error = 75; break; } + key = (char*)malloc(length + 1); + if(!key) { decoder->error = 9940; break; } + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + if(data[length + 1] != 0) { decoder->error = 72; break; } /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) { decoder->error = 75; break; } + length = chunkLength - string2_begin; + decoder->error = LodePNG_decompress(&decoded.data, &decoded.size, (unsigned char*)(&data[string2_begin]), length, &decoder->settings.zlibsettings); + if(decoder->error) break; + ucvector_push_back(&decoded, 0); + + decoder->error = LodePNG_Text_add(&decoder->infoPng.text, key, (char*)decoded.data); + + break; + } + + free(key); + ucvector_cleanup(&decoded); + if(decoder->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(LodePNG_chunk_type_equals(chunk, "iTXt")) + { + if(decoder->settings.readTextChunks) + { + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!decoder->error) /*not really a while loop, only used to break on error*/ + { + if(chunkLength < 5) { decoder->error = 76; break; } + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 2 >= chunkLength) { decoder->error = 75; break; } + key = (char*)malloc(length + 1); + if(!key) { decoder->error = 9941; break; } + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + compressed = data[length + 1]; + if(data[length + 2] != 0) { decoder->error = 72; break; } /*the 0 byte indicating compression must be 0*/ + + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + if(begin + length + 1 >= chunkLength) { decoder->error = 75; break; } + langtag = (char*)malloc(length + 1); + if(!langtag) { decoder->error = 9942; break; } + langtag[length] = 0; + for(i = 0; i < length; i++) langtag[i] = data[begin + i]; + + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + if(begin + length + 1 >= chunkLength) { decoder->error = 75; break; } + transkey = (char*)malloc(length + 1); + if(!transkey) { decoder->error = 9943; break; } + transkey[length] = 0; + for(i = 0; i < length; i++) transkey[i] = data[begin + i]; + + begin += length + 1; + if(begin > chunkLength) { decoder->error = 75; break; } + length = chunkLength - begin; + + if(compressed) + { + decoder->error = LodePNG_decompress(&decoded.data, &decoded.size, (unsigned char*)(&data[begin]), length, &decoder->settings.zlibsettings); + if(decoder->error) break; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) { decoder->error = 9944; break; } + decoded.data[length] = 0; + for(i = 0; i < length; i++) decoded.data[i] = data[begin + i]; + } + + decoder->error = LodePNG_IText_add(&decoder->infoPng.itext, key, langtag, transkey, (char*)decoded.data); + + break; + } + + free(key); + free(langtag); + free(transkey); + ucvector_cleanup(&decoded); + if(decoder->error) break; + } + } + else if(LodePNG_chunk_type_equals(chunk, "tIME")) + { + if(chunkLength != 7) { decoder->error = 73; break; } + decoder->infoPng.time_defined = 1; + decoder->infoPng.time.year = 256 * data[0] + data[+ 1]; + decoder->infoPng.time.month = data[2]; + decoder->infoPng.time.day = data[3]; + decoder->infoPng.time.hour = data[4]; + decoder->infoPng.time.minute = data[5]; + decoder->infoPng.time.second = data[6]; + } + else if(LodePNG_chunk_type_equals(chunk, "pHYs")) + { + if(chunkLength != 9) { decoder->error = 74; break; } + decoder->infoPng.phys_defined = 1; + decoder->infoPng.phys_x = 16777216 * data[0] + 65536 * data[1] + 256 * data[2] + data[3]; + decoder->infoPng.phys_y = 16777216 * data[4] + 65536 * data[5] + 256 * data[6] + data[7]; + decoder->infoPng.phys_unit = data[8]; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + if(LodePNG_chunk_critical(chunk)) { decoder->error = 69; break; } /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + unknown = 1; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + if(decoder->settings.rememberUnknownChunks) + { + LodePNG_UnknownChunks* unknown = &decoder->infoPng.unknown_chunks; + decoder->error = LodePNG_append_chunk(&unknown->data[critical_pos - 1], &unknown->datasize[critical_pos - 1], chunk); + if(decoder->error) break; + } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + } + + if(!decoder->settings.ignoreCrc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(LodePNG_chunk_check_crc(chunk)) { decoder->error = 57; break; } + } + + if(!IEND) chunk = LodePNG_chunk_next_const(chunk); + } + + if(!decoder->error) + { + ucvector scanlines; + ucvector_init(&scanlines); + if(!ucvector_resize(&scanlines, ((decoder->infoPng.width * (decoder->infoPng.height * LodePNG_InfoColor_getBpp(&decoder->infoPng.color) + 7)) / 8) + decoder->infoPng.height)) decoder->error = 9945; /*maximum final image length is already reserved in the vector's length - this is not really necessary*/ + if(!decoder->error) decoder->error = LodePNG_decompress(&scanlines.data, &scanlines.size, idat.data, idat.size, &decoder->settings.zlibsettings); /*decompress with the Zlib decompressor*/ + + if(!decoder->error) + { + ucvector outv; + ucvector_init(&outv); + if(!ucvector_resizev(&outv, (decoder->infoPng.height * decoder->infoPng.width * LodePNG_InfoColor_getBpp(&decoder->infoPng.color) + 7) / 8, 0)) decoder->error = 9946; + if(!decoder->error) decoder->error = postProcessScanlines(outv.data, scanlines.data, &decoder->infoPng); + *out = outv.data; + *outsize = outv.size; + } + ucvector_cleanup(&scanlines); + } + + ucvector_cleanup(&idat); +} + +void LodePNG_decode(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize) +{ + *out = 0; + *outsize = 0; + decodeGeneric(decoder, out, outsize, in, insize); + if(decoder->error) return; + if(!decoder->settings.color_convert || LodePNG_InfoColor_equal(&decoder->infoRaw.color, &decoder->infoPng.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the infoPng color settings on the infoRaw so that the infoRaw still reflects what colorType + the raw image has to the end user*/ + if(!decoder->settings.color_convert) + { + decoder->error = LodePNG_InfoColor_copy(&decoder->infoRaw.color, &decoder->infoPng.color); + if(decoder->error) return; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(decoder->infoRaw.color.colorType == 2 || decoder->infoRaw.color.colorType == 6) && !(decoder->infoRaw.color.bitDepth == 8)) { decoder->error = 56; return; } + + *outsize = (decoder->infoPng.width * decoder->infoPng.height * LodePNG_InfoColor_getBpp(&decoder->infoRaw.color) + 7) / 8; + *out = (unsigned char*)malloc(*outsize); + if(!(*out)) + { + decoder->error = 9947; + *outsize = 0; + } + else decoder->error = LodePNG_convert(*out, data, &decoder->infoRaw.color, &decoder->infoPng.color, decoder->infoPng.width, decoder->infoPng.height); + free(data); + } +} + +unsigned LodePNG_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + unsigned error; + size_t dummy_size; + LodePNG_Decoder decoder; + LodePNG_Decoder_init(&decoder); + LodePNG_decode(&decoder, out, &dummy_size, in, insize); + error = decoder.error; + *w = decoder.infoPng.width; + *h = decoder.infoPng.height; + LodePNG_Decoder_cleanup(&decoder); + return error; +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_decode32f(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error; + error = LodePNG_loadFile(&buffer, &buffersize, filename); + if(!error) error = LodePNG_decode32(out, w, h, buffer, buffersize); + free(buffer); + return error; +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void LodePNG_DecodeSettings_init(LodePNG_DecodeSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->readTextChunks = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignoreCrc = 0; +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + settings->rememberUnknownChunks = 0; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + LodeZlib_DecompressSettings_init(&settings->zlibsettings); +} + +void LodePNG_Decoder_init(LodePNG_Decoder* decoder) +{ + LodePNG_DecodeSettings_init(&decoder->settings); + LodePNG_InfoRaw_init(&decoder->infoRaw); + LodePNG_InfoPng_init(&decoder->infoPng); + decoder->error = 1; +} + +void LodePNG_Decoder_cleanup(LodePNG_Decoder* decoder) +{ + LodePNG_InfoRaw_cleanup(&decoder->infoRaw); + LodePNG_InfoPng_cleanup(&decoder->infoPng); +} + +void LodePNG_Decoder_copy(LodePNG_Decoder* dest, const LodePNG_Decoder* source) +{ + LodePNG_Decoder_cleanup(dest); + *dest = *source; + LodePNG_InfoRaw_init(&dest->infoRaw); + LodePNG_InfoPng_init(&dest->infoPng); + dest->error = LodePNG_InfoRaw_copy(&dest->infoRaw, &source->infoRaw); if(dest->error) return; + dest->error = LodePNG_InfoPng_copy(&dest->infoPng, &source->infoPng); if(dest->error) return; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + unsigned error = LodePNG_create_chunk(&out->data, &out->size, (unsigned)length, chunkName, data); + if(error) return error; + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, unsigned bitDepth, unsigned colorType, unsigned interlaceMethod) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + LodePNG_add32bitInt(&header, w); /*width*/ + LodePNG_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitDepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colorType); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlaceMethod); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNG_InfoColor* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i < info->palettesize * 4; i++) if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); /*add all channels except alpha channel*/ + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNG_InfoColor* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colorType == 3) + { + for(i = 0; i < info->palettesize; i++) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); /*add only alpha channel*/ + } + else if(info->colorType == 0) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + } + } + else if(info->colorType == 2) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b % 256)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, LodeZlib_DeflateSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = LodePNG_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) /*add text chunk*/ +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&text, (unsigned char)keyword[i]); + ucvector_push_back(&text, 0); + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, LodeZlib_DeflateSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + ucvector_push_back(&data, 0); /* 0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + error = LodePNG_compress(&compressed.data, &compressed.size, (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed.size; i++) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, const char* transkey, const char* textstring, LodeZlib_DeflateSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed_data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; i++) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; i++) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector_init(&compressed_data); + error = LodePNG_compress(&compressed_data.data, &compressed_data.size, (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed_data.size; i++) ucvector_push_back(&data, compressed_data.data[i]); + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNG_InfoPng* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colorType == 0 || info->color.colorType == 4) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + } + else if(info->color.colorType == 2 || info->color.colorType == 6) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b % 256)); + } + else if(info->color.colorType == 3) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNG_Time* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)malloc(7); + if(!data) return 9948; + data[0] = (unsigned char)(time->year / 256); + data[1] = (unsigned char)(time->year % 256); + data[2] = time->month; + data[3] = time->day; + data[4] = time->hour; + data[5] = time->minute; + data[6] = time->second; + error = addChunk(out, "tIME", data, 7); + free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNG_InfoPng* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + LodePNG_add32bitInt(&data, info->phys_x); + LodePNG_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: + if(prevline) for(i = 0; i < length; i++) out[i] = scanline[i]; + else for(i = 0; i < length; i++) out[i] = scanline[i]; + break; + case 1: + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length ; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + break; + case 2: + if(prevline) for(i = 0; i < length; i++) out[i] = scanline[i] - prevline[i]; + else for(i = 0; i < length; i++) out[i] = scanline[i]; + break; + case 3: + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i] - prevline[i] / 2; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) / 2); + } + else + { + for(i = 0; i < length; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth] / 2; + } + break; + case 4: + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = (unsigned char)(scanline[i] - paethPredictor(0, prevline[i], 0)); + for(i = bytewidth; i < length; i++) out[i] = (unsigned char)(scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = (unsigned char)(scanline[i] - paethPredictor(scanline[i - bytewidth], 0, 0)); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, const LodePNG_InfoColor* info) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are the scanlines with 1 extra byte per scanline + + There is a nice heuristic described here: http://www.cs.toronto.edu/~cosmin/pngtech/optipng.html. It says: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply all five filters and select the filter that produces the smallest sum of absolute values per row. + + Here the above method is used mostly. Note though that it appears to be better to use the adaptive filtering on the plasma 8-bit palette example, but that image isn't the best reference for palette images in general. + */ + + unsigned bpp = LodePNG_InfoColor_getBpp(info); + size_t linebytes = (w * bpp + 7) / 8; /*the width of a scanline in bytes, not including the filter type*/ + size_t bytewidth = (bpp + 7) / 8; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + const unsigned char* prevline = 0; + unsigned x, y; + unsigned heuristic; + unsigned error = 0; + + if(bpp == 0) return 31; /*invalid color type*/ + + /*choose heuristic as described above*/ + if(info->colorType == 3 || info->bitDepth < 8) heuristic = 0; + else heuristic = 1; + + if(heuristic == 0) /*None filtertype for everything*/ + { + for(y = 0; y < h; y++) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + const unsigned TYPE = 0; + out[outindex] = TYPE; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, TYPE); + prevline = &in[inindex]; + } + } + else if(heuristic == 1) /*adaptive filtering*/ + { + size_t sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type, bestType = 0; + + for(type = 0; type < 5; type++) ucvector_init(&attempt[type]); + for(type = 0; type < 5; type++) + { + if(!ucvector_resize(&attempt[type], linebytes)) { error = 9949; break; } + } + + if(!error) + { + for(y = 0; y < h; y++) + { + /*try the 5 filter types*/ + for(type = 0; type < 5; type++) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + for(x = 0; x < attempt[type].size; x+=3) sum[type] += attempt[type].data[x]; /*note that not all pixels are checked to speed this up while still having probably the best choice*/ + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + } + + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + #if 0 /*deflate the scanline with a fixed tree after every filter attempt to see which one deflates best. This is slow, and _does not work as expected_: the heuristic gives smaller result!*/ + else if(heuristic == 2) /*adaptive filtering by using deflate*/ + { + size_t size[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodeZlib_DeflateSettings deflatesettings = LodeZlib_defaultDeflateSettings; + deflatesettings.btype = 1; /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, to simulate the true case where the tree is the same for the whole image*/ + for(type = 0; type < 5; type++) { ucvector_init(&attempt[type]); ucvector_resize(&attempt[type], linebytes); } + for(y = 0; y < h; y++) /*try the 5 filter types*/ + { + for(type = 0; type < 5; type++) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; dummy = 0; + LodePNG_compress(&dummy, &size[type], attempt[type].data, attempt[type].size, &deflatesettings); + free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { bestType = type; smallest = size[type]; } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + #endif + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < ilinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x < diff; x++) setBitOfReversedStream(&obp, out, 0); + } +} + +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /*Note: this function works on image buffers WITHOUT padding bits at end of scanlines with non-multiple-of-8 bit amounts, only between reduced images is padding*/ + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image*/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, const LodePNG_InfoPng* infoPng) /*return value is error*/ +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = LodePNG_InfoColor_getBpp(&infoPng->color); + unsigned w = infoPng->width; + unsigned h = infoPng->height; + unsigned error = 0; + + if(infoPng->interlaceMethod == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)malloc(*outsize); + if(!(*out) && (*outsize)) error = 9950; + + if(!error) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + { + ucvector padded; + ucvector_init(&padded); + if(!ucvector_resize(&padded, h * ((w * bpp + 7) / 8))) error = 9951; + if(!error) + { + addPaddingBits(padded.data, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded.data, w, h, &infoPng->color); + } + ucvector_cleanup(&padded); + } + else error = filter(*out, in, w, h, &infoPng->color); /*we can immediatly filter into the out buffer, no other steps needed*/ + } + } + else /*interlaceMethod is 1 (Adam7)*/ + { + unsigned char* adam7 = (unsigned char*)malloc((h * w * bpp + 7) / 8); + if(!adam7 && ((h * w * bpp + 7) / 8)) error = 9952; /*malloc failed*/ + + while(!error) /*not a real while loop, used to break out to cleanup to avoid a goto*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)malloc(*outsize); + if(!(*out) && (*outsize)) { error = 9953; break; } + + Adam7_interlace(adam7, in, w, h, bpp); + + for(i = 0; i < 7; i++) + { + if(bpp < 8) + { + ucvector padded; + ucvector_init(&padded); + if(!ucvector_resize(&padded, h * ((w * bpp + 7) / 8))) error = 9954; + if(!error) + { + addPaddingBits(&padded.data[padded_passstart[i]], &adam7[passstart[i]], ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], &padded.data[padded_passstart[i]], passw[i], passh[i], &infoPng->color); + } + + ucvector_cleanup(&padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], passw[i], passh[i], &infoPng->color); + } + } + + break; + } + + free(adam7); + } + + return error; +} + +/*palette must have 4 * palettesize bytes allocated*/ +static unsigned isPaletteFullyOpaque(const unsigned char* palette, size_t palettesize) /*palette given in format RGBARGBARGBARGBA...*/ +{ + size_t i; + for(i = 0; i < palettesize; i++) + { + if(palette[4 * i + 3] != 255) return 0; + } + return 1; +} + +/*this function checks if the input image given by the user has no transparent pixels*/ +static unsigned isFullyOpaque(const unsigned char* image, unsigned w, unsigned h, const LodePNG_InfoColor* info) +{ + /*TODO: When the user specified a color key for the input image, then this function must also check for pixels that are the same as the color key and treat those as transparent.*/ + + unsigned i, numpixels = w * h; + if(info->colorType == 6) + { + if(info->bitDepth == 8) + { + for(i = 0; i < numpixels; i++) if(image[i * 4 + 3] != 255) return 0; + } + else + { + for(i = 0; i < numpixels; i++) if(image[i * 8 + 6] != 255 || image[i * 8 + 7] != 255) return 0; + } + return 1; /*no single pixel with alpha channel other than 255 found*/ + } + else if(info->colorType == 4) + { + if(info->bitDepth == 8) + { + for(i = 0; i < numpixels; i++) if(image[i * 2 + 1] != 255) return 0; + } + else + { + for(i = 0; i < numpixels; i++) if(image[i * 4 + 2] != 255 || image[i * 4 + 3] != 255) return 0; + } + return 1; /*no single pixel with alpha channel other than 255 found*/ + } + else if(info->colorType == 3) + { + /*when there's a palette, we could check every pixel for translucency, but much quicker is to just check the palette*/ + return(isPaletteFullyOpaque(info->palette, info->palettesize)); + } + + return 0; /*color type that isn't supported by this function yet, so assume there is transparency to be safe*/ +} + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + unsigned error = LodePNG_append_chunk(&out->data, &out->size, inchunk); + if(error) return error; /*error: not enough memory*/ + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = LodePNG_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +void LodePNG_encode(LodePNG_Encoder* encoder, unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + LodePNG_InfoPng info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + encoder->error = 0; + + info = encoder->infoPng; /*UNSAFE copy to avoid having to cleanup! but we will only change primitive parameters, and not invoke the cleanup function nor touch the palette's buffer so we use it safely*/ + info.width = w; + info.height = h; + + if(encoder->settings.autoLeaveOutAlphaChannel && isFullyOpaque(image, w, h, &encoder->infoRaw.color)) + { + /*go to a color type without alpha channel*/ + if(info.color.colorType == 6) info.color.colorType = 2; + else if(info.color.colorType == 4) info.color.colorType = 0; + } + + if(encoder->settings.zlibsettings.windowSize > 32768) { encoder->error = 60; return; } /*error: windowsize larger than allowed*/ + if(encoder->settings.zlibsettings.btype > 2) { encoder->error = 61; return; } /*error: unexisting btype*/ + if(encoder->infoPng.interlaceMethod > 1) { encoder->error = 71; return; } /*error: unexisting interlace mode*/ + if((encoder->error = checkColorValidity(info.color.colorType, info.color.bitDepth))) return; /*error: unexisting color type given*/ + if((encoder->error = checkColorValidity(encoder->infoRaw.color.colorType, encoder->infoRaw.color.bitDepth))) return; /*error: unexisting color type given*/ + + if(!LodePNG_InfoColor_equal(&encoder->infoRaw.color, &info.color)) + { + unsigned char* converted; + size_t size = (w * h * LodePNG_InfoColor_getBpp(&info.color) + 7) / 8; + + if((info.color.colorType != 6 && info.color.colorType != 2) || (info.color.bitDepth != 8)) { encoder->error = 59; return; } /*for the output image, only these types are supported*/ + converted = (unsigned char*)malloc(size); + if(!converted && size) encoder->error = 9955; /*error: malloc failed*/ + if(!encoder->error) encoder->error = LodePNG_convert(converted, image, &info.color, &encoder->infoRaw.color, w, h); + if(!encoder->error) preProcessScanlines(&data, &datasize, converted, &info);/*filter(data.data, converted.data, w, h, LodePNG_InfoColor_getBpp(&info.color));*/ + free(converted); + } + else preProcessScanlines(&data, &datasize, image, &info);/*filter(data.data, image, w, h, LodePNG_InfoColor_getBpp(&info.color));*/ + + ucvector_init(&outv); + while(!encoder->error) /*not really a while loop, this is only used to break out if an error happens to avoid goto's to do the ucvector cleanup*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.bitDepth, info.color.colorType, info.interlaceMethod); +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks.data[0]) { encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[0], info.unknown_chunks.datasize[0]); if(encoder->error) break; } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*PLTE*/ + if(info.color.colorType == 3) + { + if(info.color.palettesize == 0 || info.color.palettesize > 256) { encoder->error = 68; break; } + addChunk_PLTE(&outv, &info.color); + } + if(encoder->settings.force_palette && (info.color.colorType == 2 || info.color.colorType == 6)) + { + if(info.color.palettesize == 0 || info.color.palettesize > 256) { encoder->error = 68; break; } + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colorType == 3 && !isPaletteFullyOpaque(info.color.palette, info.color.palettesize)) addChunk_tRNS(&outv, &info.color); + if((info.color.colorType == 0 || info.color.colorType == 2) && info.color.key_defined) addChunk_tRNS(&outv, &info.color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks.data[1]) { encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[1], info.unknown_chunks.datasize[1]); if(encoder->error) break; } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + encoder->error = addChunk_IDAT(&outv, data, datasize, &encoder->settings.zlibsettings); + if(encoder->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i < info.text.num; i++) + { + if(strlen(info.text.keys[i]) > 79) { encoder->error = 66; break; } + if(strlen(info.text.keys[i]) < 1) { encoder->error = 67; break; } + if(encoder->settings.text_compression) + addChunk_zTXt(&outv, info.text.keys[i], info.text.strings[i], &encoder->settings.zlibsettings); + else + addChunk_tEXt(&outv, info.text.keys[i], info.text.strings[i]); + } + /*LodePNG version id in text chunk*/ + if(encoder->settings.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i < info.text.num; i++) + if(!strcmp(info.text.keys[i], "LodePNG")) { alread_added_id_text = 1; break; } + if(alread_added_id_text == 0) + addChunk_tEXt(&outv, "LodePNG", VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + /*iTXt*/ + for(i = 0; i < info.itext.num; i++) + { + if(strlen(info.itext.keys[i]) > 79) { encoder->error = 66; break; } + if(strlen(info.itext.keys[i]) < 1) { encoder->error = 67; break; } + addChunk_iTXt(&outv, encoder->settings.text_compression, + info.itext.keys[i], info.itext.langtags[i], info.itext.transkeys[i], info.itext.strings[i], + &encoder->settings.zlibsettings); + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks.data[2]) { encoder->error = addUnknownChunks(&outv, info.unknown_chunks.data[2], info.unknown_chunks.datasize[2]); if(encoder->error) break; } +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + /*IEND*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; +} + +unsigned LodePNG_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + unsigned error; + LodePNG_Encoder encoder; + LodePNG_Encoder_init(&encoder); + LodePNG_encode(&encoder, out, outsize, image, w, h); + error = encoder.error; + LodePNG_Encoder_cleanup(&encoder); + return error; +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_encode32f(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = LodePNG_encode32(&buffer, &buffersize, image, w, h); + LodePNG_saveFile(buffer, buffersize, filename); + free(buffer); + return error; +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void LodePNG_EncodeSettings_init(LodePNG_EncodeSettings* settings) +{ + LodeZlib_DeflateSettings_init(&settings->zlibsettings); + settings->autoLeaveOutAlphaChannel = 1; + settings->force_palette = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 1; + settings->text_compression = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void LodePNG_Encoder_init(LodePNG_Encoder* encoder) +{ + LodePNG_EncodeSettings_init(&encoder->settings); + LodePNG_InfoPng_init(&encoder->infoPng); + LodePNG_InfoRaw_init(&encoder->infoRaw); + encoder->error = 1; +} + +void LodePNG_Encoder_cleanup(LodePNG_Encoder* encoder) +{ + LodePNG_InfoPng_cleanup(&encoder->infoPng); + LodePNG_InfoRaw_cleanup(&encoder->infoRaw); +} + +void LodePNG_Encoder_copy(LodePNG_Encoder* dest, const LodePNG_Encoder* source) +{ + LodePNG_Encoder_cleanup(dest); + *dest = *source; + LodePNG_InfoPng_init(&dest->infoPng); + LodePNG_InfoRaw_init(&dest->infoRaw); + dest->error = LodePNG_InfoPng_copy(&dest->infoPng, &source->infoPng); if(dest->error) return; + dest->error = LodePNG_InfoRaw_copy(&dest->infoRaw, &source->infoRaw); if(dest->error) return; +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +unsigned LodePNG_loadFile(unsigned char** out, size_t* outsize, const char* filename) /*designed for loading files from hard disk in a dynamically allocated buffer*/ +{ + FILE* file; + long size; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + file = fopen(filename, "rb"); + if(!file) return 78; + + /*get filesize:*/ + fseek(file , 0 , SEEK_END); + size = ftell(file); + rewind(file); + + /*read contents of the file into the vector*/ + *outsize = 0; + *out = (unsigned char*)malloc((size_t)size); + if(size && (*out)) (*outsize) = fread(*out, 1, (size_t)size, file); + + fclose(file); + if(!(*out) && size) return 80; /*the above malloc failed*/ + return 0; +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned LodePNG_saveFile(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite((char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef __cplusplus +/* ////////////////////////////////////////////////////////////////////////// */ +/* / C++ RAII wrapper / */ +/* ////////////////////////////////////////////////////////////////////////// */ +namespace LodeZlib +{ + unsigned decompress(std::vector& out, const std::vector& in, const LodeZlib_DecompressSettings& settings) + { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = LodeZlib_decompress(&buffer, &buffersize, in.empty() ? 0 : &in[0], in.size(), &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + return error; + } + + unsigned compress(std::vector& out, const std::vector& in, const LodeZlib_DeflateSettings& settings) + { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = LodeZlib_compress(&buffer, &buffersize, in.empty() ? 0 : &in[0], in.size(), &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + return error; + } +} + +namespace LodePNG +{ + Decoder::Decoder() { LodePNG_Decoder_init(this); } + Decoder::~Decoder() { LodePNG_Decoder_cleanup(this); } + void Decoder::operator=(const LodePNG_Decoder& other) { LodePNG_Decoder_copy(this, &other); } + + bool Decoder::hasError() const { return error != 0; } + unsigned Decoder::getError() const { return error; } + + unsigned Decoder::getWidth() const { return infoPng.width; } + unsigned Decoder::getHeight() const { return infoPng.height; } + unsigned Decoder::getBpp() { return LodePNG_InfoColor_getBpp(&infoPng.color); } + unsigned Decoder::getChannels() { return LodePNG_InfoColor_getChannels(&infoPng.color); } + unsigned Decoder::isGreyscaleType() { return LodePNG_InfoColor_isGreyscaleType(&infoPng.color); } + unsigned Decoder::isAlphaType() { return LodePNG_InfoColor_isAlphaType(&infoPng.color); } + + void Decoder::decode(std::vector& out, const unsigned char* in, size_t insize) + { + unsigned char* buffer; + size_t buffersize; + LodePNG_decode(this, &buffer, &buffersize, in, insize); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + } + + void Decoder::decode(std::vector& out, const std::vector& in) + { + decode(out, in.empty() ? 0 : &in[0], in.size()); + } + + void Decoder::inspect(const unsigned char* in, size_t size) + { + LodePNG_inspect(this, in, size); + } + + void Decoder::inspect(const std::vector& in) + { + inspect(in.empty() ? 0 : &in[0], in.size()); + } + + const LodePNG_DecodeSettings& Decoder::getSettings() const { return settings; } + LodePNG_DecodeSettings& Decoder::getSettings() { return settings; } + void Decoder::setSettings(const LodePNG_DecodeSettings& settings) { this->settings = settings; } + + const LodePNG_InfoPng& Decoder::getInfoPng() const { return infoPng; } + LodePNG_InfoPng& Decoder::getInfoPng() { return infoPng; } + void Decoder::setInfoPng(const LodePNG_InfoPng& info) { error = LodePNG_InfoPng_copy(&this->infoPng, &info); } + void Decoder::swapInfoPng(LodePNG_InfoPng& info) { LodePNG_InfoPng_swap(&this->infoPng, &info); } + + const LodePNG_InfoRaw& Decoder::getInfoRaw() const { return infoRaw; } + LodePNG_InfoRaw& Decoder::getInfoRaw() { return infoRaw; } + void Decoder::setInfoRaw(const LodePNG_InfoRaw& info) { error = LodePNG_InfoRaw_copy(&this->infoRaw, &info); } + + /* ////////////////////////////////////////////////////////////////////////// */ + + Encoder::Encoder() { LodePNG_Encoder_init(this); } + Encoder::~Encoder() { LodePNG_Encoder_cleanup(this); } + void Encoder::operator=(const LodePNG_Encoder& other) { LodePNG_Encoder_copy(this, &other); } + + bool Encoder::hasError() const { return error != 0; } + unsigned Encoder::getError() const { return error; } + + void Encoder::encode(std::vector& out, const unsigned char* image, unsigned w, unsigned h) + { + unsigned char* buffer; + size_t buffersize; + LodePNG_encode(this, &buffer, &buffersize, image, w, h); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + free(buffer); + } + } + + void Encoder::encode(std::vector& out, const std::vector& image, unsigned w, unsigned h) + { + encode(out, image.empty() ? 0 : &image[0], w, h); + } + + void Encoder::clearPalette() { LodePNG_InfoColor_clearPalette(&infoPng.color); } + void Encoder::addPalette(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { error = LodePNG_InfoColor_addPalette(&infoPng.color, r, g, b, a); } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + void Encoder::clearText() { LodePNG_Text_clear(&infoPng.text); } + void Encoder::addText(const std::string& key, const std::string& str) { error = LodePNG_Text_add(&infoPng.text, key.c_str(), str.c_str()); } + void Encoder::clearIText() { LodePNG_IText_clear(&infoPng.itext); } + void Encoder::addIText(const std::string& key, const std::string& langtag, const std::string& transkey, const std::string& str) { error = LodePNG_IText_add(&infoPng.itext, key.c_str(), langtag.c_str(), transkey.c_str(), str.c_str()); } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + const LodePNG_EncodeSettings& Encoder::getSettings() const { return settings; } + LodePNG_EncodeSettings& Encoder::getSettings() { return settings; } + void Encoder::setSettings(const LodePNG_EncodeSettings& settings) { this->settings = settings; } + + const LodePNG_InfoPng& Encoder::getInfoPng() const { return infoPng; } + LodePNG_InfoPng& Encoder::getInfoPng() { return infoPng; } + void Encoder::setInfoPng(const LodePNG_InfoPng& info) { error = LodePNG_InfoPng_copy(&this->infoPng, &info); } + void Encoder::swapInfoPng(LodePNG_InfoPng& info) { LodePNG_InfoPng_swap(&this->infoPng, &info); } + + const LodePNG_InfoRaw& Encoder::getInfoRaw() const { return infoRaw; } + LodePNG_InfoRaw& Encoder::getInfoRaw() { return infoRaw; } + void Encoder::setInfoRaw(const LodePNG_InfoRaw& info) { error = LodePNG_InfoRaw_copy(&this->infoRaw, &info); } + + /* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + + void loadFile(std::vector& buffer, const std::string& filename) //designed for loading files from hard disk in an std::vector + { + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + /*get filesize*/ + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + /*read contents of the file into the vector*/ + buffer.resize(size_t(size)); + if(size > 0) file.read((char*)(&buffer[0]), size); + } + + /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ + void saveFile(const std::vector& buffer, const std::string& filename) + { + std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary); + file.write(buffer.empty() ? 0 : (char*)&buffer[0], std::streamsize(buffer.size())); + } + +#endif /*LODEPNG_COMPILE_DISK*/ + + /* ////////////////////////////////////////////////////////////////////////// */ + + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, unsigned size, unsigned colorType, unsigned bitDepth) + { + Decoder decoder; + decoder.getInfoRaw().color.colorType = colorType; + decoder.getInfoRaw().color.bitDepth = bitDepth; + decoder.decode(out, in, size); + w = decoder.getWidth(); + h = decoder.getHeight(); + return decoder.getError(); + } + + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, unsigned colorType, unsigned bitDepth) + { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colorType, bitDepth); + } + +#ifdef LODEPNG_COMPILE_DISK + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, unsigned colorType, unsigned bitDepth) + { + std::vector buffer; + loadFile(buffer, filename); + return decode(out, w, h, buffer, colorType, bitDepth); + } +#endif /*LODEPNG_COMPILE_DISK*/ + + unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, unsigned colorType, unsigned bitDepth) + { + Encoder encoder; + encoder.getInfoRaw().color.colorType = colorType; + encoder.getInfoRaw().color.bitDepth = bitDepth; + encoder.encode(out, in, w, h); + return encoder.getError(); + } + + unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, unsigned colorType, unsigned bitDepth) + { + return encode(out, in.empty() ? 0 : &in[0], w, h, colorType, bitDepth); + } + +#ifdef LODEPNG_COMPILE_DISK + unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, unsigned colorType, unsigned bitDepth) + { + std::vector buffer; + Encoder encoder; + encoder.getInfoRaw().color.colorType = colorType; + encoder.getInfoRaw().color.bitDepth = bitDepth; + encoder.encode(buffer, in, w, h); + if(!encoder.hasError()) saveFile(buffer, filename); + return encoder.getError(); + } + + unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, unsigned colorType, unsigned bitDepth) + { + return encode(filename, in.empty() ? 0 : &in[0], w, h, colorType, bitDepth); + } +#endif /*LODEPNG_COMPILE_DISK*/ + +} +#endif /*__cplusplus C++ RAII wrapper*/ diff --git a/minorGems/graphics/converters/lodepng.h b/minorGems/graphics/converters/lodepng.h new file mode 100644 index 0000000..530c9ab --- /dev/null +++ b/minorGems/graphics/converters/lodepng.h @@ -0,0 +1,1708 @@ +/* +LodePNG version 20080927 + +Copyright (c) 2005-2008 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include +#include +#include + +/* ////////////////////////////////////////////////////////////////////////// */ +/* Code Sections */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The following defines can be commented disable code sections. Gives potential faster compile and smaller binary.*/ + +#define LODEPNG_COMPILE_ZLIB /*deflate&zlib encoder and deflate&zlib decoder*/ +#define LODEPNG_COMPILE_PNG /*png encoder and png decoder*/ +#define LODEPNG_COMPILE_DECODER /*deflate&zlib decoder and png decoder*/ +#define LODEPNG_COMPILE_ENCODER /*deflate&zlib encoder and png encoder*/ +#define LODEPNG_COMPILE_DISK /*the optional built in harddisk file loading and saving functions*/ +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS /*any code or struct datamember related to chunks other than IHDR, IDAT, PLTE, tRNS, IEND*/ +#define LODEPNG_COMPILE_UNKNOWN_CHUNKS /*handling of unknown chunks*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodeFlate & LodeZlib Setting structs */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER +typedef struct LodeZlib_DecompressSettings +{ + unsigned ignoreAdler32; +} LodeZlib_DecompressSettings; + +extern const LodeZlib_DecompressSettings LodeZlib_defaultDecompressSettings; +void LodeZlib_DecompressSettings_init(LodeZlib_DecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +typedef struct LodeZlib_DeflateSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ*/ + unsigned useLZ77; /*whether or not to use LZ77*/ + unsigned windowSize; /*the maximum is 32768*/ +} LodeZlib_DeflateSettings; + +extern const LodeZlib_DeflateSettings LodeZlib_defaultDeflateSettings; +void LodeZlib_DeflateSettings_init(LodeZlib_DeflateSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodeFlate & LodeZlib */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER +/*This function reallocates the out buffer and appends the data. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes.*/ +unsigned LodeZlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function reallocates the out buffer and appends the data. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes.*/ +unsigned LodeZlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodeZlib_DeflateSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* LodePNG */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*LodePNG_chunk functions: These functions need as input a large enough amount of allocated memory.*/ + +unsigned LodePNG_chunk_length(const unsigned char* chunk); /*get the length of the data of the chunk. Total chunk length has 12 bytes more.*/ + +void LodePNG_chunk_type(char type[5], const unsigned char* chunk); /*puts the 4-byte type in null terminated string*/ +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type); /*check if the type is the given type*/ + +/*properties of PNG chunks gotten from capitalization of chunk type name, as defined by the standard*/ +unsigned char LodePNG_chunk_critical(const unsigned char* chunk); /*0: ancillary chunk, 1: it's one of the critical chunk types*/ +unsigned char LodePNG_chunk_private(const unsigned char* chunk); /*0: public, 1: private*/ +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk); /*0: the chunk is unsafe to copy, 1: the chunk is safe to copy*/ + +unsigned char* LodePNG_chunk_data(unsigned char* chunk); /*get pointer to the data of the chunk*/ +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk); /*get pointer to the data of the chunk*/ + +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk); /*returns 0 if the crc is correct, 1 if it's incorrect*/ +void LodePNG_chunk_generate_crc(unsigned char* chunk); /*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ + +/*iterate to next chunks.*/ +unsigned char* LodePNG_chunk_next(unsigned char* chunk); +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk); + +/*add chunks to out buffer. It reallocs the buffer to append the data. returns error code*/ +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk); /*appends chunk that was already created, to the data. Returns pointer to start of appended chunk, or NULL if error happened*/ +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, const char* type, const unsigned char* data); /*appends new chunk to out. Returns pointer to start of appended chunk, or NULL if error happened; may change memory address of out buffer*/ + +typedef struct LodePNG_InfoColor /*info about the color type of an image*/ +{ + /*header (IHDR)*/ + unsigned colorType; /*color type*/ + unsigned bitDepth; /*bits per sample*/ + + /*palette (PLTE)*/ + unsigned char* palette; /*palette in RGBARGBA... order*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /*transparent color key (tRNS)*/ + unsigned key_defined; /*is a transparent color key given?*/ + unsigned key_r; /*red component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNG_InfoColor; + +void LodePNG_InfoColor_init(LodePNG_InfoColor* info); +void LodePNG_InfoColor_cleanup(LodePNG_InfoColor* info); +unsigned LodePNG_InfoColor_copy(LodePNG_InfoColor* dest, const LodePNG_InfoColor* source); + +/*Use these functions instead of allocating palette manually*/ +void LodePNG_InfoColor_clearPalette(LodePNG_InfoColor* info); +unsigned LodePNG_InfoColor_addPalette(LodePNG_InfoColor* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a); /*add 1 color to the palette*/ + +/*additional color info*/ +unsigned LodePNG_InfoColor_getBpp(const LodePNG_InfoColor* info); /*bits per pixel*/ +unsigned LodePNG_InfoColor_getChannels(const LodePNG_InfoColor* info); /*amount of channels*/ +unsigned LodePNG_InfoColor_isGreyscaleType(const LodePNG_InfoColor* info); /*is it a greyscale type? (colorType 0 or 4)*/ +unsigned LodePNG_InfoColor_isAlphaType(const LodePNG_InfoColor* info); /*has it an alpha channel? (colorType 2 or 6)*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +typedef struct LodePNG_Time /*LodePNG's encoder does not generate the current time. To make it add a time chunk the correct time has to be provided*/ +{ + unsigned year; /*2 bytes*/ + unsigned char month; /*1-12*/ + unsigned char day; /*1-31*/ + unsigned char hour; /*0-23*/ + unsigned char minute; /*0-59*/ + unsigned char second; /*0-60 (to allow for leap seconds)*/ +} LodePNG_Time; + +typedef struct LodePNG_Text /*non-international text*/ +{ + size_t num; + char** keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** strings; /*the actual text*/ +} LodePNG_Text; + +void LodePNG_Text_init(LodePNG_Text* text); +void LodePNG_Text_cleanup(LodePNG_Text* text); +unsigned LodePNG_Text_copy(LodePNG_Text* dest, const LodePNG_Text* source); + +/*Use these functions instead of allocating the char**s manually*/ +void LodePNG_Text_clear(LodePNG_Text* text); +unsigned LodePNG_Text_add(LodePNG_Text* text, const char* key, const char* str); /*push back both texts at once*/ + + +typedef struct LodePNG_IText /*international text*/ +{ + size_t num; + char** keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** langtags; /*the language tag for this text's international language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** strings; /*the actual international text - UTF-8 string*/ +} LodePNG_IText; + +void LodePNG_IText_init(LodePNG_IText* text); +void LodePNG_IText_cleanup(LodePNG_IText* text); +unsigned LodePNG_IText_copy(LodePNG_IText* dest, const LodePNG_IText* source); + +/*Use these functions instead of allocating the char**s manually*/ +void LodePNG_IText_clear(LodePNG_IText* text); +unsigned LodePNG_IText_add(LodePNG_IText* text, const char* key, const char* langtag, const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS +typedef struct LodePNG_UnknownChunks /*unknown chunks read from the PNG, or extra chunks the user wants to have added in the encoded PNG*/ +{ + /*There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND*/ + unsigned char* data[3]; + size_t datasize[3]; /*size in bytes of the unknown chunks, given for protection*/ + +} LodePNG_UnknownChunks; + +void LodePNG_UnknownChunks_init(LodePNG_UnknownChunks* chunks); +void LodePNG_UnknownChunks_cleanup(LodePNG_UnknownChunks* chunks); +unsigned LodePNG_UnknownChunks_copy(LodePNG_UnknownChunks* dest, const LodePNG_UnknownChunks* src); +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +typedef struct LodePNG_InfoPng /*information about the PNG image, except pixels and sometimes except width and height*/ +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS)*/ + unsigned width; /*width of the image in pixels (ignored by encoder, but filled in by decoder)*/ + unsigned height; /*height of the image in pixels (ignored by encoder, but filled in by decoder)*/ + unsigned compressionMethod; /*compression method of the original file*/ + unsigned filterMethod; /*filter method of the original file*/ + unsigned interlaceMethod; /*interlace method of the original file*/ + LodePNG_InfoColor color; /*color type and bits, palette, transparency*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + + /*suggested background color (bKGD)*/ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /*non-international text chunks (tEXt and zTXt)*/ + LodePNG_Text text; + + /*international text chunks (iTXt)*/ + LodePNG_IText itext; + + /*time chunk (tIME)*/ + unsigned char time_defined; /*if 0, no tIME chunk was or will be generated in the PNG image*/ + LodePNG_Time time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*is pHYs chunk defined?*/ + unsigned phys_x; + unsigned phys_y; + unsigned char phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + /*unknown chunks*/ + LodePNG_UnknownChunks unknown_chunks; +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ + +} LodePNG_InfoPng; + +void LodePNG_InfoPng_init(LodePNG_InfoPng* info); +void LodePNG_InfoPng_cleanup(LodePNG_InfoPng* info); +unsigned LodePNG_InfoPng_copy(LodePNG_InfoPng* dest, const LodePNG_InfoPng* source); + +typedef struct LodePNG_InfoRaw /*contains user-chosen information about the raw image data, which is independent of the PNG image*/ +{ + LodePNG_InfoColor color; +} LodePNG_InfoRaw; + +void LodePNG_InfoRaw_init(LodePNG_InfoRaw* info); +void LodePNG_InfoRaw_cleanup(LodePNG_InfoRaw* info); +unsigned LodePNG_InfoRaw_copy(LodePNG_InfoRaw* dest, const LodePNG_InfoRaw* source); + +/* +LodePNG_convert: Converts from any color type to 24-bit or 32-bit (later maybe more supported). return value = LodePNG error code +The out buffer must have (w * h * bpp + 7) / 8, where bpp is the bits per pixel of the output color type (LodePNG_InfoColor_getBpp) +*/ +unsigned LodePNG_convert(unsigned char* out, const unsigned char* in, LodePNG_InfoColor* infoOut, LodePNG_InfoColor* infoIn, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct LodePNG_DecodeSettings +{ + LodeZlib_DecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + unsigned ignoreCrc; /*ignore CRC checksums*/ + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned readTextChunks; /*if false but rememberUnknownChunks is true, they're stored in the unknown chunks*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +#ifdef LODEPNG_COMPILE_UNKNOWN_CHUNKS + unsigned rememberUnknownChunks; /*store all bytes from unknown chunks in the InfoPng (off by default, useful for a png editor)*/ +#endif /*LODEPNG_COMPILE_UNKNOWN_CHUNKS*/ +} LodePNG_DecodeSettings; + +void LodePNG_DecodeSettings_init(LodePNG_DecodeSettings* settings); + +typedef struct LodePNG_Decoder +{ + LodePNG_DecodeSettings settings; + LodePNG_InfoRaw infoRaw; + LodePNG_InfoPng infoPng; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNG_Decoder; + +void LodePNG_Decoder_init(LodePNG_Decoder* decoder); +void LodePNG_Decoder_cleanup(LodePNG_Decoder* decoder); +void LodePNG_Decoder_copy(LodePNG_Decoder* dest, const LodePNG_Decoder* source); + +/*decoding functions*/ +/*This function allocates the out buffer and stores the size in *outsize.*/ +void LodePNG_decode(LodePNG_Decoder* decoder, unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize); +unsigned LodePNG_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); /*return value is error*/ +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_decode32f(unsigned char** out, unsigned* w, unsigned* h, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +void LodePNG_inspect(LodePNG_Decoder* decoder, const unsigned char* in, size_t size); /*read the png header*/ + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct LodePNG_EncodeSettings +{ + LodeZlib_DeflateSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned autoLeaveOutAlphaChannel; /*automatically use color type without alpha instead of given one, if given image is opaque*/ + unsigned force_palette; /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). If colortype is 3, PLTE is _always_ created.*/ +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned add_id; /*add LodePNG version as text chunk*/ + unsigned text_compression; /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNG_EncodeSettings; + +void LodePNG_EncodeSettings_init(LodePNG_EncodeSettings* settings); + +typedef struct LodePNG_Encoder +{ + LodePNG_EncodeSettings settings; + LodePNG_InfoPng infoPng; /*the info specified by the user may not be changed by the encoder. The encoder will try to generate a PNG close to the given info.*/ + LodePNG_InfoRaw infoRaw; /*put the properties of the input raw image in here*/ + unsigned error; +} LodePNG_Encoder; + +void LodePNG_Encoder_init(LodePNG_Encoder* encoder); +void LodePNG_Encoder_cleanup(LodePNG_Encoder* encoder); +void LodePNG_Encoder_copy(LodePNG_Encoder* dest, const LodePNG_Encoder* source); + +/*This function allocates the out buffer and stores the size in *outsize.*/ +void LodePNG_encode(LodePNG_Encoder* encoder, unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); +unsigned LodePNG_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); /*return value is error*/ +#ifdef LODEPNG_COMPILE_DISK +unsigned LodePNG_encode32f(const char* filename, const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_DISK +/*free functions allowing to load and save a file from/to harddisk*/ +/*This function allocates the out buffer and stores the size in *outsize.*/ +unsigned LodePNG_loadFile(unsigned char** out, size_t* outsize, const char* filename); +unsigned LodePNG_saveFile(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef __cplusplus + +//LodePNG C++ wrapper: wraps interface with destructors and std::vectors around the harder to use C version + +#include +#include +#include + +#ifdef LODEPNG_COMPILE_ZLIB +namespace LodeZlib +{ +#ifdef LODEPNG_COMPILE_DECODER + unsigned decompress(std::vector& out, const std::vector& in, const LodeZlib_DecompressSettings& settings = LodeZlib_defaultDecompressSettings); +#endif //LODEPNG_COMPILE_DECODER +#ifdef LODEPNG_COMPILE_ENCODER + unsigned compress(std::vector& out, const std::vector& in, const LodeZlib_DeflateSettings& settings = LodeZlib_defaultDeflateSettings); +#endif //LODEPNG_COMPILE_ENCODER +} +#endif //LODEPNG_COMPILE_ZLIB + +#ifdef LODEPNG_COMPILE_PNG +namespace LodePNG +{ + +#ifdef LODEPNG_COMPILE_DECODER + class Decoder : public LodePNG_Decoder + { + public: + + Decoder(); + ~Decoder(); + void operator=(const LodePNG_Decoder& other); + + //decoding functions + void decode(std::vector& out, const unsigned char* in, size_t insize); + void decode(std::vector& out, const std::vector& in); + + void inspect(const unsigned char* in, size_t size); + void inspect(const std::vector& in); + + //error checking after decoding + bool hasError() const; + unsigned getError() const; + + //convenient access to some InfoPng parameters after decoding + unsigned getWidth() const; + unsigned getHeight() const; + unsigned getBpp(); //bits per pixel + unsigned getChannels(); //amount of channels + unsigned isGreyscaleType(); //is it a greyscale type? (colorType 0 or 4) + unsigned isAlphaType(); //has it an alpha channel? (colorType 2 or 6) + + const LodePNG_DecodeSettings& getSettings() const; + LodePNG_DecodeSettings& getSettings(); + void setSettings(const LodePNG_DecodeSettings& info); + + const LodePNG_InfoPng& getInfoPng() const; + LodePNG_InfoPng& getInfoPng(); + void setInfoPng(const LodePNG_InfoPng& info); + void swapInfoPng(LodePNG_InfoPng& info); //faster than copying with setInfoPng + + const LodePNG_InfoRaw& getInfoRaw() const; + LodePNG_InfoRaw& getInfoRaw(); + void setInfoRaw(const LodePNG_InfoRaw& info); + }; + + //simple functions for encoding/decoding the PNG in one call (RAW image always 32-bit) + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, unsigned size, unsigned colorType = 6, unsigned bitDepth = 8); + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, unsigned colorType = 6, unsigned bitDepth = 8); +#ifdef LODEPNG_COMPILE_DISK + unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, unsigned colorType = 6, unsigned bitDepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER + class Encoder : public LodePNG_Encoder + { + public: + + Encoder(); + ~Encoder(); + void operator=(const LodePNG_Encoder& other); + + void encode(std::vector& out, const unsigned char* image, unsigned w, unsigned h); + void encode(std::vector& out, const std::vector& image, unsigned w, unsigned h); + + //error checking after decoding + bool hasError() const; + unsigned getError() const; + + //convenient direct access to some parameters of the InfoPng + void clearPalette(); + void addPalette(unsigned char r, unsigned char g, unsigned char b, unsigned char a); //add 1 color to the palette +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + void clearText(); + void addText(const std::string& key, const std::string& str); //push back both texts at once + void clearIText(); + void addIText(const std::string& key, const std::string& langtag, const std::string& transkey, const std::string& str); +#endif //LODEPNG_COMPILE_ANCILLARY_CHUNKS + + const LodePNG_EncodeSettings& getSettings() const; + LodePNG_EncodeSettings& getSettings(); + void setSettings(const LodePNG_EncodeSettings& info); + + const LodePNG_InfoPng& getInfoPng() const; + LodePNG_InfoPng& getInfoPng(); + void setInfoPng(const LodePNG_InfoPng& info); + void swapInfoPng(LodePNG_InfoPng& info); //faster than copying with setInfoPng + + const LodePNG_InfoRaw& getInfoRaw() const; + LodePNG_InfoRaw& getInfoRaw(); + void setInfoRaw(const LodePNG_InfoRaw& info); + }; + + unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, unsigned colorType = 6, unsigned bitDepth = 8); + unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, unsigned colorType = 6, unsigned bitDepth = 8); +#ifdef LODEPNG_COMPILE_DISK + unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, unsigned colorType = 6, unsigned bitDepth = 8); + unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, unsigned colorType = 6, unsigned bitDepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_ENCODER + +#ifdef LODEPNG_COMPILE_DISK + //free functions allowing to load and save a file from/to harddisk + void loadFile(std::vector& buffer, const std::string& filename); + void saveFile(const std::vector& buffer, const std::string& filename); +#endif //LODEPNG_COMPILE_DISK + +} //namespace LodePNG + +#endif //LODEPNG_COMPILE_PNG + +#endif /*end of __cplusplus wrapper*/ + +/* +TODO: +[ ] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[ ] LZ77 encoder more like the one described in zlib - to make sure it's patentfree +[ ] converting color to 16-bit types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones ever touch RGB values, that is very annoying for textures as well as images in a browser) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[ ] let the "isFullyOpaque" function check color keys and transparent palettes too +[ ] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] check compatibility with vareous compilers - done but needs to be redone for every newer version +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings that the decoder stores in the error at the very end? and make some errors just let it stop with this one chunk but still do the next ones) +[ ] make option to choose if the raw image with non multiple of 8 bits per scanline should have padding bits or not, if people like storing raw images that way +*/ + +#endif + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. A note about security! + 4. simple functions + 4.1 C Simple Functions + 4.2 C++ Simple Functions + 5. decoder + 6. encoder + 7. color conversions + 8. info values + 9. error values + 10. file IO + 11. chunks and PNG editing + 12. compiler support + 13. examples + 13.1. decoder example + 13.2. encoder example + 14. LodeZlib + 15. changes + 16. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types. It can be implemented in a patent-free way. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://members.gamedev.net/lode/projects/LodePNG/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +files lodepng_examples.c or lodepng_examples.cpp to see how to use it in code, +or check the (smaller) examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. To decode PNGs, there's a Decoder struct or class that +can convert any PNG file data into an RGBA image buffer with a single function +call. To encode PNGs, there's an Encoder struct or class that can convert image +data into PNG file data with a single function call. To read and write files, +there are simple functions to convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demoscene +productions, saving a screenshot, images in programs that require them for simple +usage, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... +LodePNG has a standards conformant decoder and encoder, and supports the ability +to make a somewhat conformant editor. + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc instead that you need to free() +yourself. On top of that, you need to use init and cleanup functions for each +struct whenever using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has constructors and destructors that take care of these things, +and uses std::vectors in the interface for storing data. + +Both the C and the C++ version are contained in this file! The C++ code depends on +the C code, the C code works on its own. + +These files work without modification for both C and C++ compilers because all the +additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers ignore +it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp (instead +of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. A note about security! +------------------------- + +Despite being used already and having received bug fixes whenever bugs were reported, +LodePNG may still contain possible exploits. + +If you discover a possible exploit, please let me know, and it will be eliminated. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well as the C-style +structs when working with C++. The following conventions are used for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one, to avoid exploits +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". The destination must be inited already! + + +4. "Simple" Functions +--------------------- + +For the most simple usage cases of loading and saving a PNG image, there +are some simple functions that do everything in 1 call (instead of you +having to instantiate a struct or class). + +The simple versions always use 32-bit RGBA color for the raw image, but +still support loading arbitrary-colortype PNG images. + +The later sections of this manual are devoted to the complex versions, where +you can use other color types and conversions. + +4.1 C Simple Functions +---------------------- + +The C simple functions have a "32" or "32f" in their name, and don't take a struct as +parameter, unlike the non-simple ones (see more down in the documentation). + +unsigned LodePNG_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); + +Load PNG from given buffer. +As input, give an unsigned char* buffer gotten by loading the .png file and its size. +As output, you get a dynamically allocated buffer of large enough size, and the width and height of the image. +The buffer's size is w * h * 4. The image is in RGBA format. +The return value is the error (0 if ok). +You need to do free(out) after usage to clean up the memory. + +unsigned LodePNG_decode32f(unsigned char** out, unsigned* w, unsigned* h, const char* filename); + +Load PNG from disk, from file with given name. +Same as decode32, except you give a filename instead of an input buffer. + +unsigned LodePNG_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); + +Encode PNG into buffer. +As input, give a image buffer of size w * h * 4, in RGBA format. +As output, you get a dynamically allocated buffer and its size, which is a PNG file that can +directly be saved in this form to the harddisk. +The return value is the error (0 if ok). +You need to do free(out) after usage to clean up the memory. + +unsigned LodePNG_encode32f(const char* filename, const unsigned char* image, unsigned w, unsigned h); + +Encode PNG into file on disk with given name. +If the file exists, it's overwritten without warning! +Same parameters as encode2, except the result is stored in a file instead of a dynamic buffer. + +4.2 C++ Simple Functions +------------------------ + +For decoding a PNG there are: + +unsigned LodePNG::decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, unsigned size); +unsigned LodePNG::decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in); +unsigned LodePNG::decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename); + +These store the pixel data as 32-bit RGBA color in the out vector, and the width +and height of the image in w and h. +The 3 functions each have a different input type: The first as unsigned char +buffer, the second as std::vector buffer, and the third allows you to give the +filename in case you want to load the PNG from disk instead of from a buffer. +The return value is the error (0 if ok). + +For encoding a PNG there are: + +unsigned LodePNG::encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h); +unsigned LodePNG::encode(std::vector& out, const std::vector& in, unsigned w, unsigned h); +unsigned LodePNG::encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h); +unsigned LodePNG::encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h); + +Specify the width and height of the input image with w and h. +You can choose to get the output in an std::vector or stored in a file, and +the input can come from an std::vector or an unsigned char* buffer. The input +buffer must be in RGBA format and the size must be w * h * 4 bytes. + +The first two functions append to the out buffer, they don't clear it, clear it +first before encoding into a buffer that you expect to only contain this result. + +On the other hand, the functions that encode to a file will completely overwrite +the original file without warning if it exists. + +The return value is the error (0 if ok). + +5. Decoder +---------- + +This is about the LodePNG_Decoder struct in the C version, and the +LodePNG::Decoder class in the C++ version. The C++ version inherits +from the C struct and adds functions in the interface. + +The Decoder class can be used to convert a PNG image to a raw image. + +Usage: + +-in C++: + declare a LodePNG::Decoder + call its decode member function with the parameters described below + +-in C more needs to be done due to the lack of constructors and destructors: + declare a LodePNG_Decoder struct + call LodePNG_Decoder_init with the struct as parameter + call LodePNG_Decode with the parameters described below + after usage, call LodePNG_Decoder_cleanup with the struct as parameter + after usage, free() the out buffer with image data that was created by the decode function + +The other parameters of the decode function are: +*) out: this buffer will be filled with the raw image pixels +*) in: pointer to the PNG image data or std::vector with the data +*) size: the size of the PNG image data (not needed for std::vector version) + +After decoding you need to read the width and height of the image from the +decoder, see further down in this manual to see how. + +There's also an optional function "inspect". It has the same parameters as decode except +the "out" parameter. This function will read only the header chunk of the PNG +image, and store the information from it in the LodePNG_InfoPng (see below). +This allows knowing information about the image without decoding it. Only the +header (IHDR) information is read by this, not text chunks, not the palette, ... + +During the decoding it's possible that an error can happen, for example if the +PNG image was corrupted. To check if an error happened during the last decoding, +check the value error, which is a member of the decoder struct. +In the C++ version, use hasError() and getError() of the Decoder. +The error codes are explained in another section. + +Now about colors and settings... + +The Decoder contains 3 components: +*) LodePNG_InfoPng: it stores information about the PNG (the input) in an LodePNG_InfoPng struct, don't modify this one yourself +*) Settings: you can specify a few other settings for the decoder to use +*) LodePNG_InfoRaw: here you can say what type of raw image (the output) you want to get + +Some of the parameters described below may be inside the sub-struct "LodePNG_InfoColor color". +In the C and C++ version, when using Info structs outside of the decoder or encoder, you need to use their +init and cleanup functions, but normally you use the ones in the decoder that are already handled +in the init and cleanup functions of the decoder itself. + +=LodePNG_InfoPng= + +This contains information such as the original color type of the PNG image, text +comments, suggested background color, etc... More details about the LodePNG_InfoPng struct +are in another section. + +Because the dimensions of the image are important, there are shortcuts to get them in the +C++ version: use decoder.getWidth() and decoder.getHeight(). +In the C version, use decoder.infoPng.width and decoder.infoPng.height. + +=LodePNG_InfoRaw= + +In the LodePNG_InfoRaw struct of the Decoder, you can specify which color type you want +the resulting raw image to be. If this is different from the colorType of the +PNG, then the decoder will automatically convert the result to your LodePNG_InfoRaw +settings. Currently the following options are supported to convert to: +-colorType 6, bitDepth 8: 32-bit RGBA +-colorType 2, bitDepth 8: 24-bit RGB +-other color types if it's exactly the same as that in the PNG image + +Palette of LodePNG_InfoRaw isn't used by the Decoder, when converting from palette color +to palette color, the values of the pixels are left untouched so that the colors +will change if the palette is different. Color key of LodePNG_InfoRaw is not used by the +Decoder. If setting color_convert is false then LodePNG_InfoRaw is completely ignored, +but it will be modified to match the color type of the PNG so will be overwritten. + +By default, 32-bit color is used for the result. + +=Settings= + +The Settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNG_InfoPng. + + +6. Encoder +---------- + +This is about the LodePNG_Encoder struct in the C version, and the +LodePNG::Encoder class in the C++ version. + +The Encoder class can be used to convert raw image data into a PNG image. + +The PNG part of the encoder is working good, the zlib compression part is +becoming quite fine but not as good as the official zlib yet, because it's not +as fast and doesn't provide an as high compression ratio. + +Usage: + +-in C++: + declare a LodePNG::Encoder + call its encode member function with the parameters described below + +-in C more needs to be done due to the lack of constructors and destructors: + declare a LodePNG_Encoder struct + call LodePNG_Encoder_init with the struct as parameter + call LodePNG_Encode with the parameters described below + after usage, call LodePNG_Encoder_cleanup with the struct as parameter + after usage, free() the out buffer with PNG data that was created by the encode function + +The raw image given to the encoder is an unsigned char* buffer. You also have to +specify the width and height of the raw image. The result is stored in a given +buffer. These buffers can be unsigned char* pointers, std::vectors or dynamically +allocated unsigned char* buffers that you have to free() yourself, depending on +which you use. + +The parameters of the encode function are: +*) out: in this buffer the PNG file data will be stored (it will be appended) +*) in: vector of or pointer to a buffer containing the raw image +*) w and h: the width and height of the raw image in pixels + +Make sure that the in buffer you provide, is big enough to contain w * h pixels +of the color type specified by the LodePNG_InfoRaw. + +In the C version, you need to free() the out buffer after usage to avoid memory leaks. +In the C version, you need to use the LodePNG_Encoder_init function before using the decoder, +and the LodePNG_Encoder_cleanup function after using it. +In the C++ version, you don't need to do this since RAII takes care of it. + +The encoder generates some errors but not for everything, because, unlike when +decoding a PNG, when encoding one there aren't so much parameters of the input +that can be corrupted. It's the responsibility of the user to make sure that all +preconditions are satesfied, such as giving a correct window size, giving an +existing btype, making sure the given buffer is large enough to contain an image +with the given width and height and colortype, ... The encoder can generate +some errors, see the section with the explanations of errors for those. + +Like the Decoder, the Encoder has 3 components: +*) LodePNG_InfoRaw: here you say what color type of the raw image (the input) has +*) Settings: you can specify a few settings for the encoder to use +*) LodePNG_InfoPng: the same LodePNG_InfoPng struct as created by the Decoder. For the encoder, +with this you specify how you want the PNG (the output) to be. + +Some of the parameters described below may be inside the sub-struct "LodePNG_InfoColor color". +In the C and C++ version, when using Info structs outside of the decoder or encoder, you need to use their +init and cleanup functions, but normally you use the ones in the encoder that are already handled +in the init and cleanup functions of the decoder itself. + +=LodePNG_InfoPng= + +The Decoder class stores information about the PNG image in an LodePNG_InfoPng object. With +the Encoder you can do the opposite: you give it an LodePNG_InfoPng object, and it'll try +to match the LodePNG_InfoPng you give as close as possible in the PNG it encodes. For +example in the LodePNG_InfoPng you can specify the color type you want to use, possible +tEXt chunks you want the PNG to contain, etc... For an explanation of all the +values in LodePNG_InfoPng see a further section. Not all PNG color types are supported +by the Encoder. + +Note that the encoder will only TRY to match the LodePNG_InfoPng struct you give. +Some things are ignored by the encoder. The width and height of LodePNG_InfoPng are +ignored as well, because instead the width and height of the raw image you give +in the input are used. In fact the encoder currently uses only the following +settings from it: +-colorType: the ones it supports +-text chunks, that you can add to the LodePNG_InfoPng with "addText" +-the color key, if applicable for the given color type +-the palette, if you encode to a PNG with colorType 3 +-the background color: it'll add a bKGD chunk to the PNG if one is given +-the interlaceMethod: None (0) or Adam7 (1) + +When encoding to a PNG with colorType 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +=LodePNG_InfoRaw= + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +=Settings= + +The following settings are supported (some are in sub-structs): +*) autoLeaveOutAlphaChannel: when this option is enabled, when you specify a PNG +color type with alpha channel (not to be confused with the color type of the raw +image you specify!!), but the encoder detects that all pixels of the given image +are opaque, then it'll automatically use the corresponding type without alpha +channel, resulting in a smaller PNG image. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, 2 = dynamic huffman tree (best compression) +*) useLZ77: whether or not to use LZ77 for compressed block types +*) windowSize: the window size used by the LZ77 encoder (1 - 32768) +*) force_palette: if colorType is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 0. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +7. color conversions +-------------------- + +For trickier usage of LodePNG, you need to understand about PNG color types and +about how and when LodePNG uses the settings in LodePNG_InfoPng, LodePNG_InfoRaw and Settings. + +=PNG color types= + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification mentions the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per color channel. + +=Default Behaviour of LodePNG= + +By default, the Decoder will convert the data from the PNG to 32-bit RGBA color, +no matter what color type the PNG has, so that the result can be used directly +as a texture in OpenGL etc... without worries about what color type the original +image has. + +The Encoder assumes by default that the raw input you give it is a 32-bit RGBA +buffer and will store the PNG as either 32 bit or 24 bit depending on whether +or not any translucent pixels were detected in it. + +To get the default behaviour, don't change the values of LodePNG_InfoRaw and LodePNG_InfoPng of +the encoder, and don't change the values of LodePNG_InfoRaw of the decoder. + +=Color Conversions= + +As explained in the sections about the Encoder and Decoder, you can specify +color types and bit depths in LodePNG_InfoPng and LodePNG_InfoRaw, to change the default behaviour +explained above. (for the Decoder you can only specify the LodePNG_InfoRaw, because the +LodePNG_InfoPng contains what the PNG file has). + +To avoid some confusion: +-the Decoder converts from PNG to raw image +-the Encoder converts from raw image to PNG +-the color type and bit depth in LodePNG_InfoRaw, are those of the raw image +-the color type and bit depth in LodePNG_InfoPng, are those of the PNG +-if the color type of the LodePNG_InfoRaw and PNG image aren't the same, a conversion +between the color types is done if the color types are supported + +Supported color types: +-It's possible to load PNGs from any colortype and to save PNGs of any colorType. +-Both encoder and decoder use the same converter. So both encoder and decoder +suport the same color types at the input and the output. So the decoder supports +any type of PNG image and can convert it to certain types of raw image, while the +encoder supports any type of raw data but only certain color types for the output PNG. +-The converter can convert from _any_ input color type, to 24-bit RGB or 32-bit RGBA +-The converter can convert from greyscale input color type, to 8-bit greyscale or greyscale with alpha +-If both color types are the same, conversion from anything to anything is possible +-Color types that are invalid according to the PNG specification are not allowed +-When converting from a type with alpha channel to one without, the alpha channel information is discarded +-When converting from a type without alpha channel to one with, the result will be opaque except pixels that have the same color as the color key of the input if one was given +-When converting from 16-bit bitDepth to 8-bit bitDepth, the 16-bit precision information is lost, only the most significant byte is kept +-Converting from color to greyscale is not supported on purpose: choosing what kind of color to greyscale conversion to do is not a decision a PNG codec should make +-Converting from/to a palette type, only keeps the indices, it ignores the colors defined in the palette + +No conversion needed...: +-If the color type of the PNG image and raw image are the same, then no +conversion is done, and all color types are supported. +-In the encoder, you can make it save a PNG with any color by giving the +LodePNG_InfoRaw and LodePNG_InfoPng the same color type. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +infoRaw are then ignored. + +The function LodePNG_convert does this, which is available in the interface but +normally isn't needed since the encoder and decoder already call it. + +=More Notes= + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. +However: The input image you give to the encoder, and the output image you get from the decoder +will NOT have these padding bits in that case, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +8. info values +-------------- + +Both the encoder and decoder use a variable of type LodePNG_InfoPng and LodePNG_InfoRaw, which +both also contain a LodePNG_InfoColor. Here's a list of each of the values stored in them: + +*) info from the PNG header (IHDR chunk): + +width: width of the image in pixels +height: height of the image in pixels +colorType: color type of the original PNG file +bitDepth: bits per sample +compressionMethod: compression method of the original file. Always 0. +filterMethod: filter method of the original file. Always 0. +interlaceMethod: interlace method of the original file. 0 is no interlace, 1 is adam7 interlace. + +Note: width and height are only used as information of a decoded PNG image. When encoding one, you don't have +to specify width and height in an LodePNG_Info struct, but you give them as parameters of the encode function. +The rest of the LodePNG_Info struct IS used by the encoder though! + +*) palette: + +This is a dynamically allocated unsigned char array with the colors of the palette. The value palettesize +indicates the amount of colors in the palette. The allocated size of the buffer is 4 * palettesize bytes, +because there are 4 values per color: R, G, B and A. Even if less color channels are used, the palette +is always in RGBA format, in the order RGBARGBARGBA..... + +When encoding a PNG, to store your colors in the palette of the LodePNG_InfoRaw, first use +LodePNG_InfoColor_clearPalette, then for each color use LodePNG_InfoColor_addPalette. +In the C++ version the Encoder class also has the above functions available directly in its interface. + +Note that the palette information from the tRNS chunk is also already included in this palette vector. + +If you encode an image with palette, don't forget that you have to set the alpha channels (A) of the palette +too, set them to 255 for an opaque palette. If you leave them at zero, the image will be encoded as +fully invisible. This both for the palette in the infoRaw and the infoPng if the png is to have a palette. + +*) transparent color key + +key_defined: is a transparent color key given? +key_r: red/greyscale component of color key +key_g: green component of color key +key_b: blue component of color key + +For greyscale PNGs, r, g and b will all 3 be set to the same. + +This color is 8-bit for 8-bit PNGs, 16-bit for 16-bit per channel PNGs. + +*) suggested background color + +background_defined: is a suggested background color given? +background_r: red component of sugg. background color +background_g: green component of sugg. background color +background_b: blue component of sugg. background color + +This color is 8-bit for 8-bit PNGs, 16-bit for 16-bit PNGs + +For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding +the encoder writes the red one away. +For palette PNGs: When decoding, the RGB value will be stored, no a palette +index. But when encoding, specify the index of the palette in background_r, +the other two are then ignored. + +The decoder pretty much ignores this background color, after all if you make a +PNG translucent normally you intend it to be used against any background, on +websites, as translucent textures in games, ... But you can get the color this +way if needed. + +*) text and itext + +Non-international text: + +-text.keys: a char** buffer containing the keywords (see below) +-text.strings: a char** buffer containing the texts (see below) +-text.num: the amount of texts in the above char** buffers (there may be more texts in itext) +-LodePNG_InfoText_clearText: use this to clear the texts again after you filled them in +-LodePNG_InfoText_addText: this function is used to push back a keyword and text + +International text: This is stored in separate arrays! The sum text.num and itext.num is the real amount of texts. + +-itext.keys: keyword in English +-itext.langtags: ISO 639 letter code for the language +-itext.transkeys: keyword in this language +-itext.strings: the text in this language, in UTF-8 +-itext.num: the amount of international texts in this PNG +-LodePNG_InfoIText_clearText: use this to clear the itexts again after you filled them in +-LodePNG_InfoIText_addText: this function is used to push back all 4 parts of an itext + +Don't allocate these text buffers yourself. Use the init/cleanup functions +correctly and use addText and clearText. + +In the C++ version the Encoder class also has the above functions available directly in its interface. +The char** buffers are used like the argv parameter of a main() function, and (i)text.num takes the role +of argc. + +In a text, there must be as much keys as strings because they always form pairs. In an itext, +there must always be as much keys, langtags, transkeys and strings. + +They keyword of text chunks gives a short description what the actual text +represents. There are a few standard standard keywords recognised +by many programs: Title, Author, Description, Copyright, Creation Time, +Software, Disclaimer, Warning, Source, Comment. It's allowed to use other keys. + +The keyword is minimum 1 character and maximum 79 characters long. It's +discouraged to use a single line length longer than 79 characters for texts. + +*) additional color info + +These functions are available with longer names in the C version, and directly +in the Decoder's interface in the C++ version. + +getBpp(): bits per pixel of the PNG image +getChannels(): amount of color channels of the PNG image +isGreyscaleType(): it's color type 0 or 4 +isAlphaType(): it's color type 2 or 6 + +These values are calculated out of color type and bit depth of InfoColor. + +The difference between bits per pixel and bit depth is that bit depth is the +number of bits per color channel, while a pixel can have multiple channels. + +*) pHYs chunk (image dimensions) + +phys_defined: if 0, there is no pHYs chunk and the values are undefined, if 1 else there is one +phys_x: pixels per unit in x direction +phys_y: pixels per unit in y direction +phys_unit: the unit, 0 is no unit (x and y only give the ratio), 1 is metre + +*) tIME chunk (modification time) + +time_defined: if 0, there is no tIME chunk and the values are undefined, if 1 there is one +time: this struct contains year as a 2-byte number (0-65535), month, day, hour, minute, +second as 1-byte numbers that must be in the correct range + +Note: to make the encoder add a time chunk, set time_defined to 1 and fill in +the correct values in all the time parameters, LodePNG will not fill the current +time in these values itself, all it does is copy them over into the chunk bytes. + + +9. error values +--------------- + +The meanings of the LodePNG error values: + +*) 0: no error, everything went ok +*) 1: the Encoder/Decoder has done nothing yet, so error checking makes no sense yet +*) 10: while huffman decoding: end of input memory reached without endcode +*) 11: while huffman decoding: error in code tree made it jump outside of tree +*) 13: problem while processing dynamic deflate block +*) 14: problem while processing dynamic deflate block +*) 15: problem while processing dynamic deflate block +*) 16: unexisting code while processing dynamic deflate block +*) 17: while inflating: end of out buffer memory reached +*) 18: while inflating: invalid distance code +*) 19: while inflating: end of out buffer memory reached +*) 20: invalid deflate block BTYPE encountered while decoding +*) 21: NLEN is not ones complement of LEN in a deflate block +*) 22: while inflating: end of out buffer memory reached. + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image. +*) 23: while inflating: end of in buffer memory reached +*) 24: invalid FCHECK in zlib header +*) 25: invalid compression method in zlib header +*) 26: FDICT encountered in zlib header while it's not used for PNG +*) 27: PNG file is smaller than a PNG header +*) 28: incorrect PNG signature (the first 8 bytes of the PNG file) + Maybe it's not a PNG, or a PNG file that got corrupted so that the header indicates the corruption. +*) 29: first chunk is not the header chunk +*) 30: chunk length too large, chunk broken off at end of file +*) 31: illegal PNG color type or bpp +*) 32: illegal PNG compression method +*) 33: illegal PNG filter method +*) 34: illegal PNG interlace method +*) 35: chunk length of a chunk is too large or the chunk too small +*) 36: illegal PNG filter type encountered +*) 37: illegal bit depth for this color type given +*) 38: the palette is too big (more than 256 colors) +*) 39: more palette alpha values given in tRNS, than there are colors in the palette +*) 40: tRNS chunk has wrong size for greyscale image +*) 41: tRNS chunk has wrong size for RGB image +*) 42: tRNS chunk appeared while it was not allowed for this color type +*) 43: bKGD chunk has wrong size for palette image +*) 44: bKGD chunk has wrong size for greyscale image +*) 45: bKGD chunk has wrong size for RGB image +*) 46: value encountered in indexed image is larger than the palette size (bitdepth == 8). Is the palette too small? +*) 47: value encountered in indexed image is larger than the palette size (bitdepth < 8). Is the palette too small? +*) 48: the input data is empty. Maybe a PNG file you tried to load doesn't exist or is in the wrong path. +*) 49: jumped past memory while generating dynamic huffman tree +*) 50: jumped past memory while generating dynamic huffman tree +*) 51: jumped past memory while inflating huffman block +*) 52: jumped past memory while inflating +*) 53: size of zlib data too small +*) 55: jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib. +*) 56: given output image colorType or bitDepth not supported for color conversion +*) 57: invalid CRC encountered (checking CRC can be disabled) +*) 58: invalid ADLER32 encountered (checking ADLER32 can be disabled) +*) 59: conversion to unexisting or unsupported color type or bit depth requested by encoder or decoder +*) 60: invalid window size given in the settings of the encoder (must be 0-32768) +*) 61: invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed) +*) 62: conversion from non-greyscale color to greyscale color requested by encoder or decoder. LodePNG + leaves the choice of RGB to greyscale conversion formula to the user. +*) 63: length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk (2^31-1) +*) 64: the length of the "end" symbol 256 in the Huffman tree is 0, resulting in the inability of a deflated + block to ever contain an end code. It must be at least 1. +*) 66: the length of a text chunk keyword given to the encoder is longer than the maximum 79 bytes. +*) 67: the length of a text chunk keyword given to the encoder is smaller than the minimum 1 byte. +*) 68: tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors +*) 69: unknown chunk type with "critical" flag encountered by the decoder +*) 71: unexisting interlace mode given to encoder (must be 0 or 1) +*) 72: while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0) +*) 73: invalid tIME chunk size +*) 74: invalid pHYs chunk size +*) 75: no null termination char found while decoding any kind of text chunk, or wrong length +*) 76: iTXt chunk too short to contain required bytes +*) 77: integer overflow in buffer size happened somewhere +*) 78: file doesn't exist or couldn't be opened for reading +*) 79: file couldn't be opened for writing +*) 80: tried creating a tree for 0 symbols +*) 9900-9999: out of memory while allocating chunk of memory somewhere + + +10. file IO +----------- + +For cases where you want to load the PNG image from a file, you can use your own +file loading code, or the file loading and saving functions provided with +LodePNG. These use the same unsigned char format used by the Decoder and Encoder. + +The loadFile function fills the given buffer up with the file from harddisk +with the given name. + +The saveFile function saves the contents of the given buffer to the file +with given name. Warning: this overwrites the contents that were previously in +the file if it already existed, without warning. + +Note that you don't have to decode a PNG image from a file, you can as well +retrieve the buffer another way in your code, because the decode function takes +a buffer as parameter, not a filename. + +Both C and C++ versions of the loadFile and saveFile functions are available. +For the C version of loadFile, you need to free() the buffer after use. The +C++ versions use std::vectors so they clean themselves automatically. + + +11. chunks and PNG editing +-------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if you +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + + +11.1 iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using LodePNG_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned LodePNG_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void LodePNG_chunk_type(char type[5], const unsigned char* chunk): +unsigned char LodePNG_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char LodePNG_chunk_critical(const unsigned char* chunk): +unsigned char LodePNG_chunk_private(const unsigned char* chunk): +unsigned char LodePNG_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* LodePNG_chunk_data(unsigned char* chunk): +const unsigned char* LodePNG_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned LodePNG_chunk_check_crc(const unsigned char* chunk): +void LodePNG_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* LodePNG_chunk_next(unsigned char* chunk): +const unsigned char* LodePNG_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned LodePNG_append_chunk(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned LodePNG_create_chunk(unsigned char** out, size_t* outlength, unsigned length, const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + + +11.2 chunks in infoPng +---------------------- + +The LodePNG_InfoPng struct contains a struct LodePNG_UnknownChunks in it. This +struct has 3 buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +infoPng.unknown_chunks.data[0] is the chunks before PLTE +infoPng.unknown_chunks.data[1] is the chunks after PLTE, before IDAT +infoPng.unknown_chunks.data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.rememberUnknownChunks to 1. By default, this option +is off and is 0. + +The encoder will always encode unknown chunks that are stored in the infoPng. If +you need it to add a particular chunk that isn't known by LodePNG, you can use +LodePNG_append_chunk or LodePNG_create_chunk to the chunk data in +infoPng.unknown_chunks.data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there and LodePNG will generate the chunk. + + +12. compiler support +-------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +Use optimization! For both the encoder and decoder, compiling with the best +optimizations makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible resulting in bad things. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.2.2 on Linux. + +*) Mingw and Bloodshed DevC++ + +The Mingw compiler (a port of gcc) used by Bloodshed DevC++ for Windows is fully +supported by LodePNG. + +*) Visual Studio 2005 and Visual C++ 2005 Express Edition + +Versions 20070604 up to 20080107 have been tested on VS2005 and work. There are no +warnings, except two warnings about 'fopen' being deprecated. 'fopen' is a function +required by the C standard, so this warning is the fault of VS2005, it's nice of +them to enforce secure code, however the multiplatform LodePNG can't follow their +non-standard extensions. LodePNG is fully ISO C90 compliant. + +If you're using LodePNG in VS2005 and don't want to see the deprecated warnings, +put this on top of lodepng.h before the inclusions: #define _CRT_SECURE_NO_DEPRECATE + +*) Visual Studio 6.0 + +The C++ version of LodePNG was not supported by Visual Studio 6.0 because Visual +Studio 6.0 doesn't follow the C++ standard and implements it incorrectly. +The current C version of LodePNG has not been tested in VS6 but may work now. + +*) Comeau C/C++ + +Vesion 20070107 compiles without problems on the Comeau C/C++ Online Test Drive +at http://www.comeaucomputing.com/tryitout in both C90 and C++ mode. + +*) Compilers on Macintosh + +I'd love to support Macintosh but don't have one available to test it on. +If it doesn't work with your compiler, maybe it can be gotten to work with the +gcc compiler for Macintosh. Someone reported that it doesn't work well at all +for Macintosh. All information on attempts to get it to work on Mac is welcome. + +*) Other Compilers + +If you encounter problems on other compilers, I'm happy to help out make LodePNG +support the compiler if it supports the ISO C90 and C++ standard well enough. If +the required modification to support the compiler requires using non standard or +lesser C/C++ code or headers, I won't support it. + + +13. examples +------------ + +This decoder and encoder example show the most basic usage of LodePNG (using the +classes, not the simple functions, which would be trivial) + +More complex examples can be found in: +-lodepng_examples.c: 9 different examples in C, such as showing the image with SDL, ... +-lodepng_examples.cpp: the exact same examples in C++ using the C++ wrapper of LodePNG + + +13.1. decoder C++ example +------------------------- + +//////////////////////////////////////////////////////////////////////////////// +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector buffer, image; + LodePNG::loadFile(buffer, filename); //load the image file with given filename + LodePNG::Decoder decoder; + decoder.decode(image, buffer.size() ? &buffer[0] : 0, (unsigned)buffer.size()); //decode the png + + //if there's an error, display it + if(decoder.hasError()) std::cout << "error: " << decoder.getError() << std::endl; + + //the pixels are now in the vector "image", use it as texture, draw it, ... +} + +//alternative version using the "simple" function +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned w, h; + unsigned error = LodePNG::decode(image, w, h, filename); + + //if there's an error, display it + if(error != 0) std::cout << "error: " << error << std::endl; + + //the pixels are now in the vector "image", use it as texture, draw it, ... +} +//////////////////////////////////////////////////////////////////////////////// + + +13.2 encoder C++ example +------------------------ + +//////////////////////////////////////////////////////////////////////////////// +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + //check if user gave a filename + if(argc <= 1) + { + std::cout << "please provide a filename to save to\n"; + return 0; + } + + //generate some image + std::vector image; + image.resize(512 * 512 * 4); + for(unsigned y = 0; y < 512; y++) + for(unsigned x = 0; x < 512; x++) + { + image[4 * 512 * y + 4 * x + 0] = 255 * !(x & y); + image[4 * 512 * y + 4 * x + 1] = x ^ y; + image[4 * 512 * y + 4 * x + 2] = x | y; + image[4 * 512 * y + 4 * x + 3] = 255; + } + + //encode and save + std::vector buffer; + LodePNG::Encoder encoder; + encoder.encode(buffer, image, 512, 512); + LodePNG::saveFile(buffer, argv[1]); + + //the same as the 4 lines of code above, but in 1 call: + //LodePNG::encode(argv[1], image, 512, 512); +} +//////////////////////////////////////////////////////////////////////////////// + + +13.3 Decoder C example +---------------------- + +This example loads the PNG in 1 function call + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t w, h; + + if(argc <= 1) return 0; + + error = LodePNG_decode3(&image, &w, &h, filename); + + free(image); +} + + +14. LodeZlib +------------ + +Also available in the interface is LodeZlib. Both C and C++ versions of these +functions are available. The interface is similar to that of the "simple" PNG +encoding and decoding functions. + +LodeZlib can be used to zlib compress and decompress a buffer. It cannot be +used to create gzip files however. Also, it only supports the part of zlib +that is required for PNG, it does not support compression and decompression +with dictionaries. + + +15. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also vareous fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release + + +16. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2008 Lode Vandevenne +*/ diff --git a/minorGems/graphics/converters/testPNG.cpp b/minorGems/graphics/converters/testPNG.cpp new file mode 100644 index 0000000..c1a16fc --- /dev/null +++ b/minorGems/graphics/converters/testPNG.cpp @@ -0,0 +1,44 @@ +#include "PNGImageConverter.h" + +#include "minorGems/graphics/Image.h" + +#include "minorGems/io/file/FileOutputStream.h" + +#include "minorGems/system/Time.h" + + +int main() { + + int imageSize = 640; + Image testImage( imageSize, imageSize, 3, false ); + + + // red fades toward bottom + // green fades toward right + double *red = testImage.getChannel( 0 ); + double *green = testImage.getChannel( 1 ); + + for( int y=0; y +#include + +#include + +// include the jpeg library as a C file. +// (yuk... spent way too much time trying to figure this one out!) +extern "C" { +#include + } + +/* + * is used for the decompression + * error recovery mechanism. + */ + +#include + + + +void JPEGImageConverter::formatImage( Image *inImage, + OutputStream *inStream ) { + + if( inImage->getNumChannels() != 3 ) { + printf( "JPEGImageConverter only works on 3-channel images.\n" ); + return; + } + + // most of this code was copied without modification from + // IJG's example.c + + // This struct contains the JPEG compression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + // It is possible to have several such structures, representing multiple + // compression/decompression processes, in existence at once. We refer + // to any one struct (and its associated working data) as a "JPEG object". + + struct jpeg_compress_struct cinfo; + + // This struct represents a JPEG error handler. It is declared separately + // because applications often want to supply a specialized error handler + // (see the second half of this file for an example). But here we just + // take the easy way out and use the standard error handler, which will + // print a message on stderr and call exit() if compression fails. + // Note that this struct must live as long as the main JPEG parameter + // struct, to avoid dangling-pointer problems. + + struct jpeg_error_mgr jerr; + + // More stuff + FILE * outfile; // target file + JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s] + int row_stride; // physical row width in image buffer + + // Step 1: allocate and initialize JPEG compression object + + // We have to set up the error handler first, in case the initialization + // step fails. (Unlikely, but it could happen if you are out of memory.) + // This routine fills in the contents of struct jerr, and returns jerr's + // address which we place into the link field in cinfo. + + cinfo.err = jpeg_std_error( &jerr ); + // Now we can initialize the JPEG compression object. + jpeg_create_compress( &cinfo ); + + // Step 2: specify data destination (eg, a file) + // Note: steps 2 and 3 can be done in either order. + + // use a temp file with a random name to make this more + // thread-safe + char *fileName = new char[99]; + sprintf( fileName, "temp%d.dat", rand() ); + + // Here we use the library-supplied code to send compressed data to a + // stdio stream. You can also write your own code to do something else. + // VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + // requires it in order to write binary files. + + if( ( outfile = fopen( fileName, "wb" ) ) == NULL ) { + printf( "can't open jpeg conversion temp file %s\n", fileName ); + return; + } + + jpeg_stdio_dest( &cinfo, outfile ); + + + // Step 3: set parameters for compression + + // First we supply a description of the input image. + // Four fields of the cinfo struct must be filled in: + + + // image width and height, in pixels + cinfo.image_width = inImage->getWidth(); + cinfo.image_height = inImage->getHeight(); + cinfo.input_components = 3; // # of color components per pixel + cinfo.in_color_space = JCS_RGB; // colorspace of input image + // Now use the library's routine to set default compression parameters. + // (You must set at least cinfo.in_color_space before calling this, + // since the defaults depend on the source color space.) + + jpeg_set_defaults( &cinfo ); + // Now you can set any non-default parameters you wish to. + // Here we just illustrate the use of + // quality (quantization table) scaling: + + jpeg_set_quality( &cinfo, mQuality, + TRUE ); // limit to baseline-JPEG values + + // Step 4: Start compressor + + // TRUE ensures that we will write a complete interchange-JPEG file. + // Pass TRUE unless you are very sure of what you're doing. + + jpeg_start_compress( &cinfo, TRUE ); + + // Step 5: while (scan lines remain to be written) + // jpeg_write_scanlines(...); + + // Here we use the library's state variable cinfo.next_scanline as the + // loop counter, so that we don't have to keep track ourselves. + // To keep things simple, we pass one scanline per call; you can pass + // more if you wish, though. + + // JSAMPLEs per row in image_buffer + row_stride = cinfo.image_width * 3; + + // channels of inImage, which we will need to pull pixel values out of + double *redChannel = inImage->getChannel(0); + double *greenChannel = inImage->getChannel(1); + double *blueChannel = inImage->getChannel(2); + + // array that we will copy inImage pixels into + // one scanline at a time + row_pointer[0] = new JSAMPLE[ row_stride ]; + + //int rowNumber = 0; + + while( cinfo.next_scanline < cinfo.image_height ) { + // jpeg_write_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could pass + // more than one scanline at a time if that's more convenient. + + + // make a scanline + + + int yOffset = cinfo.next_scanline * cinfo.image_width; + + // for each pixel in the row + for( int p=0; pgetLength(); + unsigned char *fileBuffer = new unsigned char[ fileLength ]; + fread( fileBuffer, 1, fileLength, inFile ); + + // now write the entire buffer to our output stream + inStream->write( fileBuffer, + fileLength ); + + delete [] fileBuffer; + + delete file; + + fclose( inFile ); + + // delete this temporary file + remove( fileName ); + + delete [] fileName; + + + // And we're done! + + } + + + +// copied this directly from IJG's example.c +//extern "C" { + + /* + * ERROR HANDLING: + * + * The JPEG library's standard error handler (jerror.c) is divided into + * several "methods" which you can override individually. This lets you + * adjust the behavior without duplicating a lot of code, which you might + * have to update with each future release. + * + * Our example here shows how to override the "error_exit" method so that + * control is returned to the library's caller when a fatal error occurs, + * rather than calling exit() as the standard error_exit method does. + * + * We use C's setjmp/longjmp facility to return + * control. This means that the + * routine which calls the JPEG library must + * first execute a setjmp() call to + * establish the return point. We want the replacement error_exit to do a + * longjmp(). But we need to make the setjmp buffer accessible to the + * error_exit routine. To do this, we make a private extension of the + * standard JPEG error handler object. (If we were using C++, we'd say we + * were making a subclass of the regular error handler.) + * + * Here's the extended error handler struct: + */ + + struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + + jmp_buf setjmp_buffer; /* for return to caller */ + }; + + typedef struct my_error_mgr * my_error_ptr; + + + /* + * Here's the routine that will replace the standard error_exit method: + */ + + METHODDEF(void) my_error_exit( j_common_ptr cinfo ) { + /* cinfo->err really points to a my_error_mgr struct, + so coerce pointer + */ + my_error_ptr myerr = (my_error_ptr)( cinfo->err ); + + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + (*cinfo->err->output_message)( cinfo ); + + /* Return control to the setjmp point */ + longjmp( myerr->setjmp_buffer, 1 ); + } + +// } + + + +Image *JPEGImageConverter::deformatImage( InputStream *inStream ) { + + // use a temp file with a random name to make this more + // thread-safe + char *fileName = new char[99]; + sprintf( fileName, "temp%d.dat", rand() ); + + FILE *tempFile = fopen( fileName, "wb" ); + + if( tempFile == NULL ) { + printf( "can't open jpeg conversion temp file %s\n", fileName ); + return NULL; + } + + // buffer for dumping stream to temp file + unsigned char *tempBuffer = new unsigned char[1]; + unsigned char previousByte = 0; + + // dump the JPEG stream from the input stream into tempFile + // so that we can pass this file to libjpeg + + /* + // optimization: use a buffer to prevent too many fwrite calls + int bufferLength = 5000; + unsigned char *fileBuffer = new unsigned char[ bufferLength ]; + int currentBufferPosition = 0; + + while( !( tempBuffer[0] == 0xD9 && previousByte == 0xFF ) ) { + previousByte = tempBuffer[0]; + + inStream->read( tempBuffer, 1 ); + + fileBuffer[currentBufferPosition] = tempBuffer[0]; + + if( currentBufferPosition == bufferLength - 1 ) { + // at the end of the file buffer + fwrite( fileBuffer, 1, bufferLength, tempFile ); + currentBufferPosition = 0; + } + else { + // keep filling the fileBuffer + currentBufferPosition++; + } + } + // now write remaining fileBuffer data to file + fwrite( fileBuffer, 1, currentBufferPosition + 1, tempFile ); + + delete [] fileBuffer; + */ + + // write until EOI sequence seen (0xFFD9) + while( !( tempBuffer[0] == 0xD9 && previousByte == 0xFF ) ) { + previousByte = tempBuffer[0]; + + inStream->read( tempBuffer, 1 ); + + fwrite( tempBuffer, 1, 1, tempFile ); + } + + // end of jpeg stream reached. + fclose( tempFile ); + delete [] tempBuffer; + + + // the remainder of this method was mostly copied from + // IJG's example.c + + + + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct my_error_mgr jerr; + /* More stuff */ + FILE * infile; /* source file */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + + /* In this example we want to open the input + * file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + if( ( infile = fopen( fileName, "rb" ) ) == NULL ) { + printf( "can't open jpeg conversion temp file %s\n", fileName ); + return NULL; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + /* Establish the setjmp return context for my_error_exit to use. */ + if( setjmp( jerr.setjmp_buffer ) ) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, + * close the input file, and return. + */ + jpeg_destroy_decompress( &cinfo ); + fclose( infile ); + printf( "error in decompressing jpeg from stream.\n" ); + return NULL; + } + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress( &cinfo ); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src( &cinfo, infile ); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header( &cinfo, TRUE ); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress( &cinfo ); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + + int imageWidth = cinfo.output_width; + int imageHeight = cinfo.output_height; + + // the return image with 3 channels + Image *returnImage = new Image( imageWidth, imageHeight, 3, false ); + + // channels of returnImage, + // which we will need to put pixel values into of + double *redChannel = returnImage->getChannel(0); + double *greenChannel = returnImage->getChannel(1); + double *blueChannel = returnImage->getChannel(2); + int currentIndex = 0; + + + row_stride = cinfo.output_width * cinfo.output_components; + /* Make a one-row-high sample array that + * will go away when done with image + */ + buffer = ( *cinfo.mem->alloc_sarray ) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1 ); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + int rowNumber = 0; + double inv255 = 1.0 / 255.0; + + while( cinfo.output_scanline < cinfo.output_height ) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + (void) jpeg_read_scanlines( &cinfo, buffer, 1 ); + + // write the scanline into returnImage + + int yOffset = rowNumber * cinfo.output_width; + + // for each pixel in the row + // copy it into the return image channels + for( int p=0; p= inHeight ) { + endBoxY = inHeight - 1; + + // box hanging over bottom edge + // no rows to add to sum + addARow = false; + } + + int boxSizeY = endBoxY - startBoxY + 1; + + for( int x=0; x= inWidth ) { + endBoxX = inWidth - 1; + } + + int boxSizeX = endBoxX - startBoxX + 1; + + // sum all pixels in the box around this pixel + double sum = 0; + + + if( ! lastRowSumsSet ) { + // do the "slow way" for the first row + + for( int boxY = startBoxY; boxY<=endBoxY; boxY++ ) { + int yBoxIndexContrib = boxY * inWidth; + + double rowSum = 0; + for( int boxX = startBoxX; boxX<=endBoxX; boxX++ ) { + + rowSum += inChannel[ yBoxIndexContrib + boxX ]; + } + + sum += rowSum; + + // store row sum for future use + singleRowBoxSums[ yBoxIndexContrib + x ] = rowSum; + } + + } + else { + // we have sum for this pixel from the previous row + // use it to avoid looping over entire box again + + sum = lastRowSums[ x ]; + + if( addARow ) { + // add pixels from row at endBoxY + + + int yBoxIndexContrib = endBoxY * inWidth; + + double rowSum = 0; + for( int boxX = startBoxX; boxX<=endBoxX; boxX++ ) { + rowSum += inChannel[ yBoxIndexContrib + boxX ]; + } + + sum += rowSum; + + // store it for later when we will need to subtract it + singleRowBoxSums[ yBoxIndexContrib + x ] = rowSum; + } + if( subtractARow ) { + // subtract pixels from startBoxY of previous row's box + + int yBoxIndexContrib = (startBoxY - 1) * inWidth; + + // use pre-computed sum for the row we're subtracting + sum -= singleRowBoxSums[ yBoxIndexContrib + x ]; + } + + } + + + // divide by number of pixels to complete the average + blurredChannel[ pixelIndex ] = sum / (boxSizeX * boxSizeY); + + // save to use when computing box sum for next row + lastRowSums[ x ] = sum; + } + + // we now have valid last row sums that we can use for + // all the rest of the rows + lastRowSumsSet = true; + + } + + // copy blurred image back into passed-in image + memcpy( inChannel, blurredChannel, sizeof(double) * inWidth * inHeight ); + + delete [] blurredChannel; + delete [] lastRowSums; + delete [] singleRowBoxSums; + } + +#endif diff --git a/minorGems/graphics/filters/InvertFilter.h b/minorGems/graphics/filters/InvertFilter.h new file mode 100644 index 0000000..1cf2a39 --- /dev/null +++ b/minorGems/graphics/filters/InvertFilter.h @@ -0,0 +1,38 @@ +/* + * Modification History + * + * 2000-December-21 Jason Rohrer + * Created. + */ + + +#ifndef INVERT_FILTER_INCLUDED +#define INVERT_FILTER_INCLUDED + +#include "minorGems/graphics/ChannelFilter.h" + +/** + * Filter that inverts the values in a channel. + * + * @author Jason Rohrer + */ +class InvertFilter : public ChannelFilter { + + public: + + // implements the ChannelFilter interface + void apply( double *inChannel, int inWidth, int inHeight ); + }; + + + +inline void InvertFilter::apply( double *inChannel, + int inWidth, int inHeight ) { + + int numPixels = inWidth * inHeight; + for( int i=0; i= inHeight ) { + endBoxY = inHeight - 1; + } + + int boxSizeY = endBoxY - startBoxY + 1; + + + for( int x=0; x= inWidth ) { + endBoxX = inWidth - 1; + } + + int boxSizeX = endBoxX - startBoxX + 1; + int *buffer = new int[boxSizeX * boxSizeY]; + + // sum all pixels in the box around this pixel + for( int boxY = startBoxY; boxY<=endBoxY; boxY++ ) { + int yBoxIndexContrib = boxY * inWidth; + int yBoxContrib = boxSizeX * ( boxY-startBoxY ); + + for( int boxX = startBoxX; boxX<=endBoxX; boxX++ ) { + //buffer[boxSizeX*(boxY-startBoxY)+(boxX-startBoxX)] = (int)(1000.0 * inChannel[yBoxIndexContrib + boxX]); + buffer[ yBoxContrib + ( boxX-startBoxX ) ] = intChannel[ yBoxIndexContrib + boxX ]; + } + } + + medianChannel[ yIndexContrib + x ] = (double)quick_select( buffer, boxSizeX*boxSizeY ) / 1000.0; + + delete [] buffer; + } + } + + + // copy blurred image back into passed-in image + memcpy( inChannel, medianChannel, sizeof(double) * inWidth * inHeight ); + + delete [] medianChannel; + delete [] intChannel; +} + +#endif diff --git a/minorGems/graphics/filters/MultiFilter.h b/minorGems/graphics/filters/MultiFilter.h new file mode 100644 index 0000000..c9d527e --- /dev/null +++ b/minorGems/graphics/filters/MultiFilter.h @@ -0,0 +1,87 @@ +/* + * Modification History + * + * 2000-December-21 Jason Rohrer + * Created. + */ + + +#ifndef MULTI_FILTER_INCLUDED +#define MULTI_FILTER_INCLUDED + +#include "minorGems/graphics/ChannelFilter.h" +#include "minorGems/util/SimpleVector.h" + +/** + * Filter that applies a collection of filters. + * + * @author Jason Rohrer + */ +class MultiFilter : public ChannelFilter { + + public: + + /** + * Constructs a MultiFilter. + */ + MultiFilter(); + + + ~MultiFilter(); + + + /** + * Adds a filter to end of this MultiFilter. Filters + * are applied in the order in which they are added. + * + * @param inFilter filter to add. Is not + * destroyed when the MultiFilter is destroyed. + */ + void addFilter( ChannelFilter *inFilter ); + + + /** + * Removes a filter. + * + * @param inFilter filter to remove. + */ + void removeFilter( ChannelFilter *inFilter ); + + + // implements the ChannelFilter interface + void apply( double *inChannel, int inWidth, int inHeight ); + + + private: + SimpleVector *mFilterVector; + + }; + + + +inline MultiFilter::MultiFilter() + : mFilterVector( new SimpleVector() ) { + + } + + + +inline MultiFilter::~MultiFilter() { + delete mFilterVector; + } + + + +inline void MultiFilter::apply( double *inChannel, + int inWidth, int inHeight ) { + + int numFilters = mFilterVector->size(); + for( int i=0; igetElement( i ) ); + thisFilter->apply( inChannel, inWidth, inHeight ); + } + } + + + +#endif diff --git a/minorGems/graphics/filters/SeamlessFilter.h b/minorGems/graphics/filters/SeamlessFilter.h new file mode 100644 index 0000000..c34a741 --- /dev/null +++ b/minorGems/graphics/filters/SeamlessFilter.h @@ -0,0 +1,155 @@ +/* + * Modification History + * + * 2001-January-19 Jason Rohrer + * Created. + * + * 2006-September-25 Jason Rohrer + * Added control over blending curve. + * Reorderd loop for efficiency. + */ + + +#ifndef SEAMLESS_FILTER_INCLUDED +#define SEAMLESS_FILTER_INCLUDED + +#include "minorGems/graphics/ChannelFilter.h" + + +#include + + + +/** + * Filter that turns any image into a seamless tile. + * + * @author Jason Rohrer + */ +class SeamlessFilter : public ChannelFilter { + + public: + + /** + * Constructs a filter. + * + * @param inBlendCurveExponent the exponent of the blending curve + * as we near the edge of the image. Defaults to 1 for a linear + * blending curve. Setting to 2 would result in a parabolic + * curve which would push the blended area closer to the edge + * and leave more high-contrast, unblended image data near + * the middle. + */ + SeamlessFilter( double inBlendCurveExponent = 1 ); + + // implements the ChannelFilter interface + virtual void apply( double *inChannel, int inWidth, int inHeight ); + + + private: + + double mBlendingCurveExponent; + + }; + + + +inline SeamlessFilter::SeamlessFilter( double inBlendCurveExponent ) + : mBlendingCurveExponent( inBlendCurveExponent ) { + + } + + + +inline void SeamlessFilter::apply( double *inChannel, + int inWidth, int inHeight ) { + + int numPixels = inWidth * inHeight; + + int halfHigh = inHeight/2; + int halfWide = inWidth/2; + + // mix the texture with itself. + // as we move closer and closer to the texture edge, we should + // start mixing in more and more of the center values + + double *channelCopy = new double[ numPixels ]; + + + // first, create an image with no seams if tiled horizontally + // (getting rid of vertical seams) + + for( int x=0; x= mThreshold ) { + inChannel[i] = 1.0; + } + else { + inChannel[i] = 0.0; + } + } + } + +#endif diff --git a/minorGems/graphics/filters/quickselect.h b/minorGems/graphics/filters/quickselect.h new file mode 100644 index 0000000..1878932 --- /dev/null +++ b/minorGems/graphics/filters/quickselect.h @@ -0,0 +1,41 @@ +/* + * This Quickselect routine is based on the algorithm described in + * "Numerical recipies in C", Second Edition, + * Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5 + */ + +#define ELEM_SWAP(a,b) { register int t=(a);(a)=(b);(b)=t; } + +int quick_select(int arr[], int n) +{ + int low, high; + int median; + int middle, ll, hh; low = 0 ; high = n-1 ; median = (low + high) / 2; + for (;;) { + if (high <= low) /* One element only */ + return arr[median] ; if (high == low + 1) { /* Two elements only */ + if (arr[low] > arr[high]) + ELEM_SWAP(arr[low], arr[high]) ; + return arr[median] ; + } /* Find median of low, middle and high items; swap into position low */ + middle = (low + high) / 2; + if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]) ; + if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]) ; + if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]) ; /* Swap low item (now in position middle) into position (low+1) */ + ELEM_SWAP(arr[middle], arr[low+1]) ; /* Nibble from each end towards middle, swapping items when stuck */ + ll = low + 1; + hh = high; + for (;;) { + do ll++; while (arr[low] > arr[ll]) ; + do hh--; while (arr[hh] > arr[low]) ; if (hh < ll) + break; ELEM_SWAP(arr[ll], arr[hh]) ; + } /* Swap middle item (in position low) back into correct position */ + ELEM_SWAP(arr[low], arr[hh]) ; /* Re-set active partition */ + if (hh <= median) + low = ll; + if (hh >= median) + high = hh - 1; + } +} + +#undef ELEM_SWAP diff --git a/minorGems/graphics/getKey.h b/minorGems/graphics/getKey.h new file mode 100644 index 0000000..f7b2a7c --- /dev/null +++ b/minorGems/graphics/getKey.h @@ -0,0 +1,71 @@ +// Jason Rohrer +// getKey.h + +/** +* +* general interface for getting current key depressed +* Implemented by a graphix framework on a particular platform +* +* +* Created 12-15-99 +* Mods: +* +*/ + + +// returns true if key represented by given key code is down +char getKeyDown( int vKeyCode ); + +// returns true if key is up +char getKeyUp( int vKeyCode ); + + + + + +/** + + + Sample PC implementation + +#include + +getKeyDown( int vKeyCode ) { + return ((GetAsyncKeyState(vKeyCode) & 0x8000) ? true : false); + } +getKeyUp( int vKeyCode ) { + return ((GetAsyncKeyState(vKeyCode) & 0x8000) ? false : true); + } + + + +Sample Mac implementation + + +#include + +getKeyDown( int vKeyCode ) { + KeyMapByteArray keyArray; + GetKeys( keyArray ); + + int arrayInd = vKeyCode >> 3; // divide by 8 to get start array index of key code + + unsigned char neededByte = keyArray[ arrayInd ]; + + return (neededByte >> vKeyCode % 8) && 0x01; // trim off bit needed + } + +getKeyUp( int vKeyCode ) { + KeyMapByteArray keyArray; + GetKeys( keyArray ); + + int arrayInd = vKeyCode >> 3; // divide by 8 to get start array index of key code + + unsigned char neededByte = keyArray[ arrayInd ]; + + return !((neededByte >> vKeyCode % 8) && 0x01); // trim off bit needed, and invert + } + + + +*/ \ No newline at end of file diff --git a/minorGems/graphics/getMouse.h b/minorGems/graphics/getMouse.h new file mode 100644 index 0000000..cd815cf --- /dev/null +++ b/minorGems/graphics/getMouse.h @@ -0,0 +1,16 @@ +// Jason Rohrer +// getMouse.h + +/** +* +* general interface for getting current mouse position +* Implemented by a graphix framework on a particular platform +* +* +* Created 3-21-2000 +* Mods: +* +*/ + + +void getMouse( int *x, int *y ); \ No newline at end of file diff --git a/minorGems/graphics/keyCodes.h b/minorGems/graphics/keyCodes.h new file mode 100644 index 0000000..27712ac --- /dev/null +++ b/minorGems/graphics/keyCodes.h @@ -0,0 +1,52 @@ +// Jason Rohrer +// keyCodes.h + +/** +* +* Header for defining key codes for various platforms +* +* +* Created 12-15-99 +* Mods: +* Jason Rohrer 3-23-2000 Added more key codes +* +*/ + + +#ifdef WINDOWS_KEY_CODES + + #define M_KEY 0x4D + #define N_KEY 0x4E + + #define S_KEY 0x53 + + #define Q_KEY 0x51 + + #define L_KEY 0x4C + + #define R_KEY 0x52 + + #define T_KEY 0x54 + +#endif + + + + +#ifdef MAC_KEY_CODES + + #define M_KEY 0x2E + #define N_KEY 0x2D + + #define S_KEY 0x01 + + #define Q_KEY 0x0C + + + #define L_KEY 0x25 + + #define R_KEY 0x0F + + #define T_KEY 0x11 + +#endif \ No newline at end of file diff --git a/minorGems/graphics/linux/SDLTest.cpp b/minorGems/graphics/linux/SDLTest.cpp new file mode 100644 index 0000000..4639e12 --- /dev/null +++ b/minorGems/graphics/linux/SDLTest.cpp @@ -0,0 +1,221 @@ + +//#include + +#include "minorGems/graphics/ScreenGraphics.h" +#include "minorGems/graphics/GraphicBuffer.h" +#include "minorGems/graphics/Color.h" + +#include "minorGems/ui/Mouse.h" + +#include +#include +#include + +int numIcons = 200; + +int currentStep = 0; +int numSteps = 1000; + +void catch_int(int sig_num) { + printf( "Quiting..." ); + currentStep = numSteps; + signal( SIGINT, catch_int); + } + +int main() { + int i, j; + + // let catch_int handle interrupt (^c) + signal( SIGINT, catch_int); + + ScreenGraphics *graphics = new ScreenGraphics( 640, 480 ); + Mouse *mouse = new Mouse(2); + + + unsigned long *pixelBuff = new unsigned long[ 640 * 480 ]; + + GraphicBuffer *buffer = new GraphicBuffer( pixelBuff, 640, 480 ); + + IconMap **maps = new IconMap*[numIcons]; + + IconMap *mouseMap; + + IconMap *mouseMap1 = new IconMap( 10, 10 ); + Color mouseColor1( 1.0, 0.0, 0.0, 1.0 ); + for( int y=0; y<10; y++ ) { + for( int x=0; x<10; x++ ) { + mouseMap1->imageMap[ mouseMap1->yOffset[y] + x ] = + mouseColor1.composite; + } + } + IconMap *mouseMap2 = new IconMap( 10, 10 ); + Color mouseColor2( 0.0, 1.0, 0.0, 1.0 ); + for( int y=0; y<10; y++ ) { + for( int x=0; x<10; x++ ) { + mouseMap2->imageMap[ mouseMap2->yOffset[y] + x ] = + mouseColor2.composite; + } + } + + IconMap *mouseMap3 = new IconMap( 10, 10 ); + Color mouseColor3( 0.0, 0.0, 1.0, 1.0 ); + for( int y=0; y<10; y++ ) { + for( int x=0; x<10; x++ ) { + mouseMap3->imageMap[ mouseMap3->yOffset[y] + x ] = + mouseColor3.composite; + } + } + + mouseMap = mouseMap1; + + Color c( 0.0f, 0.0f, 0.0f, 1.0f ); + buffer->fill( c ); + + float *xPos = new float[numIcons]; + float *yPos = new float[numIcons]; + float *xDelta = new float[numIcons]; + float *yDelta = new float[numIcons]; + + for( i=0; iimageMap[ maps[i]->yOffset[y] + x ] = + randColor.composite; + } + } + } + int mouseX = 0; + int mouseY = 0; + char buttonDown1 = false; + char buttonDown2 = false; + for( currentStep=0; currentSteperaseIconMap( mouseMap, mouseX, mouseY, c ); + + mouse->getLocation( &mouseX, &mouseY ); + + buffer->drawIconMap( mouseMap, mouseX, mouseY ); + + if( !buttonDown1 && mouse->isButtonDown(0) ) { + buttonDown1 = true; + mouseMap = mouseMap2; + } + else if( buttonDown1 && !mouse->isButtonDown(0) ) { + buttonDown1 = false; + mouseMap = mouseMap1; + } + + else if( !buttonDown2 && mouse->isButtonDown(1) ) { + buttonDown2 = true; + mouseMap = mouseMap3; + } + else if( buttonDown2 && !mouse->isButtonDown(1) ) { + buttonDown2 = false; + mouseMap = mouseMap1; + } + + + + for( i=0; ieraseIconMap( maps[i], (int)( xPos[i] - 5 ), + (int)( yPos[i] - 5 ), c ); + if( xPos[i] > 640 || xPos[i] < 0 ) { + xDelta[i] = -xDelta[i]; + } + xPos[i] += xDelta[i]; + if( yPos[i] > 480 || yPos[i] < 0 ) { + yDelta[i] = -yDelta[i]; + } + yPos[i] += yDelta[i]; + + buffer->drawIconMap( maps[i], (int)( xPos[i] - 5 ), + (int)( yPos[i] - 5 ) ); + } + + graphics->swapBuffers( buffer ); + } + + + /* + for( int i=0; i<100; i++ ) { + if( i%2 == 0 ) { + Color c( 1.0f, 0.0f, 0.0f, 1.0f ); + buffer->fill( c ); + } + else { + Color c( 0.0f, 1.0f, 0.0f, 1.0f ); + buffer->fill( c ); + } + graphics->swapBuffers( buffer ); + } + */ + printf( "Done.\n" ); + return 0; + /* + if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + exit(1); + } + + //atexit( SDL_Quit ); + + SDL_Surface *screen = SDL_SetVideoMode( 640, 480, + 32, SDL_HWSURFACE | SDL_DOUBLEBUF );//| SDL_FULLSCREEN ); + + if ( screen == NULL ) { + printf( "Couldn't set 640x480x32 video mode: %s\n", + SDL_GetError() ); + exit(1); + } + + for( int i=0; i< 100; i++ ) { + if ( SDL_MUSTLOCK(screen) ) { + if ( SDL_LockSurface(screen) < 0 ) { + printf( "Couldn't lock screen: %s\n", + SDL_GetError()); + exit(1); + } + } + + Uint32 value; + if( i%2 == 0 ) { + value = 0x0; + } + else { + value = 0xFFFFFFFF; + } + + Uint32 *buffer = (Uint32 *)( screen->pixels ); + for ( int y=0; yh; y++ ) { + for ( int x=0; xw; x++ ) { + + int r = ( ( ( x * 255 ) / screen->w ) + i ) % 255; + int g = ( ( ( y * 255 ) / screen->h ) + i ) % 255; + int b = 0; + int a = 255; + + //buffer[ y * screen->w + x ] = (a << 24) | (r << 16) | (g << 8) | b; + buffer[ y * screen->w + x ] = value; + } + } + + if ( SDL_MUSTLOCK(screen) ) { + SDL_UnlockSurface(screen); + } + + //SDL_UpdateRect( screen, 0, 0, screen->w, screen->h ); + SDL_Flip( screen ); + } + SDL_Quit(); + + SDL_Delay(2000); + + exit(0); + */ + } diff --git a/minorGems/graphics/linux/ScreenGraphicsLinux.cpp b/minorGems/graphics/linux/ScreenGraphicsLinux.cpp new file mode 100644 index 0000000..246c55b --- /dev/null +++ b/minorGems/graphics/linux/ScreenGraphicsLinux.cpp @@ -0,0 +1,153 @@ +/* + * Modification History + * + * 2000-November-18 Jason Rohrer + * Created. + * + * 2000-November-19 Jason Rohrer + * Change so that it doesn't use SDL's interrupt parachute, i.e., + * so it will quit on ^c. + * + * 2001-May-1 Jason Rohrer + * Changed to use more standard SDL include location. + * + * 2006-April-28 Jason Rohrer + * Added functions for more direct access to screen pixels. + * + * 2006-June-26 Jason Rohrer + * Added support for dirty rectangle. + * Added resize support. + */ + + +#include "minorGems/graphics/ScreenGraphics.h" + +#include + +/** + * Note: Linux implementation uses windowed mode. + */ + +ScreenGraphics::ScreenGraphics( int inWidth, int inHeight ) + : mWidth( inWidth ), mHeight( inHeight ) { + + if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return; + } + + SDL_Surface *screen = SDL_SetVideoMode( mWidth, mHeight, + 32, SDL_SWSURFACE ); + + if ( screen == NULL ) { + printf( "Couldn't set %dx%dx32 video mode: %s\n", mWidth, mHeight, + SDL_GetError() ); + return; + } + + + mNativeObjectPointer = (void*)screen; + } + + + +ScreenGraphics::~ScreenGraphics() { + SDL_Quit(); + } + + + +void ScreenGraphics::resize( int inNewWidth, int inNewHeight ) { + + mWidth = inNewWidth; + mHeight = inNewHeight; + + + SDL_Surface *screen = SDL_SetVideoMode( mWidth, mHeight, + 32, SDL_SWSURFACE ); + + if ( screen == NULL ) { + printf( "Couldn't set %dx%dx32 video mode: %s\n", mWidth, mHeight, + SDL_GetError() ); + return; + } + + mNativeObjectPointer = (void*)screen; + } + + + +char ScreenGraphics::isResolutionAvailable( int inWidth, int inHeight ) { + if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 ) { + printf( "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return false; + } + + // ask SDL if this screen size is supported + char videoModeOk = + (char)SDL_VideoModeOK( inWidth, inHeight, 32, SDL_SWSURFACE ); + + SDL_Quit(); + + + return videoModeOk; + } + + + +void ScreenGraphics::swapBuffers( GraphicBuffer *inOutBuffer ) { + if( inOutBuffer->getHeight() != mHeight || + inOutBuffer->getWidth() != mWidth ) { + printf( "Buffer of incorrect size passed to screen.\n" ); + return; + } + + SDL_Surface *screen = (SDL_Surface*)mNativeObjectPointer; + + // check if we need to lock the screen + if( SDL_MUSTLOCK( screen ) ) { + if( SDL_LockSurface( screen ) < 0 ) { + printf( "Couldn't lock screen: %s\n", SDL_GetError() ); + return; + } + } + + Uint32 *buffer = (Uint32 *)( screen->pixels ); + memcpy( (void*)buffer, (void*)( inOutBuffer->getBuffer() ), + mWidth * mHeight * 4 ); + + // unlock the screen if necessary + if ( SDL_MUSTLOCK(screen) ) { + SDL_UnlockSurface(screen); + } + + SDL_Flip( screen ); + } + + + +GraphicBuffer *ScreenGraphics::getScreenBuffer() { + SDL_Surface *screen = (SDL_Surface*)mNativeObjectPointer; + + Uint32 *buffer = (Uint32 *)( screen->pixels ); + + return new GraphicBuffer( (unsigned long *)buffer, mWidth, mHeight ); + } + + + +void ScreenGraphics::flipDirtyRectangle( int inTopLeftX, int inTopLeftY, + int inWidth, int inHeight ) { + + SDL_Surface *screen = (SDL_Surface*)mNativeObjectPointer; + + SDL_UpdateRect( screen, inTopLeftX, inTopLeftY, inWidth, inHeight ); + } + + + +void ScreenGraphics::flipScreen() { + SDL_Surface *screen = (SDL_Surface*)mNativeObjectPointer; + + SDL_Flip( screen ); + } diff --git a/minorGems/graphics/linux/graphixCommonDefs.h b/minorGems/graphics/linux/graphixCommonDefs.h new file mode 100644 index 0000000..0f527e1 --- /dev/null +++ b/minorGems/graphics/linux/graphixCommonDefs.h @@ -0,0 +1,8 @@ +// ALifeGUICommonDefs.h + + +// common definitions for ALifeGui + + + +#define LINUX_KEY_CODES diff --git a/minorGems/graphics/loadFile.cpp b/minorGems/graphics/loadFile.cpp new file mode 100644 index 0000000..6104d18 --- /dev/null +++ b/minorGems/graphics/loadFile.cpp @@ -0,0 +1,86 @@ +#include +#include +#include "loadFile.h" + +// loads any file into the dest ptr. + + +void loadFile( const char* fileName, long sizeInBytes, unsigned char* byteDestPtr) { + + + + FILE *f; + + int numBytesRead; + + + f = fopen( fileName, "rb"); // open the file + + // don't load if file failed to open + if( f == NULL ) { + return; + } + + numBytesRead = fread( (void*)byteDestPtr, sizeof(char), sizeInBytes, f); + + fclose(f); + + } + + + + + + +// loads a photoshop RAW image file, 32-bit + +// NOTE: + // This function exists because photoshop swaps the red and blue channels when + // it saves a file as raw. Thus, the file stream doesn't match the way a video + // card deals with 32-bit color. + + // This function loads the file and then swaps the appropriate bytes + +void loadRawFile( const char* fileName, long sizeInBytes, unsigned char* byteDestPtr) { + + //unsigned char tempChannel; + + + long byteCount; + + FILE *f; + + int numBytesRead; + + + f = fopen( fileName, "rb"); // open the file + + // don't load if file failed to open + if( f == NULL ) { + return; + } + + numBytesRead = fread( (void*)byteDestPtr, sizeof(char), sizeInBytes, f); + + + // now that file read, swap the red and blue channels: + + for( byteCount = 0; byteCount< sizeInBytes; byteCount=byteCount+4) { + + unsigned char alpha = byteDestPtr[byteCount+3]; + + byteDestPtr[byteCount+3] = byteDestPtr[byteCount+2]; + byteDestPtr[byteCount+2] = byteDestPtr[byteCount+1]; + byteDestPtr[byteCount+1] = byteDestPtr[byteCount]; + + byteDestPtr[byteCount] = alpha; + +/* tempChannel = byteDestPtr[byteCount]; // currently in red position + byteDestPtr[byteCount] = byteDestPtr[byteCount+3]; // currently set to what's in alpha position + byteDestPtr[byteCount+2] = tempChannel; +*/ + + } + + fclose(f); + } \ No newline at end of file diff --git a/minorGems/graphics/loadfile.h b/minorGems/graphics/loadfile.h new file mode 100644 index 0000000..3a30c07 --- /dev/null +++ b/minorGems/graphics/loadfile.h @@ -0,0 +1,3 @@ +void loadFile( const char* fileName, long sizeInBytes, unsigned char* byteDestPtr); + +void loadRawFile( const char* fileName, long sizeInBytes, unsigned char* byteDestPtr); \ No newline at end of file diff --git a/minorGems/graphics/mac/graphixCommonDefs.h b/minorGems/graphics/mac/graphixCommonDefs.h new file mode 100644 index 0000000..15d437d --- /dev/null +++ b/minorGems/graphics/mac/graphixCommonDefs.h @@ -0,0 +1,8 @@ +// ALifeGUICommonDefs.h + + +// common definitions for ALifeGui + + + +#define MAC_KEY_CODES \ No newline at end of file diff --git a/minorGems/graphics/mac/graphixFramework.cpp b/minorGems/graphics/mac/graphixFramework.cpp new file mode 100644 index 0000000..928ae4f --- /dev/null +++ b/minorGems/graphics/mac/graphixFramework.cpp @@ -0,0 +1,304 @@ +/** 32 bit DrawSprockets pluggable framework +* +* Jason Rohrer, 4-28-99 +* +* Mods: +* Jason Rohrer 11-8-99 Changed to use GraphicBuffer object as screen buffer +* Jason Rohrer 12-15-99 Added support for keyboard querying +* Jason Rohrer 3-21-2000 Added support for mouse querying +* +*/ + + +#include +#include +#include // for keyboard events + +#include +#include +#include "swapBuffers.h" // interface for swapping buffers +#include "getKey.h" // interface for getting key states +#include "getMouse.h" // interface for getting mouse state + +//#include "ALifeGuiRunner.h" +#include "GoGUIRunner.h" +#include "GraphicBuffer.h" + +// Constants +#define BallWidth 20 +#define BallHeight 20 +#define BobSize 8 /* Size of text in each ball */ + +//MT Tutorial +#define rWindow 128 // resource ID number for window + + +// Globals +Rect windRect; +WindowPtr w; +DSpContextReference theContext; +unsigned long *double_buffer = NULL; // the double buffer +unsigned long *screen_buffer = NULL; + +GraphicBuffer *graphicDoubleBuffer; + + +short bufferHigh = 480; +short bufferWide = 640; + + +// prototypes +void Initialize(void); +void graphixPicker(void); // our mother function, picks effect order, etc. +void initVideo(DSpContextReference *theContext); +void MyInitAttributes(DSpContextAttributes*); +void CleanUp(DSpContextReference theContext); + + + +int main() { + + Initialize(); // setup a window + MaxApplZone(); + + initVideo( &theContext ); // set up and switch video + + graphixPicker(); + + CleanUp( theContext); + + } + + +void graphixPicker() { // our "mother function" + // selects which effect to run next + + // graphix plug-in format: + // myGraphix( bufferPtr, bufferHigh, bufferWide, colorBits, numFrames) + // function can cll "swapBuffer32" internally whenever it needs the back buffer + // sent to the screen. + + + + // jcrTorus( double_buffer, 480, 640, 32, 100); + // jcrVoxels( double_buffer, 480, 640, 32, 230); + // jcrTorus( double_buffer, 480, 640, 32, 50); + + + //jcrBloom( double_buffer, 480, 640, 32, 100); + + // GraphicBuffer passed in contains all needed info about screen + // pass in number of frames to run for + //ALifeGuiRunner( *graphicDoubleBuffer, 1000 ); + + GoGUIRunner( *graphicDoubleBuffer, 0 ); + } + + +// Initialize the draw sprockets +// set up the video, fade out display, and switch video modes +void initVideo(DSpContextReference *theContext) { + + CGrafPtr backBuffer; + PixMapHandle bufferMap; + + DSpContextAttributes theDesiredAttributes; + OSStatus theError; + + + + MyInitAttributes(&theDesiredAttributes); + theDesiredAttributes.displayWidth = 640; + theDesiredAttributes.displayHeight = 480; + theDesiredAttributes.colorNeeds = kDSpColorNeeds_Require; + theDesiredAttributes.backBufferDepthMask = kDSpDepthMask_32; + theDesiredAttributes.displayDepthMask = kDSpDepthMask_32; + theDesiredAttributes.backBufferBestDepth = 32; + theDesiredAttributes.displayBestDepth = 32; + theError = DSpFindBestContext(&theDesiredAttributes, theContext); + + // add page flipping by video card to request + theDesiredAttributes.contextOptions |= kDSpContextOption_PageFlip; + //theDesiredAttributes.contextOptions |= kDSpContextOption_DontSyncVBL; + + // Reserver a display + theError = DSpContext_Reserve(*theContext, &theDesiredAttributes); + + // fade out + theError = DSpContext_FadeGammaOut(NULL, NULL); + + theError = DSpContext_SetState(*theContext, kDSpContextState_Active); + ShowCursor(); + HideCursor(); + + + // setup global pointers to screen and back buffer + + theError = DSpContext_GetBackBuffer(*theContext, kDSpBufferKind_Normal, &backBuffer); + bufferMap = (PixMapHandle)GetGWorldPixMap(backBuffer); + double_buffer = (unsigned long*)(**bufferMap).baseAddr; + + // create GraphicBuffer object + graphicDoubleBuffer = new GraphicBuffer( double_buffer, 640, 480 ); + + theError = DSpContext_GetFrontBuffer(*theContext, &backBuffer); + bufferMap = (PixMapHandle)GetGWorldPixMap(backBuffer); + screen_buffer = (unsigned long*)(**bufferMap).baseAddr; + + theError = DSpContext_FadeGammaIn(NULL, NULL); + + } + + +// initialize a set of Display attributes to zero +void MyInitAttributes (DSpContextAttributes *inAttributes) +{ + if (NULL == inAttributes) + DebugStr("\pStimpy! You Idiot!"); + + inAttributes->frequency = 0; + inAttributes->displayWidth = 0; + inAttributes->displayHeight = 0; + inAttributes->reserved1 = 0; + inAttributes->reserved2 = 0; + inAttributes->colorNeeds = 0; + inAttributes->colorTable = NULL; + inAttributes->contextOptions = 0; + inAttributes->backBufferDepthMask = 0; + inAttributes->displayDepthMask = 0; + inAttributes->backBufferBestDepth = 0; + inAttributes->displayBestDepth = 0; + inAttributes->pageCount = 0; + inAttributes->gameMustConfirmSwitch = false; + inAttributes->reserved3[0] = 0; + inAttributes->reserved3[1] = 0; + inAttributes->reserved3[2] = 0; + inAttributes->reserved3[3] = 0; +} + +void CleanUp(DSpContextReference theContext) { + OSStatus theError; + + theError = DSpContext_FadeGammaOut(NULL, NULL); + + /* put the context into the inactive state */ + theError = DSpContext_SetState(theContext, kDSpContextState_Inactive); + + /* fade back in */ + theError = DSpContext_FadeGammaIn(NULL, NULL); + + /* release the context */ + theError = DSpContext_Release(theContext); + + ShowCursor(); + } + + + + + +// swap bufferB to the screen (32 bit version) +void swapBuffers32( GraphicBuffer &bufferB ) { + OSStatus theError; + + // swap buffers + theError = DSpContext_SwapBuffers(theContext, NULL, 0); + + + // get pointer to new back buffer and return it + CGrafPtr backBuffer; + PixMapHandle bufferMap; + theError = DSpContext_GetBackBuffer(theContext, kDSpBufferKind_Normal, &backBuffer); + bufferMap = (PixMapHandle)GetGWorldPixMap(backBuffer); + + // replace the buffer in bufferB with the new double buffer + bufferB.setBuffer( (unsigned long*)(**bufferMap).baseAddr ); + +// memcpy(screen_buffer, bufferPtrB, 4*bufferWide*bufferHigh); + + + + } // end of swapBuffers + + + +// returns true if key represented by given key code is down +char getKeyDown( int vKeyCode ) { + + unsigned char keyArray [16]; + GetKeys( (long *)keyArray ); + + return ((keyArray[vKeyCode>>3] >> (vKeyCode & 7)) & 1); + } + +// returns true if key is up +char getKeyUp( int vKeyCode ) { + + unsigned char keyArray [16]; + GetKeys( (long *)keyArray ); + + return !((keyArray[vKeyCode>>3] >> (vKeyCode & 7)) & 1); + } + + +void getMouse( int *x, int *y ) { + Point mousePoint; + DSpGetMouse( &mousePoint ); + + *x = mousePoint.h; + *y = mousePoint.v; + } + + + +void Initialize(void) +{ + WindowPtr mainPtr; + OSErr error; + SysEnvRec theWorld; + + // + // Test the computer to be sure we can do color. + // If not we would crash, which would be bad. + // If we can¹t run, just beep and exit. + // + + error = SysEnvirons(1, &theWorld); + if (theWorld.hasColorQD == false) { +// SysBeep(50); + ExitToShell(); // If no color QD, we must leave. + } + + // Initialize all the needed managers. + InitGraf(&qd.thePort); +// InitFonts(); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(nil); + InitCursor(); + + // + // To make the Random sequences truly random, we need to make the seed start + // at a different number. An easy way to do this is to put the current time + // and date into the seed. Since it is always incrementing the starting seed + // will always be different. Don¹t for each call of Random, or the sequence + // will no longer be random. Only needed once, here in the init. + // + GetDateTime((unsigned long*) &qd.randSeed); + + // + // Make a new window for drawing in, and it must be a color window. + // The window is full screen size, made smaller to make it more visible. + // + mainPtr = GetNewCWindow(rWindow, nil, (WindowPtr) -1); // MW Tutorial + windRect = mainPtr->portRect; + + SetPort(mainPtr); // set window to current graf port + TextSize(BobSize); // smaller font for drawing. +} + + + + + diff --git a/minorGems/graphics/swapBuffers.h b/minorGems/graphics/swapBuffers.h new file mode 100644 index 0000000..9ce4b78 --- /dev/null +++ b/minorGems/graphics/swapBuffers.h @@ -0,0 +1,24 @@ +#include "GraphicBuffer.h" + +// function that swaps bufferB to the screen (32 bit version) +// returns new back buffer +//unsigned long *swapBuffers32( unsigned long *bufferPtrB, short bufferHigh, short bufferWide); + +// now replaces "buffer" in bufferB with the new double buffer after swap +// no need to pass back the new buffer +void swapBuffers32( GraphicBuffer &bufferB ); + + + + +// swap bufferB to the screen (8 bit version) +// returns new back buffer +unsigned char *swapBuffers8( unsigned char *bufferPtrB, short bufferHigh, short bufferWide ); + + + + + +// NOTE: the bodies of these functions must occur in the body of the main DDraw code file +// i.e., in the file where all the directDraw objects are instantiated. +// these functions assume the ddraw ojects are declared globally \ No newline at end of file diff --git a/minorGems/graphics/test/rgb2yiq.cpp b/minorGems/graphics/test/rgb2yiq.cpp new file mode 100644 index 0000000..0038dae --- /dev/null +++ b/minorGems/graphics/test/rgb2yiq.cpp @@ -0,0 +1,149 @@ +/* + * Modification History + * + * 2001-September-19 Jason Rohrer + * Created. + * + * 2001-September-20 Jason Rohrer + * Finished initial implementation. + * Changed so that images are normalized to [0,1] before + * being output. + */ + +#include "minorGems/graphics/Image.h" +#include "minorGems/graphics/ImageColorConverter.h" + +#include "minorGems/graphics/converters/BMPImageConverter.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileInputStream.h" +#include "minorGems/io/file/FileOutputStream.h" + +#include +#include + + + +void usage( char *inAppName ); +Image *loadBMPImage( char *inFileName ); +void writeBMPImage( char *inFileName, Image *inImage ); + +// normalizes an image into the pixel range [0,1] if some +// pixels are out of range +void normalizeImage( Image *inImage ); + + + +// a test program that converts an rgb BMP to a yiq BMP +int main( char inNumArgs, char **inArgs ) { + + if( inNumArgs != 3 ) { + usage( inArgs[0] ); + } + + Image *rgbImage = loadBMPImage( inArgs[1] ); + + Image *yiqImage = ImageColorConverter::RGBToYIQ( rgbImage ); + + normalizeImage( yiqImage ); + + + writeBMPImage( inArgs[2], yiqImage ); + + delete rgbImage; + delete yiqImage; + + return 0; + } + + +void usage( char *inAppName ) { + printf( "Usage:\n" ); + printf( "\t%s rgb_image_bmp yiq_image_bmp\n", inAppName ); + printf( "Example:\n" ); + printf( "\t%s testRGB.bmp testYIQ.bmp\n", inAppName ); + + exit( 1 ); + } + + + +void normalizeImage( Image *inImage ) { + // now normalize the image so that pixels are in the range [0,1] + + int numPixels = inImage->getWidth() * inImage->getHeight(); + int numChannels = inImage->getNumChannels(); + double minValue = 1000.0; + double maxValue = -1000.0; + int c; + + // search for min and max values + for( c=0; cgetChannel( c ); + for( int p=0; p maxValue ) { + maxValue = channel[p]; + } + } + } + + double diff = maxValue - minValue; + + if( minValue >= 0 && diff + minValue <= 1 ) { + // no need to normalize, as values are already + // in range + return; + } + + double scale = 1.0 / diff; + + // now normalize all pixels + for( c=0; cgetChannel( c ); + for( int p=0; pdeformatImage( imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + + return returnImage; + } + + + +void writeBMPImage( char *inFileName, Image *inImage ) { + File *imageFile = new File( NULL, inFileName, + strlen( inFileName ) ); + FileOutputStream *imageStream + = new FileOutputStream( imageFile ); + + BMPImageConverter *converter = new BMPImageConverter(); + + converter->formatImage( inImage, imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + } + + + diff --git a/minorGems/graphics/test/rgb2yiqCompile b/minorGems/graphics/test/rgb2yiqCompile new file mode 100755 index 0000000..d8a4f85 --- /dev/null +++ b/minorGems/graphics/test/rgb2yiqCompile @@ -0,0 +1 @@ +g++ -o rgb2yiq -I../../.. rgb2yiq.cpp ../../../minorGems/io/file/linux/PathLinux.cpp \ No newline at end of file diff --git a/minorGems/graphics/test/tgaConverter.cpp b/minorGems/graphics/test/tgaConverter.cpp new file mode 100644 index 0000000..3918e6d --- /dev/null +++ b/minorGems/graphics/test/tgaConverter.cpp @@ -0,0 +1,181 @@ +/* + * Modification History + * + * 2001-September-24 Jason Rohrer + * Created. + */ + +#include "minorGems/graphics/Image.h" +#include "minorGems/graphics/ImageColorConverter.h" + +#include "minorGems/graphics/converters/TGAImageConverter.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileInputStream.h" +#include "minorGems/io/file/FileOutputStream.h" + +#include +#include +#include + + + +void usage( char *inAppName ); +Image *loadTGAImage( char *inFileName ); +void writeTGAImage( char *inFileName, Image *inImage ); + +// normalizes an image into the pixel range [0,1] if some +// pixels are out of range +void normalizeImage( Image *inImage ); + + + +// a test program that converts a tga image between +// parameterizable color formats +int main( char inNumArgs, char **inArgs ) { + + if( inNumArgs != 5 ) { + usage( inArgs[0] ); + } + + Image *inImage = loadTGAImage( inArgs[1] ); + + Image *outImage; + + // convert inImage to outImage based on the color space strings + if( !strcmp( inArgs[3], "rgb" ) ) { + if( !strcmp( inArgs[4], "yiq" ) ) { + outImage = ImageColorConverter::RGBToYIQ( inImage ); + } + if( !strcmp( inArgs[4], "ycbcr" ) ) { + outImage = ImageColorConverter::RGBToYCbCr( inImage ); + } + } + if( !strcmp( inArgs[3], "yiq" ) ) { + if( !strcmp( inArgs[4], "rgb" ) ) { + outImage = ImageColorConverter::YIQToRGB( inImage ); + } + if( !strcmp( inArgs[4], "ycbcr" ) ) { + Image *tempImage = ImageColorConverter::YIQToRGB( inImage ); + + outImage = ImageColorConverter::RGBToYCbCr( tempImage ); + + delete tempImage; + } + } + if( !strcmp( inArgs[3], "ycbcr" ) ) { + if( !strcmp( inArgs[4], "rgb" ) ) { + outImage = ImageColorConverter::YCbCrToRGB( inImage ); + } + if( !strcmp( inArgs[4], "yiq" ) ) { + Image *tempImage = ImageColorConverter::YCbCrToRGB( inImage ); + + outImage = ImageColorConverter::RGBToYIQ( tempImage ); + + delete tempImage; + } + } + + writeTGAImage( inArgs[2], outImage ); + + delete inImage; + delete outImage; + + return 0; + } + + +void usage( char *inAppName ) { + printf( "Usage:\n" ); + printf( "\t%s in_image_tga out_image_tga in_format out_format\n", + inAppName ); + printf( "Example:\n" ); + printf( "\t%s testRGB.tga testYIQ.bmp rgb yiq\n", inAppName ); + + printf( "The following color formats are supported:\n" ); + printf( "\trgb, yiq, ycbcr\n" ); + + exit( 1 ); + } + + + +void normalizeImage( Image *inImage ) { + // now normalize the image so that pixels are in the range [0,1] + + int numPixels = inImage->getWidth() * inImage->getHeight(); + int numChannels = inImage->getNumChannels(); + double minValue = 1000.0; + double maxValue = -1000.0; + int c; + + // search for min and max values + for( c=0; cgetChannel( c ); + for( int p=0; p maxValue ) { + maxValue = channel[p]; + } + } + } + + double diff = maxValue - minValue; + + if( minValue >= 0 && diff + minValue <= 1 ) { + // no need to normalize, as values are already + // in range + return; + } + + double scale = 1.0 / diff; + + // now normalize all pixels + for( c=0; cgetChannel( c ); + for( int p=0; pdeformatImage( imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + + return returnImage; + } + + + +void writeTGAImage( char *inFileName, Image *inImage ) { + File *imageFile = new File( NULL, inFileName, + strlen( inFileName ) ); + FileOutputStream *imageStream + = new FileOutputStream( imageFile ); + + TGAImageConverter *converter = new TGAImageConverter(); + + converter->formatImage( inImage, imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + } + + + diff --git a/minorGems/graphics/test/tgaConverterCompile b/minorGems/graphics/test/tgaConverterCompile new file mode 100755 index 0000000..747c428 --- /dev/null +++ b/minorGems/graphics/test/tgaConverterCompile @@ -0,0 +1 @@ +g++ -g -o tgaConverter -I../../.. tgaConverter.cpp ../../../minorGems/io/file/linux/PathLinux.cpp \ No newline at end of file diff --git a/minorGems/graphics/test/yiq2rgb.cpp b/minorGems/graphics/test/yiq2rgb.cpp new file mode 100644 index 0000000..e29e46d --- /dev/null +++ b/minorGems/graphics/test/yiq2rgb.cpp @@ -0,0 +1,147 @@ +/* + * Modification History + * + * 2001-September-20 Jason Rohrer + * Created. + * Changed so that images are normalized to [0,1] before + * being output. + */ + +#include "minorGems/graphics/Image.h" +#include "minorGems/graphics/ImageColorConverter.h" + +#include "minorGems/graphics/converters/BMPImageConverter.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileInputStream.h" +#include "minorGems/io/file/FileOutputStream.h" + +#include +#include + + +void usage( char *inAppName ); +Image *loadBMPImage( char *inFileName ); +void writeBMPImage( char *inFileName, Image *inImage ); +// normalizes an image into the pixel range [0,1] if some +// pixels are out of range +void normalizeImage( Image *inImage ); + + +// a test program that converts an rgb BMP to a yiq BMP +int main( char inNumArgs, char **inArgs ) { + + if( inNumArgs != 3 ) { + usage( inArgs[0] ); + } + + Image *yiqImage = loadBMPImage( inArgs[1] ); + + if( yiqImage == NULL ) { + printf( "Reading image from file %s failed\n", inArgs[1] ); + usage( inArgs[0] ); + } + + Image *rgbImage = ImageColorConverter::YIQToRGB( yiqImage ); + + normalizeImage( rgbImage ); + + writeBMPImage( inArgs[2], rgbImage ); + + delete rgbImage; + delete yiqImage; + + return 0; + } + + +void usage( char *inAppName ) { + printf( "Usage:\n" ); + printf( "\t%s yiq_image_bmp rgb_image_bmp\n", inAppName ); + printf( "Example:\n" ); + printf( "\t%s testYIQ.bmp testRGB.bmp\n", inAppName ); + + exit( 1 ); +} + + + +void normalizeImage( Image *inImage ) { + // now normalize the image so that pixels are in the range [0,1] + + int numPixels = inImage->getWidth() * inImage->getHeight(); + int numChannels = inImage->getNumChannels(); + double minValue = 1000.0; + double maxValue = -1000.0; + int c; + + // search for min and max values + for( c=0; cgetChannel( c ); + for( int p=0; p maxValue ) { + maxValue = channel[p]; + } + } + } + + double diff = maxValue - minValue; + + if( minValue >= 0 && diff + minValue <= 1 ) { + // no need to normalize, as values are already + // in range + return; + } + + double scale = 1.0 / diff; + + // now normalize all pixels + for( c=0; cgetChannel( c ); + for( int p=0; pdeformatImage( imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + + return returnImage; + } + + + +void writeBMPImage( char *inFileName, Image *inImage ) { + File *imageFile = new File( NULL, inFileName, + strlen( inFileName ) ); + FileOutputStream *imageStream + = new FileOutputStream( imageFile ); + + BMPImageConverter *converter = new BMPImageConverter(); + + converter->formatImage( inImage, imageStream ); + + delete imageFile; + delete imageStream; + delete converter; + } + + + diff --git a/minorGems/graphics/test/yiq2rgbCompile b/minorGems/graphics/test/yiq2rgbCompile new file mode 100755 index 0000000..415315e --- /dev/null +++ b/minorGems/graphics/test/yiq2rgbCompile @@ -0,0 +1 @@ +g++ -g -o yiq2rgb -I../../.. yiq2rgb.cpp ../../../minorGems/io/file/linux/PathLinux.cpp \ No newline at end of file diff --git a/minorGems/graphics/win32/graphixCommonDefs.h b/minorGems/graphics/win32/graphixCommonDefs.h new file mode 100644 index 0000000..84f74c2 --- /dev/null +++ b/minorGems/graphics/win32/graphixCommonDefs.h @@ -0,0 +1,8 @@ +// ALifeGUICommonDefs.h + + +// common definitions for ALifeGui + + + +#define WINDOWS_KEY_CODES \ No newline at end of file diff --git a/minorGems/graphics/win32/graphixFramework.cpp b/minorGems/graphics/win32/graphixFramework.cpp new file mode 100644 index 0000000..cdafa54 --- /dev/null +++ b/minorGems/graphics/win32/graphixFramework.cpp @@ -0,0 +1,471 @@ +// 32 bit DDraw pluggable framework +// Jason Rohrer, 4-28-99 + +/** +* Mods: +* Jason Rohrer 11-8-99 Changed to use GraphicBuffer object as screen buffer +* Jason Rohrer 3-21-2000 Added support for mouse querying +*/ + + +// based on the work of: + +/* + * water ripples effect + * -------------------- + * anthony greene :: emit + * april 1999 + */ + +#define WIN32_LEAN_AND_MEAN // make sure certain headers are included correctly + + +/* includes */ + +#include // include the standard windows stuff +//#include // include the 32 bit stuff +//#include // include the multi media stuff + // need winmm.lib also +#include // include direct draw components + +#include + + +#include "swapBuffers.h" // interface for swapping buffers +#include "getKey.h" // interface for handling keyboard input +#include "getMouse.h" // interface for handling mouse input + + +#include "GraphicBuffer.h" + +//#include "ALifeGuiRunner.h" +#include "GoGUIRunner.h" + + + +/* defines */ + +#define WINDOW_CLASS_NAME "WINDOW_CLASS" // this is the name of the window class +#define SCREEN_WIDTH 640 // the width of the viewing surface +#define SCREEN_HEIGHT 480 // the height of the viewing surface +#define SCREEN_BPP 32 // the bits per pixel +#define MAX_COLORS 256 // the maximum number of colors + + +/* types */ + +typedef unsigned char BYTE; +typedef unsigned char UCHAR; +typedef unsigned short WORD; + + +/* macros */ + +// these query the keyboard in real-time +#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) +#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1) + + +/* prototypes */ + +int DD_Init(HWND hwnd); +int DD_Shutdown(void); + +void graphixPicker(void); // our mother function, picks effect order, etc. + + +/* directdraw globals */ + +LPDIRECTDRAW lpdd = NULL; // dd object +LPDIRECTDRAWSURFACE lpddsprimary = NULL; // dd primary surface +LPDIRECTDRAWSURFACE lpddsback = NULL; // dd back surface +LPDIRECTDRAWPALETTE lpddpal = NULL; // a pointer to the created dd palette +PALETTEENTRY color_palette[256]; // holds the shadow palette entries +DDSURFACEDESC ddsd; // a direct draw surface description struct +DDSCAPS ddscaps; // a direct draw surface capabilities struct +HRESULT ddrval; // result back from dd calls +HWND main_window_handle = NULL; // used to store the window handle + +unsigned long *double_buffer = NULL; // the double buffer +unsigned long *screen_buffer = NULL; + +GraphicBuffer *graphicDoubleBuffer; + + +/* globals */ + +int Height[2][640*480]; +int old; +int nu; +int running = 0; +int cycle; + +/* direct x functions */ + +int DD_Init(HWND hwnd) +{ + // this function is responsible for initializing direct draw, + // it creates a primary surface + + int index; // looping index + + // now that the windows portion is complete, start up direct draw + if (DirectDrawCreate(NULL,&lpdd,NULL)!=DD_OK) + { + // shutdown any other dd objects and kill window + DD_Shutdown(); + return(0); + } + + // now set the coop level to exclusive and set for full screen and mode x + if (lpdd->SetCooperativeLevel(hwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | + DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX)!=DD_OK) + { + // shutdown any other dd objects and kill window + DD_Shutdown(); + return(0); + } + + // now set the display mode + if (lpdd->SetDisplayMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP)!=DD_OK) + { + // shutdown any other dd objects and kill window + DD_Shutdown(); + return(0); + } + + // Create the primary surface + memset(&ddsd,0,sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + + if (lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL)!=DD_OK) + { + // shutdown any other dd objects and kill window + DD_Shutdown(); + return(0); + } // end if + + // allocate memory for the double buffer + if ((double_buffer = (unsigned long *)malloc(SCREEN_WIDTH*SCREEN_HEIGHT*4))==NULL) + return(0); + + // create GraphicBuffer object + graphicDoubleBuffer = new GraphicBuffer( double_buffer, 640, 480 ); + + + // create the palette and attach it to the primary surface + // clear all the palette entries to RGB 0,0,0 + memset(color_palette,0,256*sizeof(PALETTEENTRY)); + + // set all of the flags to the correct value + int c=0; + for (index=0; index<256; index++) + { + // create the GRAY/RED/GREEN/BLUE palette, 64 shades of each + if (index < 64) { + color_palette[index].peRed = index; + color_palette[index].peGreen = 0; + color_palette[index].peBlue = 0; + } else if (index < 128) { + color_palette[index].peRed = index; + color_palette[index].peGreen = 0; + color_palette[index].peBlue = 0; + } else if (index < 192) { + color_palette[index].peRed = index; + color_palette[index].peGreen = c; + color_palette[index].peBlue = c; + c++; + } else if (index < 256) { + color_palette[index].peRed = index; + color_palette[index].peGreen = c; + color_palette[index].peBlue = c; + c++; + } + // set the no collapse flag + color_palette[index].peFlags = PC_NOCOLLAPSE; + } + + // now create the palette object, note that it is a member of the dd object itself + if (lpdd->CreatePalette((DDPCAPS_8BIT | DDPCAPS_INITIALIZE),color_palette,&lpddpal,NULL)!=DD_OK) + { + // shutdown any other dd objects and kill window + DD_Shutdown(); + return(0); + } + + // now attach the palette to the primary surface + lpddsprimary->SetPalette(lpddpal); + + // return success if we got this far + return(1); +} + + +int DD_Shutdown(void) +{ + // this function tests for dd components that have been created + // and releases them back to the operating system + + // test if the dd object exists + if (lpdd) + { + // test if there is a primary surface + if(lpddsprimary) + { + // release the memory and set pointer to NULL + lpddsprimary->Release(); + lpddsprimary = NULL; + } + + // now release the dd object itself + lpdd->Release(); + lpdd = NULL; + + // free double buffer + if (double_buffer!=NULL) + free(double_buffer); + + // return success + return(1); + } + else + return(0); +} + + +/* windows callback fucntion */ + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + // this is the main message handler of the system + + HDC hdc; // handle to graphics context + PAINTSTRUCT ps; // used to hold the paint info + + // what is the message.. + switch(msg) + { + case WM_CREATE: { + // do windows inits here + return(0); + } break; + + case WM_PAINT: { + // this message occurs when your window needs repainting + hdc = BeginPaint(hwnd,&ps); + EndPaint((struct HWND__ *)hdc,&ps); + return(0); + } break; + + case WM_DESTROY: { + // this message is sent when your window is destroyed + PostQuitMessage(0); + return(0); + } break; + + case WM_MOUSEMOVE: { + // extract x,y + /* + int mouse_x = (int)LOWORD(lparam); + int mouse_y = (int)HIWORD(lparam); + Height[old][mouse_y*640+mouse_x] = 300; + */ + return(0); + } break; + + default:break; + } + + // let windows process any messages that we didn't take care of + return (DefWindowProc(hwnd, msg, wparam, lparam)); +} + + +int WINAPI WinMain( HINSTANCE hinstance, + HINSTANCE hprevinstance, + LPSTR lpcmdline, + int ncmdshow) +{ + WNDCLASSEX winclass; // this holds the windows class info + HWND hwnd; // this holds the handle of our new window + MSG msg; // this holds a generic message + + // first fill in the window class stucture + + winclass.cbSize = sizeof(WNDCLASSEX); + winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; + winclass.lpfnWndProc = WindowProc; + winclass.cbClsExtra = 0; + winclass.cbWndExtra = 0; + winclass.hInstance = hinstance; + winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); + winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + winclass.hCursor = LoadCursor(NULL, IDC_ARROW); + winclass.hbrBackground = (struct HBRUSH__ *)GetStockObject(BLACK_BRUSH); + winclass.lpszMenuName = NULL; + winclass.lpszClassName = WINDOW_CLASS_NAME; + + // register the window class + if (!RegisterClassEx(&winclass)) + return(0); + + // create the window + if (!(hwnd = CreateWindowEx(WS_EX_TOPMOST, + WINDOW_CLASS_NAME, // class + "water", // title + WS_VISIBLE | WS_POPUP, + 0,0, // x,y + GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), + NULL, // parent + NULL, // menu + hinstance, // instance + NULL))) // creation parms + return(0); + + // hide the mouse cursor + ShowCursor(0); + + // save the window handle + main_window_handle = hwnd; + + // initialize direct draw + if (!DD_Init(hwnd)) { + DestroyWindow(hwnd); + return(0); + } + + + // peek at the message once just to make them happy + PeekMessage(&msg,NULL,0,0,PM_REMOVE); + + // do it once, then leave + + graphixPicker(); // picks which effects to run. + + + // afterwards, end!!! + + + + // shut down direct draw + DD_Shutdown(); + + // return to Windows + return(msg.wParam); + +} + + + + + + +void graphixPicker() { // our "mother function" + // selects which effect to run next + + // graphix plug-in format: + // myGraphix( bufferPtr, bufferHigh, bufferWide, colorBits, numFrames) + // function can cll "swapBuffer32" internally whenever it needs the back buffer + // sent to the screen. + + + + // jcrTorus( double_buffer, 480, 640, 32, 100); + // jcrVoxels( double_buffer, 480, 640, 32, 230); + // jcrTorus( double_buffer, 480, 640, 32, 50); + // jcrBloom( double_buffer, 480, 640, 32, 100); + + + //ALifeGuiRunner( *graphicDoubleBuffer, 200 ); + GoGUIRunner( *graphicDoubleBuffer, 0 ); + } + + + + +char getKeyDown( int vKeyCode ) { + return ((GetAsyncKeyState(vKeyCode) & 0x8000) ? true : false); + } +char getKeyUp( int vKeyCode ) { + return ((GetAsyncKeyState(vKeyCode) & 0x8000) ? false : true); + } + +void getMouse( int *x, int *y ) { + POINT p; + + GetCursorPos( &p ); + + *x = p.x; + *y = p.y; + } + + + +/// CODE FOR SWAPPING BUFFERES BELOW THIS POINT....... + + + + + +// swap bufferB to the screen (32 bit version) +void swapBuffers32( GraphicBuffer &bufferB ) { + + unsigned long *primary_buffer = NULL, // used to draw + *dest_ptr = NULL, // used in line by line copy + *src_ptr = NULL; // " " + + short bufferHigh = 480; + short bufferWide = 640; + + + // copy the double buffer into the primary buffer + memset(&ddsd,0,sizeof(ddsd)); + ddsd.dwSize = sizeof(ddsd); + + // lock the primary surface + lpddsprimary->Lock(NULL,&ddsd, + DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL); + + // get video pointer to primary surfce + primary_buffer = (unsigned long *)ddsd.lpSurface; + + // test if memory is linear + if (ddsd.lPitch == 640*4) + { + // copy memory from double buffer to primary buffer + memcpy(primary_buffer, double_buffer, 640*480*4); + } + else + { // non-linear + // make copy of source and destination addresses + dest_ptr = primary_buffer; + src_ptr = double_buffer; + + // memory is non-linear, copy line by line + for (int y=0; yUnlock(primary_buffer); + + } // end of swapBuffers + + + + diff --git a/minorGems/io/InputStream.h b/minorGems/io/InputStream.h new file mode 100644 index 0000000..dd28152 --- /dev/null +++ b/minorGems/io/InputStream.h @@ -0,0 +1,179 @@ +/* + * Modification History + * + * 2001-January-9 Jason Rohrer + * Created. + * + * 2001-January-9 Jason Rohrer + * Changed the "number of bytes read" parameter and return value + * to longs. + * + * 2001-February-3 Jason Rohrer + * Added a readDouble function to fix platform-specific double problems. + * Also added a readLong function for completeness. Implemented + * these functions, making use of the new TypeIO interface. + * + * 2001-February-12 Jason Rohrer + * Changed to subclass Stream. + * + * 2002-March-9 Jason Rohrer + * Added a readByte() function. + * + * 2002-March-31 Jason Rohrer + * Made destructor virtual so it works with subclasses. + * + * 2004-May-9 Jason Rohrer + * Added support for shorts. + */ + +#include "minorGems/common.h" + + +#ifndef INPUT_STREAM_CLASS_INCLUDED +#define INPUT_STREAM_CLASS_INCLUDED + +#include "Stream.h" + +#include "TypeIO.h" + + + +/** + * Interface for a byte input stream. + * + * @author Jason Rohrer + */ +class InputStream : public Stream { + + public: + + InputStream(); + virtual ~InputStream(); + + /** + * Reads bytes from this stream. + * + * @param inBuffer the buffer where read bytes will be put. + * Must be pre-allocated memory space. + * @param inNumBytes the number of bytes to read from the stream. + * + * @return the number of bytes read successfully, + * or -1 for a stream error. + */ + virtual long read( unsigned char *inBuffer, long inNumBytes ) = 0; + + + + /** + * Reads a byte from this stream. + * + * @param outByte pointer to where the byte should be stored. + * + * @return the number of bytes read successfully, or -1 for a + * stream error. + */ + long readByte( unsigned char *outByte ); + + + + /** + * Reads a double from the stream in a platform-independent way. + * + * @param outDouble pointer to where the double should be stored. + * + * @return the number of bytes read successfully, or -1 for a + * stream error. + */ + long readDouble( double *outDouble ); + + + /** + * Reads a long from the stream in a platform-independent way. + * + * @param outLong pointer to where the long should be stored. + * + * @return the number of bytes read successfully, or -1 for a + * stream error. + */ + long readLong( long *outLong ); + + + /** + * Reads a short from the stream in a platform-independent way. + * + * @param outShort pointer to where the short should be stored. + * + * @return the number of bytes read successfully, or -1 for a + * stream error. + */ + long readShort( short *outShort ); + + + private: + unsigned char *mDoubleBuffer; + unsigned char *mLongBuffer; + unsigned char *mShortBuffer; + unsigned char *mByteBuffer; + + }; + + + +inline InputStream::InputStream() + : mDoubleBuffer( new unsigned char[8] ), + mLongBuffer( new unsigned char[4] ), + mShortBuffer( new unsigned char[2] ), + mByteBuffer( new unsigned char[1] ) { + + } + + + +inline InputStream::~InputStream() { + + delete [] mDoubleBuffer; + delete [] mShortBuffer; + delete [] mLongBuffer; + delete [] mByteBuffer; + } + + + +inline long InputStream::readByte( unsigned char *outByte ) { + int numBytes = read( mByteBuffer, 1 ); + + *outByte = mByteBuffer[0]; + + return numBytes; + } + + + +inline long InputStream::readDouble( double *outDouble ) { + int numBytes = read( mDoubleBuffer, 8 ); + *outDouble = TypeIO::bytesToDouble( mDoubleBuffer ); + + return numBytes; + } + + + +inline long InputStream::readLong( long *outLong ) { + int numBytes = read( mLongBuffer, 4 ); + *outLong = TypeIO::bytesToLong( mLongBuffer ); + + return numBytes; + } + + + +inline long InputStream::readShort( short *outShort ) { + int numBytes = read( mShortBuffer, 4 ); + *outShort = TypeIO::bytesToShort( mShortBuffer ); + + return numBytes; + } + + + +#endif diff --git a/minorGems/io/OutputStream.h b/minorGems/io/OutputStream.h new file mode 100644 index 0000000..b9b9d10 --- /dev/null +++ b/minorGems/io/OutputStream.h @@ -0,0 +1,185 @@ +/* + * Modification History + * + * 2001-January-9 Jason Rohrer + * Created. + * + * 2001-January-9 Jason Rohrer + * Changed the "number of bytes written" parameter and return value + * to longs. + * + * 2001-February-3 Jason Rohrer + * Added a writeDouble function to fix platform-specific double problems. + * Also added a writeLong function for completeness. Implemented + * these functions, making use of the new TypeIO interface. + * + * 2001-February-12 Jason Rohrer + * Changed to subclass Stream. + * + * 2002-February-25 Jason Rohrer + * Added a function for writing a string. + * + * 2002-March-31 Jason Rohrer + * Made destructor virtual so it works with subclasses. + * + * 2004-May-9 Jason Rohrer + * Added support for shorts. + * + * 2010-May-18 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef OUTPUT_STREAM_CLASS_INCLUDED +#define OUTPUT_STREAM_CLASS_INCLUDED + +#include "Stream.h" + +#include "TypeIO.h" + +#include + + + +/** + * Interface for a byte output stream. + * + * @author Jason Rohrer + */ +class OutputStream : public Stream { + + public: + + OutputStream(); + virtual ~OutputStream(); + + /** + * Writes bytes to this stream. + * + * @param inBuffer the buffer of bytes to send. + * @param inNumBytes the number of bytes to send. + * + * @return the number of bytes written successfully, + * or -1 for a stream error. + */ + virtual long write( unsigned char *inBuffer, long inNumBytes ) = 0; + + + + /** + * Writes a string to this stream. + * + * @param inString a \0-terminated string. + * Must be destroyed by caller. + * @return the number of bytes written successfully, or -1 for a + * stream error. + */ + long writeString( const char *inString ); + + + + /** + * Writes a double to the stream in a platform-independent way. + * + * @param inDouble the double to write + * + * @return the number of bytes written successfully, or -1 for a + * stream error. + */ + long writeDouble( double inDouble ); + + + /** + * Writes a long to the stream in a platform-independent way. + * + * @param inLong the long to write + * + * @return the number of bytes written successfully, or -1 for a + * stream error. + */ + long writeLong( long inLong ); + + + + /** + * Writes a short to the stream in a platform-independent way. + * + * @param inShort the long to write + * + * @return the number of bytes written successfully, or -1 for a + * stream error. + */ + long writeShort( short inShort ); + + + + private: + unsigned char *mDoubleBuffer; + unsigned char *mLongBuffer; + unsigned char *mShortBuffer; + + }; + + + +inline OutputStream::OutputStream() + : mDoubleBuffer( new unsigned char[8] ), + mLongBuffer( new unsigned char[4] ), + mShortBuffer( new unsigned char[2] ) { + + } + + + +inline OutputStream::~OutputStream() { + + delete [] mDoubleBuffer; + delete [] mLongBuffer; + delete [] mShortBuffer; + } + + + +inline long OutputStream::writeString( const char *inString ) { + + int numBytes = write( (unsigned char *)inString, strlen( inString ) ); + + return numBytes; + } + + + +inline long OutputStream::writeDouble( double inDouble ) { + TypeIO::doubleToBytes( inDouble, mDoubleBuffer ); + + int numBytes = write( mDoubleBuffer, 8 ); + + return numBytes; + } + + + +inline long OutputStream::writeLong( long inLong ) { + TypeIO::longToBytes( inLong, mLongBuffer ); + + int numBytes = write( mLongBuffer, 4 ); + + return numBytes; + } + + + +inline long OutputStream::writeShort( short inShort ) { + TypeIO::shortToBytes( inShort, mShortBuffer ); + + int numBytes = write( mShortBuffer, 2 ); + + return numBytes; + } + + + +#endif diff --git a/minorGems/io/PipedStream.h b/minorGems/io/PipedStream.h new file mode 100644 index 0000000..06e0e8e --- /dev/null +++ b/minorGems/io/PipedStream.h @@ -0,0 +1,176 @@ +/* + * Modification History + * + * 2001-February-19 Jason Rohrer + * Created. + * + * 2001-February-20 Jason Rohrer + * Added a missing return value. + */ + +#include "minorGems/common.h" + + + +#ifndef PIPED_STREAM_CLASS_INCLUDED +#define PIPED_STREAM_CLASS_INCLUDED + +#include "minorGems/io/InputStream.h" +#include "minorGems/io/OutputStream.h" + +#include "minorGems/util/SimpleVector.h" + +#include + +/** + * An input/output stream that can server as a pipe between + * two components that read from and write to streams. + * + * Buffered internally to prevent blocking, so is compatible + * with non-threaded components. Note, however, that + * ever buffer written to the stream is copied internally, + * so max memory usage is doubled. + * + * IS NOT THREAD-SAFE! + * + * @author Jason Rohrer + */ +class PipedStream : public InputStream, public OutputStream { + + public: + + /** + * Constructs a PipedStream. + */ + PipedStream(); + + /** + * Destroys any pending unread buffers. + */ + ~PipedStream(); + + + + + // implements the InputStream interface + long read( unsigned char *inBuffer, long inNumBytes ); + + + // implements the OutputStream interface + long write( unsigned char *inBuffer, long inNumBytes ); + + + protected: + SimpleVector *mBuffers; + SimpleVector *mBufferSizes; + + long mCurrentBufferIndex; + + }; + + + +inline PipedStream::PipedStream() + : mBuffers( new SimpleVector() ), + mBufferSizes( new SimpleVector() ), + mCurrentBufferIndex( 0 ) { + + } + + + +inline PipedStream::~PipedStream() { + int numRemaining = mBuffers->size(); + for( int i=0; igetElement( i ) ); + + delete [] buffer; + } + delete mBuffers; + delete mBufferSizes; + } + + + +inline long PipedStream::read( unsigned char *inBuffer, long inNumBytes ) { + if( mBuffers->size() == 0 ) { + // none read, since no buffers available + InputStream:: + setNewLastErrorConst( "No data available on piped stream read." ); + return 0; + } + else { + + unsigned char *currentBuffer = *( mBuffers->getElement( 0 ) ); + long currentBufferSize = *( mBufferSizes->getElement( 0 ) ); + + long outputIndex = 0; + + while( outputIndex < inNumBytes ) { + long bytesRemaining = inNumBytes - outputIndex; + + long bytesRemainingInCurrent = currentBufferSize - mCurrentBufferIndex; + + // if current buffer isn't big enough to fill output + if( bytesRemaining >= bytesRemainingInCurrent ) { + // copy all of current buffer into inBuffer + memcpy( &( inBuffer[outputIndex] ), + &( currentBuffer[mCurrentBufferIndex] ), + bytesRemainingInCurrent ); + + outputIndex += bytesRemainingInCurrent; + + // delete the current buffer + mBuffers->deleteElement( 0 ); + mBufferSizes->deleteElement( 0 ); + delete [] currentBuffer; + + mCurrentBufferIndex = 0; + + if( outputIndex != inNumBytes && mBuffers->size() == 0 ) { + // partial read, since no more buffers available + InputStream::setNewLastErrorConst( + "Partial data available on piped stream read." ); + return outputIndex; + } + if( mBuffers->size() != 0 ) { + // get the new current buffer + currentBuffer = *( mBuffers->getElement( 0 ) ); + currentBufferSize = *( mBufferSizes->getElement( 0 ) ); + } + } + else { + // current buffer is bigger + memcpy( &( inBuffer[outputIndex] ), + &( currentBuffer[mCurrentBufferIndex] ), + bytesRemaining ); + + mCurrentBufferIndex += bytesRemaining; + outputIndex += bytesRemaining; + } + + } // end while + + // if we made it out of the while loop, we read all bytes + return inNumBytes; + + } // end else + } + + + +inline long PipedStream::write( unsigned char *inBuffer, long inNumBytes ) { + // add a copy of the buffer to the vector of buffers + unsigned char *copy = new unsigned char[ inNumBytes ]; + + memcpy( copy, inBuffer, inNumBytes ); + + mBuffers->push_back( copy ); + mBufferSizes->push_back( inNumBytes ); + + return inNumBytes; + } + + + +#endif diff --git a/minorGems/io/Serializable.h b/minorGems/io/Serializable.h new file mode 100644 index 0000000..a01dc22 --- /dev/null +++ b/minorGems/io/Serializable.h @@ -0,0 +1,81 @@ +/* + * Modification History + * + * 2001-January-10 Jason Rohrer + * Created. + * Added untility functions for converting integers to and from byte arrays. + * + * 2001-January-15 Jason Rohrer + * Made utility functions static. + * Fixed a major bug in the longToBytes function. + * + * 2001-February-3 Jason Rohrer + * Removed the long io functions, which are now contained in the + * input- and outputStream classes. + * + * 2005-November-21 Jason Rohrer + * Fixed a warning by adding a virtual destructor. + */ + +#include "minorGems/common.h" + + +#ifndef SERIALIZABLE_CLASS_INCLUDED +#define SERIALIZABLE_CLASS_INCLUDED + +#include "InputStream.h" +#include "OutputStream.h" + +/** + * Interface for an object that can be serialized to and deserialized + * from a stream. + * + * Note that the deserialize function is meant to be called from an already + * constructed object (to set object parameters using data read from the + * stream), and is not a method of obtaining an object. + * + * All multi-byte data members should be encoded in using a big endian format. + * + * @author Jason Rohrer + */ +class Serializable { + + public: + + /** + * Writes this object out to a stream. + * + * @param inOutputStream the stream to write to. + * + * @return the number of bytes written successfully, + * or -1 for a stream error. + */ + virtual int serialize( OutputStream *inOutputStream ) = 0; + + + /** + * Reads this object in from a stream. + * + * @param inInputStream the stream to read from. + * + * @return the number of bytes read successfully, + * or -1 for a stream error. + */ + virtual int deserialize( InputStream *inInputStream ) = 0; + + + + virtual ~Serializable(); + + }; + + + +inline Serializable::~Serializable() { + // does nothing + // exists to ensure that subclass destructors are called + } + + + +#endif diff --git a/minorGems/io/Stream.h b/minorGems/io/Stream.h new file mode 100644 index 0000000..e04f7fa --- /dev/null +++ b/minorGems/io/Stream.h @@ -0,0 +1,150 @@ +/* + * Modification History + * + * 2001-February-12 Jason Rohrer + * Created. + * + * 2002-March-29 Jason Rohrer + * Added Fortify inclusion. + * + * 2002-March-31 Jason Rohrer + * Made destructor virtual so it works with subclasses. + * Fixed several bugs in deletion of mLastError. + */ + +#include "minorGems/common.h" + + +#ifndef STREAM_CLASS_INCLUDED +#define STREAM_CLASS_INCLUDED + + +#include + + + +#ifdef FORTIFY +#include "minorGems/util/development/fortify/fortify.h" +#endif + + + +/** + * Base class for all streams. + * + * @author Jason Rohrer + */ +class Stream { + + public: + + /** + * Constructs a stream. + */ + Stream(); + + virtual ~Stream(); + + /** + * Gets the last error associated with this stream. + * Calling this function clears the error until + * another error occurs. + * + * @return the last stream error in human-readable form. + * Must be destroyed by caller. Returns NULL if + * there is no error. Note that this string is '\0' terminated. + */ + char *getLastError(); + + + protected: + + /** + * Called by subclasses to specify a new last error. Useful + * when error doesn't contain information specifiable by + * a constant string. + * + * @param inString human-readable string representing the error. + * Note that this string must be '\0' terminated. + * Will be destroyed by this class. + */ + void setNewLastError( char *inString ); + + + /** + * Called by subclasses to specify a new last error. Useful + * when the error can be described by a constant string + * + * @param inString human-readable constant string representing + * the error. + * Note that this string must be '\0' terminated. + */ + void setNewLastErrorConst( const char *inString ); + + + private: + // set to NULL when there is no error + char *mLastError; + }; + + + +inline Stream::Stream() + : mLastError( NULL ) { + + } + + + +inline Stream::~Stream() { + if( mLastError != NULL ) { + delete [] mLastError; + } + } + + + +inline char *Stream::getLastError() { + + char *returnString = mLastError; + mLastError = NULL; + + return returnString; + } + + + +inline void Stream::setNewLastError( char *inString ) { + if( mLastError != NULL ) { + delete [] mLastError; + } + + mLastError = inString; + } + + + +inline void Stream::setNewLastErrorConst( const char *inString ) { + + int length = 0; + char lastChar = 'a'; + while( lastChar != '\0' ) { + lastChar = inString[length]; + length++; + } + + // add one more to length to accommodate '\0' terminination + length++; + + if( mLastError != NULL ) { + delete [] mLastError; + } + + mLastError = new char[ length ]; + + memcpy( mLastError, inString, length ); + } + + + +#endif diff --git a/minorGems/io/TypeIO.h b/minorGems/io/TypeIO.h new file mode 100644 index 0000000..17a3b79 --- /dev/null +++ b/minorGems/io/TypeIO.h @@ -0,0 +1,141 @@ +/* + * Modification History + * + * 2001-February-3 Jason Rohrer + * Created. + * Fixed parameter names to match convention. + * + * 2003-January-11 Jason Rohrer + * Added missing casts. + * + * 2004-May-9 Jason Rohrer + * Added support for shorts. + */ + +#include "minorGems/common.h" + + +#ifndef TYPE_IO_INCLUDED +#define TYPE_IO_INCLUDED + +/** + * Interfaces for platform-independent type input and output. + * + * The specification for the input/output format for types is as follows: + * + * Types should be output in the order and format that a big-endian Linux + * outputs them by default. + * + * Note that minorGems/numtest.cpp can be used to test how doubles are + * stored on a specific platform. + * + * @author Jason Rohrer + */ +class TypeIO { + + public: + + /** + * Converts an 32-bit integer to a byte array in a + * platform-independent fashion. + * + * @param inInt the integer to convert to a byte array. + * @param outBytes preallocated array where bytes will be returned. + */ + static void longToBytes( long inInt, unsigned char *outBytes ); + + + /** + * Converts a 4-byte array to a 32-bit integer + * platform-independent fashion. + * + * @param inBytes array of bytes to convert. + * + * @return the integer represented by the bytes. + */ + static long bytesToLong( unsigned char *inBytes ); + + + /** + * Converts an 16-bit integer to a byte array in a + * platform-independent fashion. + * + * @param inInt the integer to convert to a byte array. + * @param outBytes preallocated array where bytes will be returned. + */ + static void shortToBytes( short inInt, unsigned char *outBytes ); + + + /** + * Converts a 2-byte array to a 16-bit integer + * platform-independent fashion. + * + * @param inBytes array of bytes to convert. + * + * @return the integer represented by the bytes. + */ + static short bytesToShort( unsigned char *inBytes ); + + + /** + * Converts an 64-bit float to a byte array in a + * platform-independent fashion. + * + * @param inDouble the double to convert to a byte array. + * @param outBytes preallocated array where bytes will be returned. + */ + static void doubleToBytes( + double inDouble, unsigned char *outBytes ); + + + /** + * Converts a 8-byte array to a 64-bit float + * platform-independent fashion. + * + * @param inBytes array of bytes to convert. + * + * @return the double represented by the bytes. + */ + static double bytesToDouble( unsigned char *inBytes ); + + }; + + + +// for now, these long IO functions can be implemented in the same way +// on every platform. + +inline void TypeIO::longToBytes( long inInt, + unsigned char *outBytes ) { + // use a big-endian conversion + outBytes[0] = (unsigned char)( inInt >> 24 & 0xFF ); + outBytes[1] = (unsigned char)( inInt >> 16 & 0xFF ); + outBytes[2] = (unsigned char)( inInt >> 8 & 0xFF ); + outBytes[3] = (unsigned char)( inInt & 0xFF ); + } + + + +inline long TypeIO::bytesToLong( unsigned char *inBytes ) { + return (long)( inBytes[0] << 24 | + inBytes[1] << 16 | inBytes[2] << 8 | inBytes[3] ); + } + + + +inline void TypeIO::shortToBytes( short inInt, + unsigned char *outBytes ) { + // use a big-endian conversion + outBytes[0] = (unsigned char)( inInt >> 8 & 0xFF ); + outBytes[1] = (unsigned char)( inInt & 0xFF ); + } + + + +inline short TypeIO::bytesToShort( unsigned char *inBytes ) { + return (short)( inBytes[0] << 8 | inBytes[1] ); + } + + + +#endif diff --git a/minorGems/io/file/Directory.h b/minorGems/io/file/Directory.h new file mode 100644 index 0000000..462b73b --- /dev/null +++ b/minorGems/io/file/Directory.h @@ -0,0 +1,73 @@ +/* + * Modification History + * + * 2003-January-23 Jason Rohrer + * Created. + * + * + * 2003-November-10 Jason Rohrer + * Added makeDirectory function. + */ + + + +#include "minorGems/common.h" +#include "minorGems/io/file/File.h" + + + +#ifndef DIRECTORY_INCLUDED +#define DIRECTORY_INCLUDED + + + +/** + * Class of static directory functions. + * + * This class exists because most directory operations are + * platform-dependent, and a large body of existing code + * depends on a platform-independent File.h. + * + * @author Jason Rohrer. + */ +class Directory { + + public: + + + + /** + * Removes a directory. + * + * The directory must be empty for this call to succeed. + * + * @param inFile the file representing the directory. + * Must be destroyed by caller. + * + * @return true if the directory is removed successfully, or + * false otherwise (for example, if the directory is not empy). + */ + static char removeDirectory( File *inFile ); + + + + /** + * Makes a directory. + * + * @param inFile the file representing the directory. + * Must be destroyed by caller. + * + * @return true if the directory is removed successfully, or + * false otherwise (for example, if the directory is not empy). + */ + static char makeDirectory( File *inFile ); + + + + }; + + + +#endif + + diff --git a/minorGems/io/file/File.h b/minorGems/io/file/File.h new file mode 100644 index 0000000..4d7ae65 --- /dev/null +++ b/minorGems/io/file/File.h @@ -0,0 +1,994 @@ +/* + * Modification History + * + * 2001-February-11 Jason Rohrer + * Created. + * + * 2001-February-25 Jason Rohrer + * Fixed file name bugs in length and existence functions. + * + * 2001-May-11 Jason Rohrer + * Added a missing include. + * + * 2001-November-3 Jason Rohrer + * Added a function for checking if a file is a directory. + * Added a function for getting the child files of a directory. + * Added a function for getting a pathless file name. + * + * 2001-November-13 Jason Rohrer + * Made name length parameter optional in constructor. + * Made return length parameter optional in name getting functions. + * + * 2001-November-17 Jason Rohrer + * Added a functions for removing a file and for copying a file. + * + * 2002-March-11 Jason Rohrer + * Added destruction comment to getFullFileName(). + * + * 2002-March-13 Jason Rohrer + * Changed mName to be \0-terminated to fix interaction bugs with Path. + * Fixed a missing delete. + * Added a function for creating a directory. + * + * 2002-March-31 Jason Rohrer + * Fixed some bad syntax. + * + * 2002-April-6 Jason Rohrer + * Replaced use of strdup. + * + * 2002-April-8 Jason Rohrer + * Fixed fopen bug. + * + * 2002-April-11 Jason Rohrer + * Fixed a memory leak. + * Fixed a casting error. + * + * 2002-June-28 Jason Rohrer + * Added a function for copying a file class. + * + * 2002-August-3 Jason Rohrer + * Added a function for getting the parent file. + * + * 2002-August-5 Jason Rohrer + * Used an unused error variable. + * + * 2002-September-11 Jason Rohrer + * Added return value to remove. + * + * 2003-January-27 Jason Rohrer + * Added a function for reading file contents. + * + * 2003-February-3 Jason Rohrer + * Added a function for writing a string to a file. + * + * 2003-March-13 Jason Rohrer + * Added a function for getting a child file from a directory. + * + * 2003-June-2 Jason Rohrer + * Fixed parent directory behavior when current file is root directory. + * Fixed a bug in getting child files of root directory. + * + * 2003-November-6 Jason Rohrer + * Added function for getting last modification time. + * + * 2003-November-10 Jason Rohrer + * Changed to use platform-dependent makeDirectory function. + * + * 2004-January-4 Jason Rohrer + * Added recursive child file functions. + * + * 2005-August-29 Jason Rohrer + * Fixed an uninitialized variable warning. + * + * 2010-March-6 Jason Rohrer + * Added versions of writeToFile readFileContents for binary data. + * + * 2010-April-23 Jason Rohrer + * Fixed a string length bug when line ends are Windows. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + + +#include "minorGems/common.h" + + + +#ifndef FILE_CLASS_INCLUDED +#define FILE_CLASS_INCLUDED + +#include +#include +#include + +#include + +#include "Path.h" + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/stringUtils.h" + + + +/** + * File interface. Provides access to information about a + * file. + * + * @author Jason Rohrer + */ +class File { + + public: + + /** + * Constructs a file. + * + * @param inPath the path for this file. + * Is destroyed when this class is destroyed. + * Pass in NULL to specify + * no path (the current working directory). + * @param inName the name of the file to open. + * Must be destroyed by caller if not const. + * Copied internally. + * @param inNameLength length of the name in chars, + * or -1 to use the c-string length of inName + * (assuming that inName is \0-terminated). + * Defaults to -1. + */ + File( Path *inPath, const char *inName, int inNameLength = -1 ); + + + ~File(); + + + + /** + * Gets whether this file is a directory. + * + * @return true iff this file is a directory. + */ + char isDirectory(); + + + + /** + * Makes a directory in the location of this file. + * + * Can only succeed if exists() is false. + * + * @return true iff directory creation succeeded. + */ + char makeDirectory(); + + + + /** + * Gets the files contained in this file if it is a directory. + * + * @param outNumFiles pointer to where the number of + * files will be returned. + * + * @return an array of files, or NULL if this + * file is not a directory, is an empty directory, or doesn't exist. + * Must be destroyed by caller if non-NULL. + */ + File **getChildFiles( int *outNumFiles ); + + + + /** + * Gets the files contained in this file if it is a directory and + * recursively in subdirectories of this file. + * + * @param inDepthLimit the maximum subdirectory depth to recurse into. + * If inDepthLimit is 0, then only child files in this directory + * will be returned. + * @param outNumFiles pointer to where the number of + * files will be returned. + * + * @return an array of files, or NULL if this + * file is not a directory, is an empty directory (or a directory + * containing empty subdirectories), or doesn't exist. + * Must be destroyed by caller if non-NULL. + */ + File **getChildFilesRecursive( int inDepthLimit, int *outNumFiles ); + + + + /** + * Gets a child of this directory. + * + * @param inChildFileName the name of the child file. + * Must be destroyed by caller if non-const. + * + * @return the child file (even if it does not exist), or NULL if + * this file is not a directory. + * Must be destroyed by caller if non-NULL. + */ + File *getChildFile( const char *inChildFileName ); + + + + /** + * Gets the parent directory of this file. + * + * @return the parent directory of this file. + * Must be destroyed by caller. + */ + File *getParentDirectory(); + + + + /** + * Gets the length of this file. + * + * @return the length of this file in bytes. Returns + * 0 if the file does not exist. + */ + long getLength(); + + + /** + * Gets whether a file exists. + * + * @return true if the file exists. + */ + char exists(); + + + + /** + * Gets the last modification time of this file. + * + * @return the modification time in seconds based on the + * system clock. Returns 0 if the file does not exist. + */ + unsigned long getModificationTime(); + + + + /** + * Removes this file from the disk, if it exists. + * + * @return true iff the remove succeeded, false if the removal + * fails or the file does not exist. + */ + char remove(); + + + + /** + * Copies this file object (does not copy the file described by + * this object). + * + * @return a deep copy of this file object. + */ + File *copy(); + + + + /** + * Copies the contents of this file into another file. + * + * @param inDestination the file to copy this file into. + * If it exists, it will be overwritten. + * If it does not exist, it will be created. + * Must be destroyed by caller. + * @param inBlockSize the block size to use when copying. + * Defaults to blocks of 5000 bytes. + */ + void copy( File *inDestination, long inBlockSize = 5000 ); + + + + /** + * Gets the full-path file name sufficient + * to access this file from the current working + * directory. + * + * @param outLength pointer to where the name length, in + * characters, will be returned. Set to NULL to ignore + * the output length. Defaults to NULL. + * + * @return the full path file name for this file, + * in platform-specific form. Must be destroyed by caller. + * The returned string is '\0' terminated, but this + * extra character is not included in the length. + * Must be destroyed by caller. + */ + char *getFullFileName( int *outLength = NULL ); + + + + /** + * Gets the pathless name of this file. + * + * @param outLength pointer to where the name length, in + * characters, will be returned. Set to NULL to ignore + * the output length. Defaults to NULL. + * + * @return the name of this file. Must be destroyed by caller. + */ + char *getFileName( int *outLength = NULL ); + + + + /** + * Reads the contents of this file. + * + * @return a \0-terminated string containing the file contents, + * or NULL if reading the file into memory failed. + * Must be destroyed by caller. + */ + char *readFileContents(); + + + + /** + * Reads the contents of this file. + * + * @param outLength pointer to where the return array length should + * be returned. + * @param inTextMode true to open the file as text, false as binary. + * Defaults to false. + * + * @return an array containing the binary file contents, + * or NULL if reading the file into memory failed. + * Must be destroyed by caller. + */ + unsigned char *readFileContents( int *outLength, + char inTextMode = false ); + + + + /** + * Writes a string to this file. + * + * @param inString the \0-terminated string to write. + * Must be destroyed by caller if non-const. + * + * @return true if the file was written to successfully, or + * false otherwise. + */ + char writeToFile( const char *inString ); + + + /** + * Writes a binary data to this file. + * + * @param inData the data to write. + * Must be destroyed by caller if non-const. + * @param inLength length of inData. + * + * @return true if the file was written to successfully, or + * false otherwise. + */ + char writeToFile( unsigned char *inData, int inLength ); + + + + private: + Path *mPath; + char *mName; + int mNameLength; + + + + /** + * Gets the files contained in this file if it is a directory and + * recursively in subdirectories of this file. + * + * @param inDepthLimit the maximum subdirectory depth to recurse into. + * If inDepthLimit is 0, then only child files in this directory + * will be returned. + * @param inResultVector vector to add the discovered files to. + * Must be destroyed by caller. + */ + void getChildFilesRecursive( int inDepthLimit, + SimpleVector *inResultVector ); + + + + }; + + + +inline File::File( Path *inPath, const char *inName, int inNameLength ) + : mPath( inPath ), mNameLength( inNameLength ) { + + if( inNameLength == -1 ) { + inNameLength = strlen( inName ); + mNameLength = inNameLength; + } + + // copy name internally + mName = stringDuplicate( inName ); + + } + + + +inline File::~File() { + delete [] mName; + + if( mPath != NULL ) { + delete mPath; + } + } + + + +inline long File::getLength() { + struct stat fileInfo; + + // get full file name + int length; + char *stringName = getFullFileName( &length ); + + int statError = stat( stringName, &fileInfo ); + + delete [] stringName; + + if( statError == 0 ) { + return fileInfo.st_size; + } + else { + // file does not exist + return 0; + } + } + + + +inline char File::isDirectory() { + struct stat fileInfo; + + // get full file name + int length; + char *stringName = getFullFileName( &length ); + + int statError = stat( stringName, &fileInfo ); + + delete [] stringName; + + if( statError == -1 ) { + return false; + } + else { + return S_ISDIR( fileInfo.st_mode ); + } + } + + + +inline File **File::getChildFiles( int *outNumFiles ) { + + int length; + char *stringName = getFullFileName( &length ); + + DIR *directory = opendir( stringName ); + + if( directory != NULL ) { + + SimpleVector< File* > *fileVector = new SimpleVector< File* >(); + + struct dirent *entry = readdir( directory ); + + if( entry == NULL ) { + delete fileVector; + + closedir( directory ); + + delete [] stringName; + + *outNumFiles = 0; + return NULL; + } + + + while( entry != NULL ) { + // skip parentdir and thisdir files, if they occur + if( strcmp( entry->d_name, "." ) && + strcmp( entry->d_name, ".." ) ) { + + Path *newPath; + + if( mPath != NULL ) { + newPath = mPath->append( mName ); + } + else { + + if( Path::isRoot( mName ) ) { + // use name as a string path + newPath = new Path( mName ); + } + else { + char **folderPathArray = new char*[1]; + folderPathArray[0] = mName; + + // a non-absolute path to this directory's contents + int numSteps = 1; + char absolute = false; + newPath = + new Path( folderPathArray, numSteps, + absolute ); + + delete [] folderPathArray; + } + } + + // safe to pass d_name in directly because it is copied + // internally by the constructor + + fileVector->push_back( + new File( newPath, + entry->d_name, + strlen( entry->d_name ) ) ); + } + + entry = readdir( directory ); + } + + // now we have a vector full of this directory's files + int vectorSize = fileVector->size(); + + *outNumFiles = vectorSize; + + if( vectorSize == 0 ) { + delete fileVector; + + closedir( directory ); + + delete [] stringName; + + return NULL; + } + else { + File **returnFiles = new File *[vectorSize]; + for( int i=0; igetElement( i ) ); + } + + delete fileVector; + + closedir( directory ); + + delete [] stringName; + + return returnFiles; + } + } + else { + delete [] stringName; + + *outNumFiles = 0; + return NULL; + } + + + + } + + + +inline File **File::getChildFilesRecursive( int inDepthLimit, + int *outNumFiles ) { + + // create a vector for results + SimpleVector *resultVector = new SimpleVector(); + + // call the recursive function + getChildFilesRecursive( inDepthLimit, resultVector ); + + + // extract results from vector + File **resultArray = NULL; + + int numResults = resultVector->size(); + + if( numResults > 0 ) { + resultArray = resultVector->getElementArray(); + } + + delete resultVector; + + + + *outNumFiles = numResults; + return resultArray; + } + + + +inline void File::getChildFilesRecursive( + int inDepthLimit, + SimpleVector *inResultVector ) { + + // get our child files + int numChildren; + File **childFiles = getChildFiles( &numChildren ); + + if( childFiles != NULL ) { + + // for each child, add it to vector and + // recurse into it if it is a directory + + for( int i=0; ipush_back( child ); + + if( child->isDirectory() ) { + // skip recursion if we have hit our depth limit + if( inDepthLimit > 0 ) { + // recurse into this subdirectory + child->getChildFilesRecursive( inDepthLimit - 1, + inResultVector ); + } + } + } + + delete [] childFiles; + } + } + + + +inline File *File::getChildFile( const char *inChildFileName ) { + // make sure we are a directory + if( !isDirectory() ) { + return NULL; + } + + // get a path to this directory + Path *newPath; + + if( mPath != NULL ) { + newPath = mPath->append( mName ); + } + else { + + char **folderPathArray = new char*[1]; + folderPathArray[0] = mName; + + // a non-absolute path to this directory's contents + int numSteps = 1; + char absolute = false; + newPath = + new Path( folderPathArray, numSteps, + absolute ); + + delete [] folderPathArray; + } + + return new File( newPath, inChildFileName ); + } + + + +inline File *File::getParentDirectory() { + + if( mPath != NULL ) { + + char *parentName; + + Path *parentPath; + + if( strcmp( mName, ".." ) == 0 ) { + // already a parent dir reference + // append one more parent dir reference with parentName below + parentPath = mPath->append( ".." ); + + parentName = stringDuplicate( ".." ); + } + else { + // not a parent dir reference, so we can truncate + parentPath = mPath->truncate(); + + parentName = mPath->getLastStep(); + } + + File *parentFile = new File( parentPath, parentName ); + + delete [] parentName; + + return parentFile; + } + else { + if( Path::isRoot( mName ) ) { + // we are already at the root + return new File( NULL, mName ); + } + else { + // append parent dir symbol to path + char **parentPathSteps = new char*[1]; + parentPathSteps[0] = mName; + + Path *parentPath = new Path( parentPathSteps, 1, false ); + + const char *parentName = ".."; + + File *parentFile = new File( parentPath, parentName ); + + delete [] parentPathSteps; + + return parentFile; + } + } + } + + + +inline char File::exists() { + struct stat fileInfo; + + // get full file name + int length; + char *stringName = getFullFileName( &length ); + + int statError = stat( stringName, &fileInfo ); + + delete [] stringName; + + if( statError == 0 ) { + return true; + } + else { + // file does not exist + return false; + } + } + + + +inline unsigned long File::getModificationTime() { + struct stat fileInfo; + + // get full file name + int length; + char *stringName = getFullFileName( &length ); + + int statError = stat( stringName, &fileInfo ); + + delete [] stringName; + + if( statError == 0 ) { + return fileInfo.st_mtime; + } + else { + // file does not exist + return 0; + } + } + + + +inline char File::remove() { + char returnVal = false; + + if( exists() ) { + char *stringName = getFullFileName(); + + int error = ::remove( stringName ); + + if( error == 0 ) { + returnVal = true; + } + + delete [] stringName; + } + + return returnVal; + } + + + +inline File *File::copy() { + Path *pathCopy = NULL; + + if( mPath != NULL ) { + pathCopy = mPath->copy(); + } + + return new File( pathCopy, mName ); + } + + + +inline void File::copy( File *inDestination, long inBlockSize ) { + char *thisFileName = getFullFileName(); + char *destinationFileName = inDestination->getFullFileName(); + + FILE *thisFile = fopen( thisFileName, "rb" ); + FILE *destinationFile = fopen( destinationFileName, "wb" ); + + long length = getLength(); + + long bytesCopied = 0; + + char *buffer = new char[ inBlockSize ]; + + while( bytesCopied < length ) { + + long bytesToCopy = inBlockSize; + + // end of file case + if( length - bytesCopied < bytesToCopy ) { + bytesToCopy = length - bytesCopied; + } + + fread( buffer, 1, bytesToCopy, thisFile ); + fwrite( buffer, 1, bytesToCopy, destinationFile ); + + bytesCopied += bytesToCopy; + } + + fclose( thisFile ); + fclose( destinationFile ); + + delete [] buffer; + delete [] thisFileName; + delete [] destinationFileName; + } + + + +inline char *File::getFileName( int *outLength ) { + char *returnName = stringDuplicate( mName ); + + if( outLength != NULL ) { + *outLength = mNameLength; + } + + return returnName; + } + + + +inline char *File::getFullFileName( int *outLength ) { + int length = mNameLength; + + int pathLength = 0; + char *path = NULL; + if( mPath != NULL ) { + path = mPath->getPathString( &pathLength ); + + length += pathLength; + } + + // extra character for '\0' termination + char *returnString = new char[ length + 1 ]; + + if( path != NULL ) { + memcpy( returnString, path, pathLength ); + memcpy( &( returnString[pathLength] ), mName, mNameLength ); + + delete [] path; + } + else { + // no path, so copy the name directly in + memcpy( returnString, mName, mNameLength ); + } + + // terminate the string + returnString[ length ] = '\0'; + + + if( outLength != NULL ) { + *outLength = length; + } + + return returnString; + } + + + +#include "minorGems/io/file/FileInputStream.h" +#include "minorGems/io/file/FileOutputStream.h" + + + +inline char *File::readFileContents() { + + int length; + // text mode! + unsigned char *data = readFileContents( &length, true ); + + if( data == NULL ) { + return NULL; + } + + char *dataString = new char[ length + 1 ]; + + memcpy( dataString, data, length ); + dataString[ length ] = '\0'; + + delete [] data; + return dataString; + } + + + +inline unsigned char *File::readFileContents( int *outLength, + char inTextMode ) { + + if( exists() ) { + int length = getLength(); + + unsigned char *returnData = new unsigned char[ length ]; + + if( returnData != NULL ) { + FileInputStream *input = new FileInputStream( this, inTextMode ); + int numRead = input->read( returnData, length ); + + delete input; + + // in text mode, read length might not equal binary file length, + // due to line end conversion + if( numRead == length || + ( inTextMode && numRead >= 0 ) ) { + *outLength = numRead; + return returnData; + } + else { + delete [] returnData; + return NULL; + } + } + else { + // failed to allocate this much memory + return NULL; + } + } + else { + return NULL; + } + + } + + + +inline char File::writeToFile( const char *inString ) { + return writeToFile( (unsigned char *)inString, strlen( inString ) ); + } + + + +inline char File::writeToFile( unsigned char *inData, int inLength ) { + FileOutputStream *output = new FileOutputStream( this ); + + long numWritten = output->write( inData, inLength ); + + delete output; + + if( inLength == numWritten ) { + return true; + } + else { + return false; + } + + } + + + +#include "Directory.h" + + + +inline char File::makeDirectory() { + if( exists() ) { + return false; + } + else { + return Directory::makeDirectory( this ); + } + } + + + +#endif diff --git a/minorGems/io/file/FileInputStream.h b/minorGems/io/file/FileInputStream.h new file mode 100644 index 0000000..f05cd58 --- /dev/null +++ b/minorGems/io/file/FileInputStream.h @@ -0,0 +1,183 @@ +/* + * Modification History + * + * 2001-February-11 Jason Rohrer + * Created. + * + * 2001-April-12 Jason Rohrer + * Changed so that File is not destroyed when this stream is destroyed. + * + * 2001-April-29 Jason Rohrer + * Fixed a bug in the use of fread + * (num elements and element size swapped). + * Fixed a memory leak in the error message handling. + * + * 2006-November-23 Jason Rohrer + * Fixed a memory leak in error message handling. + * + * 2010-April-22 Jason Rohrer + * Added support for text mode. + */ + +#include "minorGems/common.h" + + + +#ifndef FILE_INPUT_STREAM_CLASS_INCLUDED +#define FILE_INPUT_STREAM_CLASS_INCLUDED + +#include "minorGems/io/InputStream.h" +#include "File.h" + +#include + +/** + * File implementation of an InputStream. + * + * @author Jason Rohrer + */ +class FileInputStream : public InputStream { + + public: + + /** + * Constructs an input stream. + * + * @param inFile the file to open for reading. + * If the file does not exist, all calls to read will fail. + * inFile is NOT destroyed when this class is destroyed. + * @param inTextMode true to open the file as text, false as binary. + * Defaults to false. + */ + FileInputStream( File *inFile, char inTextMode = false ); + + + /** + * Destroys this stream and closes the file. + */ + ~FileInputStream(); + + + /** + * Gets the file attached to this stream. + * + * @return the file used by this stream. + * Should not be modified or destroyed by caller until after + * this class is destroyed. + */ + File *getFile(); + + + // implementst InputStream interface + virtual long read( unsigned char *inBuffer, long inNumBytes ); + + private: + File *mFile; + + FILE *mUnderlyingFile; + }; + + + + +inline FileInputStream::FileInputStream( File *inFile, char inTextMode ) + : mFile( inFile ) { + + int fileNameLength; + + char *fileName = mFile->getFullFileName( &fileNameLength ); + + if( inTextMode ) { + mUnderlyingFile = fopen( fileName, "r" ); + } + else { + mUnderlyingFile = fopen( fileName, "rb" ); + } + + + if( mUnderlyingFile == NULL ) { + // file open failed. + + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, "Opening file %s failed.", fileName ); + setNewLastError( stringBuffer ); + } + + delete [] fileName; + } + + + +inline FileInputStream::~FileInputStream() { + if( mUnderlyingFile != NULL ) { + fclose( mUnderlyingFile ); + } + } + + + +inline File *FileInputStream::getFile() { + return mFile; + } + + + +inline long FileInputStream::read( + unsigned char *inBuffer, long inNumBytes ) { + + if( mUnderlyingFile != NULL ) { + + long numRead = fread( inBuffer, 1, inNumBytes, mUnderlyingFile ); + + if( numRead < inNumBytes ) { + + int fileNameLength; + char *fileName = mFile->getFullFileName( &fileNameLength ); + + if( feof( mUnderlyingFile ) ) { + // we reached the end of the file. + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, "Reached end of file %s on read.", + fileName ); + setNewLastError( stringBuffer ); + + delete [] fileName; + } + else { + // some other kind of error occured + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, "Reading from file %s failed.", + fileName ); + setNewLastError( stringBuffer ); + + delete [] fileName; + + if( numRead == 0 ) { + // a complete read failure + return -1; + } + } + } + + return numRead; + } + else { + // file was not opened properly + + int fileNameLength; + char *fileName = mFile->getFullFileName( &fileNameLength ); + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, + "File %s was not opened properly before reading.", + fileName ); + delete [] fileName; + + setNewLastError( stringBuffer ); + + return -1; + } + } + + + +#endif diff --git a/minorGems/io/file/FileOutputStream.h b/minorGems/io/file/FileOutputStream.h new file mode 100644 index 0000000..fe53223 --- /dev/null +++ b/minorGems/io/file/FileOutputStream.h @@ -0,0 +1,175 @@ +/* + * Modification History + * + * 2001-February-11 Jason Rohrer + * Created. + * + * 2001-April-12 Jason Rohrer + * Changed so that File is not destroyed when this stream is destroyed. + * + * 2001-April-29 Jason Rohrer + * Fixed a bug in the use of fwrite + * (num elements and element size swapped). + * Fixed a memory leak in the error message handling. + * + * 2006-August-22 Jason Rohrer + * Fixed include order bug. + * + * 2010-April-6 Jason Rohrer + * Fixed memory leak. + */ + +#include "minorGems/common.h" + + +#include "File.h" + + +#ifndef FILE_OUTPUT_STREAM_CLASS_INCLUDED +#define FILE_OUTPUT_STREAM_CLASS_INCLUDED + +#include "minorGems/io/OutputStream.h" + +#include + +/** + * File implementation of an OutputStream. + * + * @author Jason Rohrer + */ +class FileOutputStream : public OutputStream { + + public: + + /** + * Constructs an output stream. + * + * @param inFile the file to open for writing. + * If the file does not exist, it will be created. + * inFile is NOT destroyed when this class is destroyed. + * @param inAppend set to true to append to file. If + * file does not exist, file is still created. Defaults + * to false. + */ + FileOutputStream( File *inFile, char inAppend = false ); + + + /** + * Destroys this stream and closes the file. + */ + ~FileOutputStream(); + + + /** + * Gets the file attached to this stream. + * + * @return the file used by this stream. + * Should not be modified or destroyed by caller until after + * this class is destroyed. + */ + File *getFile(); + + + // implementst OutputStream interface + virtual long write( unsigned char *inBuffer, long inNumBytes ); + + private: + File *mFile; + + FILE *mUnderlyingFile; + }; + + + + +inline FileOutputStream::FileOutputStream( File *inFile, + char inAppend ) + : mFile( inFile ) { + + int fileNameLength; + + char *fileName = mFile->getFullFileName( &fileNameLength ); + + if( inAppend ) { + mUnderlyingFile = fopen( fileName, "ab" ); + } + else { + mUnderlyingFile = fopen( fileName, "wb" ); + } + + if( mUnderlyingFile == NULL ) { + // file open failed. + + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, "Opening file %s failed.", fileName ); + setNewLastError( stringBuffer ); + } + + delete [] fileName; + } + + + +inline FileOutputStream::~FileOutputStream() { + if( mUnderlyingFile != NULL ) { + fclose( mUnderlyingFile ); + } + } + + + +inline File *FileOutputStream::getFile() { + return mFile; + } + + + +inline long FileOutputStream::write( + unsigned char *inBuffer, long inNumBytes ) { + + if( mUnderlyingFile != NULL ) { + + long numWritten = + fwrite( inBuffer, 1, inNumBytes, mUnderlyingFile ); + + if( numWritten < inNumBytes ) { + int fileNameLength; + char *fileName = mFile->getFullFileName( &fileNameLength ); + + // some other kind of error occured + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, "Writing to file %s failed.", + fileName ); + setNewLastError( stringBuffer ); + + delete [] fileName; + + if( numWritten == 0 ) { + // a complete write failure + return -1; + } + } + + return numWritten; + } + else { + // file was not opened properly + + int fileNameLength; + char *fileName = mFile->getFullFileName( &fileNameLength ); + char *stringBuffer = new char[ fileNameLength + 50 ]; + sprintf( stringBuffer, + "File %s was not opened properly before writing.", + fileName ); + + delete [] fileName; + + setNewLastError( stringBuffer ); + + return -1; + } + } + + + +#endif diff --git a/minorGems/io/file/Path.h b/minorGems/io/file/Path.h new file mode 100644 index 0000000..a8e00de --- /dev/null +++ b/minorGems/io/file/Path.h @@ -0,0 +1,615 @@ +/* + * Modification History + * + * 2001-February-12 Jason Rohrer + * Created. + * + * 2001-May-11 Jason Rohrer + * Added a version of getPathString that + * returns a '\0' terminated string. + * + * 2001-September-21 Jason Rohrer + * Added a missing include. + * + * 2001-September-23 Jason Rohrer + * Added a copy function. + * Made some comments more explicit. + * Changed the constructor to allow for const path step strings. + * + * 2001-November-3 Jason Rohrer + * Added a function for appending a string to a path. + * Changed the interface to the main constructor. + * + * 2002-March-29 Jason Rohrer + * Added Fortify inclusion. + * + * 2002-April-11 Jason Rohrer + * Fixed a variable scoping bug. + * + * 2002-July-2 Jason Rohrer + * Fixed a major memory leak in copy(). + * + * 2002-August-1 Jason Rohrer + * Added support for path truncation. + * Added support for parsing platform-dependent path strings. + * + * 2003-May-29 Jason Rohrer + * Fixed a bug when an extra delimeters are at the end of the path. + * Fixed a bug when string path consists only of root. + * + * 2003-June-2 Jason Rohrer + * Fixed a bug in absolute path detection. + * Added platform-specific functions for root and absolute path detection. + * Fixed a memory bug when string path contains root only. + * Fixed a path step bug when path is root. + * Fixed bugs in truncate and append when non-default root string is used. + * + * 2005-August-29 Jason Rohrer + * Fixed an uninitialized variable warning. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef PATH_CLASS_INCLUDED +#define PATH_CLASS_INCLUDED + + +#include + + + +#include "minorGems/util/stringUtils.h" + + + +#ifdef FORTIFY +#include "minorGems/util/development/fortify/fortify.h" +#endif + + + +/** + * Platform-independent file path interface. Contains + * all of path except for file name. Thus, appending + * a file name to the path will produce a complete file path. + * + * E.g., on Linux, file path: + * temp/files/ + * file name: + * test.txt + * full path: + * temp/files/test.txt + * + * @author Jason Rohrer + */ +class Path { + + public: + + /** + * Constructs a path. + * + * @param inPathSteps an array of c-strings representing + * each step in the path, with no delimeters. + * For example, { "temp", "files" } to represent + * the linux path temp/files. + * Must be destroyed by caller since copied internally. + * @param inNumSteps the number of strings in the path. + * @param inAbsolute set to true to make this an absolute + * path. For example, in Linux, an absolute path + * is one that starts with '/', as in /usr/include/. + * The effects of inAbsolute vary by platform. + * @param inRootString the root string for this path if it + * is absolute, or NULL to specify a default root. + * Defaults to NULL. + * Must be destroyed by caller if non-NULL. + */ + Path( char **inPathSteps, int inNumSteps, char inAbsolute, + char *inRootString = NULL ); + + + + /** + * Constructs a path by parsing a platform-dependent path string. + * + * @param inPathSteps a \0-terminated string representing the path. + * Must be destroyed by caller. + */ + Path( const char *inPathString ); + + + + ~Path(); + + + + /** + * Returns a complete, platform-dependent string path. + * + * @param outLength pointer to where the path length, in + * characters, will be returned. + * + * @return a new char array containing the path. Note + * that the string is not terminated by '\0'. Must + * be destroyed by the caller. + */ + char *getPathString( int *outLength ); + + + /** + * Returns a complete, platform-dependent string path, terminated + * bye '\0'. + * + * @return a new char array containing the path. Note + * that the string IS terminated by '\0'. Must + * be destroyed by the caller. + */ + char *getPathStringTerminated(); + + + /** + * Gets the platform-specific path delimeter. + * + * Note that this function is implemented separately for + * each supported platform. + * + * @return the path delimeter. + */ + static char getDelimeter(); + + + /** + * Gets start characters for an absolute path. + * + * Note that this function is implemented separately for + * each supported platform. + * + * @param outLength pointer to where the string length, in + * characters, will be returned. + * + * @return the absolute path start string characters. For + * example, on Linux, this would be the string "/". + * Must be destroyed by the caller. + */ + static char *getAbsoluteRoot( int *outLength ); + + + + /** + * Gets whether a path string is absolute. + * + * Note that this function is implemented separately for + * each supported platform. + * + * @param inPathString the string to check. + * Must be destroyed by caller if non-const. + * + * @return true if the string is absolute, or false otherwise. + */ + static char isAbsolute( const char *inPathString ); + + + + /** + * Extracts the root string from a path string. + * + * + * @param inPathString the string to check. + * Must be destroyed by caller if non-const. + * + * @return the root string, or NULL if inPathString is not + * absolute. Must be destroyed by caller if non-NULL. + */ + static char *extractRoot( const char *inPathString ); + + + + /** + * Gets whether a path string is a root path. + * + * Note that this function is implemented separately for + * each supported platform. For example, on Unix, only "/" + * is the root path, while on Windows, both "c:\" and "d:\" might + * be root paths. + * + * @param inPathString the string to check. + * Must be destroyed by caller if non-const. + * + * @return true if the string is a root string, or false otherwise. + */ + static char isRoot( const char *inPathString ); + + + + /** + * Gets start string for an absolute path. + * + * @return the absolute path start string in \0-terminated form. + * Must be destroyed by the caller. + */ + static char *getAbsoluteRootString(); + + + + /** + * Copies this path. + * + * @return a new path that is a deep copy of this path. + */ + Path *copy(); + + + + /** + * Constructs a new path by appending an additional + * step onto this path. + * + * @param inStepString the step to add to this path. + * Must be destroyed by caller if non-const. + * + * @return a new path with the extra step. + * Must be destroyed by caller. + */ + Path *append( const char *inStepString ); + + + + /** + * Constructs a new path by removing the last step from this path. + * + * @return a new path, or NULL if there is only one step in this path. + * Must be destroyed by caller. + */ + Path *truncate(); + + + + /** + * Gets the last step in this path. + * + * @return the last step. Must be destroyed by caller. + */ + char *getLastStep(); + + + + private: + char **mPathSteps; + int mNumSteps; + int *mStepLength; + char mAbsolute; + + // the root string of this path, if it is absolute + char *mRootString; + }; + + + +inline Path::Path( char **inPathSteps, int inNumSteps, + char inAbsolute, char *inRootString ) + : mNumSteps( inNumSteps ), mAbsolute( inAbsolute ), + mRootString( NULL ) { + + if( inRootString != NULL ) { + mRootString = stringDuplicate( inRootString ); + } + + // copy the path steps + + mPathSteps = new char*[ mNumSteps ]; + mStepLength = new int[ mNumSteps ]; + + for( int i=0; i 1 ) { + // don't count tail end delimeters + delimCount++; + } + currentDelimPointer = strstr( &( currentDelimPointer[1] ), + delimString ); + } + + + // no delimeter at end of path + mNumSteps = delimCount + 1; + + mPathSteps = new char*[ mNumSteps ]; + mStepLength = new int[ mNumSteps ]; + + + // now extract the chars between delimeters as path steps + currentDelimPointer = strstr( pathRootSkipped, delimString ); + + int stepIndex = 0; + + currentDelimPointer[0] = '\0'; + + mPathSteps[ stepIndex ] = stringDuplicate( pathRootSkipped ); + mStepLength[ stepIndex ] = strlen( mPathSteps[ stepIndex ] ); + stepIndex++; + + while( currentDelimPointer != NULL ) { + + char *nextDelimPointer = strstr( &( currentDelimPointer[1] ), + delimString ); + + if( nextDelimPointer != NULL ) { + nextDelimPointer[0] = '\0'; + } + + mPathSteps[ stepIndex ] = + stringDuplicate( &( currentDelimPointer[1] ) ); + mStepLength[ stepIndex ] = strlen( mPathSteps[ stepIndex ] ); + + stepIndex++; + + currentDelimPointer = nextDelimPointer; + } + + } + else { + // no delimeters + + if( strlen( pathRootSkipped ) > 0 ) { + mNumSteps = 1; + mPathSteps = new char*[1]; + mPathSteps[0] = stringDuplicate( pathRootSkipped ); + + mStepLength = new int[1]; + + mStepLength[0] = strlen( mPathSteps[0] ); + } + else { + // path with root only + mNumSteps = 0; + mPathSteps = new char*[0]; + mStepLength = new int[0]; + } + } + + delete [] delimString; + + delete [] pathStringCopy; + } + + + +inline Path::~Path() { + // delete each step + for( int i=0; i= 1 ) { + return stringDuplicate( mPathSteps[ mNumSteps - 1 ] ); + } + else { + if( mAbsolute ) { + if( mRootString != NULL ) { + return stringDuplicate( mRootString ); + } + else { + return getAbsoluteRootString(); + } + } + else { + // no path steps and not absolute... + return stringDuplicate( "" ); + } + } + } + + + +inline char *Path::getAbsoluteRootString() { + int rootLength; + char *root = getAbsoluteRoot( &rootLength ); + + char *rootString = new char[ rootLength + 1 ]; + strncpy( rootString, root, rootLength ); + // strncopy won't add termination if length limit reached + rootString[ rootLength ] = '\0'; + + delete [] root; + return rootString; + } + + + +#endif diff --git a/minorGems/io/file/UniversalFileIO.h b/minorGems/io/file/UniversalFileIO.h new file mode 100644 index 0000000..ceba4c3 --- /dev/null +++ b/minorGems/io/file/UniversalFileIO.h @@ -0,0 +1,138 @@ +// Jason Rohrer +// UniversalFileIO.h + +/** +* +* Object that handles universal file reading and writing +* Writes files as big endian, even on little endian machines +* +* Assumes that longs and floats are 32-bits +* +* Created 1-12-99 +* Mods: +* Jason Rohrer 1-30-2000 Fixed fwrite functions to work on little endian machines +*/ + + + +#ifndef UNIVERSAL_FILE_IO_INCLUDED +#define UNIVERSAL_FILE_IO_INCLUDED + + +#include + + + +class UniversalFileIO { + + public: + + UniversalFileIO(); + + long freadLong( FILE *f ); + float freadFloat( FILE *f ); + + void fwriteLong( FILE *f, long x ); + void fwriteFloat( FILE *f, float x ); + + private: + + char machineBigEndian; + + char bytesInLong; + char bytesInFloat; + + }; + + +inline UniversalFileIO::UniversalFileIO() + : machineBigEndian( true ), + bytesInLong( 4 ), bytesInFloat( 4 ) + { + + // test whether machine is big endian + long test = 1; + char testChopped = (*(char*)&test); + if( testChopped == 1 ) machineBigEndian = false; + } + + + + +inline long UniversalFileIO::freadLong( FILE *f ) { + if( machineBigEndian ) { + long returnVal; + fread((void *)&returnVal, sizeof(long), 1, f); + return returnVal; + } + else { + unsigned char *buffer = new unsigned char[bytesInLong]; + + fread((void *)buffer, sizeof(char), bytesInLong, f); + + // now put the bytes into a long + long returnVal = (long)( buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3] ); + + delete [] buffer; + + return returnVal; + } + } + +inline float UniversalFileIO::freadFloat( FILE *f ) { + if( machineBigEndian ) { + float returnVal; + fread( (void *)&returnVal, sizeof(float), 1, f ); + return returnVal; + } + else { + unsigned char *buffer = new unsigned char[bytesInFloat]; + + fread( (void *)buffer, sizeof(char), bytesInFloat, f ); + + // now put the bytes into a long + long temp = (long)(buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]); + + delete [] buffer; + + return *((float *) &temp); // convert long into a float + } + } + + +inline void UniversalFileIO::fwriteLong( FILE *f, long x ) { + if( machineBigEndian ) { + fwrite( (void *)&x, sizeof(long), 1, f ); + } + else { + unsigned char *buffer = new unsigned char[bytesInLong]; + + buffer[0] = (unsigned char)(x >> 24); // put bytes from long into char buffer + buffer[1] = (unsigned char)(x >> 16); + buffer[2] = (unsigned char)(x >> 8); + buffer[3] = (unsigned char)(x); + + fwrite( (void *)buffer, sizeof(char), bytesInLong, f ); + } + } + +inline void UniversalFileIO::fwriteFloat( FILE *f, float x ) { + if( machineBigEndian ) { + fwrite( (void *)&x, sizeof(float), 1, f ); + } + else { + unsigned char *buffer = new unsigned char[bytesInFloat]; + + long temp = *((long*)&x); // convert float into long so that bit-wise ops can be performed + + buffer[0] = (unsigned char)(temp >> 24); // put bytes from float into char buffer + buffer[1] = (unsigned char)(temp >> 16); + buffer[2] = (unsigned char)(temp >> 8); + buffer[3] = (unsigned char)(temp); + + fwrite( (void *)buffer, sizeof(char), bytesInFloat, f ); + } + } + + +#endif \ No newline at end of file diff --git a/minorGems/io/file/linux/PathLinux.cpp b/minorGems/io/file/linux/PathLinux.cpp new file mode 100644 index 0000000..ee93af7 --- /dev/null +++ b/minorGems/io/file/linux/PathLinux.cpp @@ -0,0 +1,77 @@ +/* + * Modification History + * + * 2001-February-12 Jason Rohrer + * Created. + * + * 2001-August-1 Jason Rohrer + * Added missing length return value. + * + * 2003-June-2 Jason Rohrer + * Added support for new path checking functions. + * + * 2010-May-18 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/io/file/Path.h" +#include "minorGems/util/stringUtils.h" + + + +/* + * Linux-specific path implementation. May be compatible + * with other posix-complient systems. + */ + + + +char Path::getDelimeter() { + return '/'; + } + + + +char *Path::getAbsoluteRoot( int *outLength ) { + char *returnString = new char[1]; + returnString[0] = '/'; + + *outLength = 1; + + return returnString; + } + + + +char Path::isAbsolute( const char *inPathString ) { + if( inPathString[0] == '/' ) { + return true; + } + else { + return false; + } + } + + + +char *Path::extractRoot( const char *inPathString ) { + if( isAbsolute( inPathString ) ){ + return stringDuplicate( "/" ); + } + else { + return NULL; + } + } + + + +char Path::isRoot( const char *inPathString ) { + if( strcmp( inPathString, "/" ) == 0 ) { + return true; + } + else { + return false; + } + } + + diff --git a/minorGems/io/file/test/testChildFiles.cpp b/minorGems/io/file/test/testChildFiles.cpp new file mode 100644 index 0000000..82f0b8d --- /dev/null +++ b/minorGems/io/file/test/testChildFiles.cpp @@ -0,0 +1,73 @@ +/* + * Modification History + * + * 2004-January-4 Jason Rohrer + * Created. + */ + + +/** + * A test program for the various child file functions in File.h + * + * @author Jason Rohrer. + */ + + + +#include "minorGems/io/file/File.h" + + + +int main( int inNumArgs, char **inArgs ) { + + char *fileName = "linux"; + + if( inNumArgs > 1 ) { + fileName = inArgs[1]; + } + + File *testFile = new File( NULL, fileName ); + + int numChildren; + + File **childFiles = testFile->getChildFiles( &numChildren ); + + + printf( "child files:\n" ); + for( int i=0; igetFullFileName(); + + printf( " %s\n", fullName ); + + delete [] fullName; + + delete childFiles[i]; + } + + delete [] childFiles; + + + + childFiles = testFile->getChildFilesRecursive( 10, &numChildren ); + + + printf( "recursive child files:\n" ); + for( int i=0; igetFullFileName(); + + printf( " %s\n", fullName ); + + delete [] fullName; + + delete childFiles[i]; + } + + delete [] childFiles; + + + delete testFile; + + return 0; + } diff --git a/minorGems/io/file/test/testPath.cpp b/minorGems/io/file/test/testPath.cpp new file mode 100644 index 0000000..b8af5b4 --- /dev/null +++ b/minorGems/io/file/test/testPath.cpp @@ -0,0 +1,39 @@ +/* + * Modification History + * + * 2002-August-1 Jason Rohrer + * Created. + */ + + +#include "Path.h" + +#include + + + +int main() { + + char *pathString = "/test/this/thing"; + + printf( "using path string = %s\n", pathString ); + + printf( "Constructing path.\n" ); + Path *path = new Path( pathString ); + + + printf( "Extracting path string.\n" ); + char *extractedPathString = path->getPathStringTerminated(); + + printf( "extracted path string = %s\n", extractedPathString ); + + + + delete [] extractedPathString; + + delete path; + + return 1; + } + + diff --git a/minorGems/io/file/testPath.cpp b/minorGems/io/file/testPath.cpp new file mode 100644 index 0000000..b8af5b4 --- /dev/null +++ b/minorGems/io/file/testPath.cpp @@ -0,0 +1,39 @@ +/* + * Modification History + * + * 2002-August-1 Jason Rohrer + * Created. + */ + + +#include "Path.h" + +#include + + + +int main() { + + char *pathString = "/test/this/thing"; + + printf( "using path string = %s\n", pathString ); + + printf( "Constructing path.\n" ); + Path *path = new Path( pathString ); + + + printf( "Extracting path string.\n" ); + char *extractedPathString = path->getPathStringTerminated(); + + printf( "extracted path string = %s\n", extractedPathString ); + + + + delete [] extractedPathString; + + delete path; + + return 1; + } + + diff --git a/minorGems/io/file/unix/DirectoryUnix.cpp b/minorGems/io/file/unix/DirectoryUnix.cpp new file mode 100644 index 0000000..16308ee --- /dev/null +++ b/minorGems/io/file/unix/DirectoryUnix.cpp @@ -0,0 +1,51 @@ +/* + * Modification History + * + * 2003-January-23 Jason Rohrer + * Created. + * + * 2003-November-10 Jason Rohrer + * Added makeDirectory function. + */ + + + +#include "minorGems/io/file/Directory.h" + + +#include + + + +char Directory::removeDirectory( File *inFile ) { + char *fileName = inFile->getFullFileName(); + + int result = rmdir( fileName ); + + delete [] fileName; + + if( result == 0 ) { + return true; + } + else { + return false; + } + } + + + +char Directory::makeDirectory( File *inFile ) { + char *stringName = inFile->getFullFileName(); + + int result = mkdir( stringName, 0xFFFF ); + + delete [] stringName; + + if( 0 == result ) { + return true; + } + else { + return false; + } + } + diff --git a/minorGems/io/file/win32/DirectoryWin32.cpp b/minorGems/io/file/win32/DirectoryWin32.cpp new file mode 100644 index 0000000..282ca9d --- /dev/null +++ b/minorGems/io/file/win32/DirectoryWin32.cpp @@ -0,0 +1,53 @@ +/* + * Modification History + * + * 2003-January-23 Jason Rohrer + * Created. + * + * 2003-November-10 Jason Rohrer + * Added makeDirectory function. + */ + + + +#include "minorGems/io/file/Directory.h" + + + +#include + + + +char Directory::removeDirectory( File *inFile ) { + char *fileName = inFile->getFullFileName(); + + int result = _rmdir( fileName ); + + delete [] fileName; + + if( result == 0 ) { + return true; + } + else { + return false; + } + } + + + +char Directory::makeDirectory( File *inFile ) { + char *stringName = inFile->getFullFileName(); + + int result = mkdir( stringName ); + + delete [] stringName; + + if( 0 == result ) { + return true; + } + else { + return false; + } + } + + diff --git a/minorGems/io/file/win32/PathWin32.cpp b/minorGems/io/file/win32/PathWin32.cpp new file mode 100644 index 0000000..4d36f9a --- /dev/null +++ b/minorGems/io/file/win32/PathWin32.cpp @@ -0,0 +1,95 @@ +/* + * Modification History + * + * 2001-February-12 Jason Rohrer + * Created. + * + * 2001-March-4 Jason Rohrer + * Fixed delimeter constants. + * + * 2001-August-1 Jason Rohrer + * Added missing length return value. + * + * 2003-June-2 Jason Rohrer + * Added support for new path checking functions. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/io/file/Path.h" +#include "minorGems/util/stringUtils.h" + + +/* + * Windows-specific path implementation. + */ + + + +char Path::getDelimeter() { + return '\\'; + } + + + +char *Path::getAbsoluteRoot( int *outLength) { + // C:\ is the only root we can generically return + + char *returnString = new char[3]; + + returnString[0] = 'C'; + returnString[1] = ':'; + returnString[2] = '\\'; + + *outLength = 3; + + return returnString; + } + + + +char Path::isAbsolute( const char *inPathString ) { + // ignore first character, which will be drive letter + if( inPathString[1] == ':' && inPathString[2] == '\\' ) { + return true; + } + else { + return false; + } + } + + + +char *Path::extractRoot( const char *inPathString ) { + if( isAbsolute( inPathString ) ){ + // copy path, then trim to only three characters + + char *pathCopy = stringDuplicate( inPathString ); + pathCopy[ 3 ] = '\0'; + + char *trimmedCopy = stringDuplicate( pathCopy ); + delete [] pathCopy; + + return trimmedCopy; + } + else { + return NULL; + } + } + + + +char Path::isRoot( const char *inPathString ) { + // must be of form "c:\" + if( strlen( inPathString ) == 3 && + inPathString[1] == ':' && + inPathString[2] == '\\' ) { + return true; + } + else { + return false; + } + } + + diff --git a/minorGems/io/file/win32/dirent.cpp b/minorGems/io/file/win32/dirent.cpp new file mode 100644 index 0000000..6b6d561 --- /dev/null +++ b/minorGems/io/file/win32/dirent.cpp @@ -0,0 +1,159 @@ +/* + * Modification History + * + * 2002-April-7 Jason Rohrer + * Added a mkdir wrapper for CodeWarrior. + * + * 2002-April-11 Jason Rohrer + * Changed type of mode parameter to work with Visual C++. + * Added missing include. + * + * 2002-July-22 Jason Rohrer + * Commented out mkdir replacement function to work with new MSL. + * + * 2002-October-13 Jason Rohrer + * Re-added mkdir wrapper function, since both CW4 and VC++ need it. + */ + + + +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Kevlin Henney (mailto:kevlin@acm.org), March 1997. + + Copyright Kevlin Henney, 1997. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives, and that no charge may be made for the software and its + documentation except to cover cost of distribution. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#include +#include +#include +#include +#include + +#include + + +struct DIR +{ + long handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* NTBS */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* the root directory is a special case... */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + + + +int mkdir( const char *pathname, unsigned int mode ) { + return mkdir( pathname ); + } diff --git a/minorGems/io/file/win32/dirent.h b/minorGems/io/file/win32/dirent.h new file mode 100644 index 0000000..7e814d2 --- /dev/null +++ b/minorGems/io/file/win32/dirent.h @@ -0,0 +1,77 @@ +/* + * Modification History + * + * 2002-April-7 Jason Rohrer + * Added a mkdir wrapper for CodeWarrior. + * + * 2002-April-11 Jason Rohrer + * Changed type of mode parameter to work with Visual C++. + * Added missing macros. + * + * 2002-July-22 Jason Rohrer + * Commented out mkdir replacement function to work with new MSL. + * + * 2002-October-13 Jason Rohrer + * Re-added mkdir wrapper function, since both CW4 and VC++ need it. + */ + +#include "minorGems/common.h" + + + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Kevlin Henney (mailto:kevlin@acm.org), March 1997. + + Copyright Kevlin Henney, 1997. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives, and that no charge may be made for the software and its + documentation except to cover cost of distribution. + +*/ + +#ifndef DIRENT_INCLUDED +#define DIRENT_INCLUDED + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + + + +#include +/** + * The Metrowerks Standard Library seems + * to have only a 1-parameter mkdir command in sys/stat.h. + */ +int mkdir( const char *pathname, unsigned int mode ); + + +// make sure our needed macros are defined +// S_IFMT and S_IFDIR seem to be defined everywhere + +#ifndef __S_ISTYPE +#define __S_ISTYPE(mode, mask) (((mode) & S_IFMT) == (mask)) +#endif + + +#ifndef S_ISDIR +#define S_ISDIR(mode) __S_ISTYPE((mode), S_IFDIR) +#endif + + + +#endif diff --git a/minorGems/io/linux/TypeIOLinux.cpp b/minorGems/io/linux/TypeIOLinux.cpp new file mode 100644 index 0000000..f926a21 --- /dev/null +++ b/minorGems/io/linux/TypeIOLinux.cpp @@ -0,0 +1,132 @@ +/* + * Modification History + * + * 2001-February-3 Jason Rohrer + * Created. + * Fixed a bug in the big-endian code. + * Fixed parameter names to match convention. + * + * 2001-February-26 Jason Rohrer + * Fixed a bug in the little-endian implementation. + * + * 2001-August-28 Jason Rohrer + * Changed to be FreeBSD compatible. + * + * 2002-March-13 Jason Rohrer + * Started to change to work with solaris. + * Finished changing to work with solaris. + * + * 2002-April-11 Jason Rohrer + * Added a default BSD case to work with OSX. + * + * 2002-May-25 Jason Rohrer + * Changed to use minorGems endian.h + */ + + +#include "minorGems/io/TypeIO.h" +#include "minorGems/system/endian.h" + + + + +/* + * Linux-specific type input and output. + * Note that all types are output in the order that + * a big-endian linux machine outputs them with no conversion. + */ + + + +#if __BYTE_ORDER == __LITTLE_ENDIAN + + +void TypeIO::doubleToBytes( double inDouble, unsigned char *outBytes ) { + + unsigned char *doubleBuffer = (unsigned char*)( &inDouble ); + + // output second word first + outBytes[0] = doubleBuffer[7]; + outBytes[1] = doubleBuffer[6]; + outBytes[2] = doubleBuffer[5]; + outBytes[3] = doubleBuffer[4]; + + outBytes[4] = doubleBuffer[3]; + outBytes[5] = doubleBuffer[2]; + outBytes[6] = doubleBuffer[1]; + outBytes[7] = doubleBuffer[0]; + } + + + +double TypeIO::bytesToDouble( unsigned char *inBytes ) { + + double returnValue; + + unsigned char *doubleBuffer = (unsigned char*)( &returnValue ); + + // put first word at the end of this double + doubleBuffer[7] = inBytes[0]; + doubleBuffer[6] = inBytes[1]; + doubleBuffer[5] = inBytes[2]; + doubleBuffer[4] = inBytes[3]; + + doubleBuffer[3] = inBytes[4]; + doubleBuffer[2] = inBytes[5]; + doubleBuffer[1] = inBytes[6]; + doubleBuffer[0] = inBytes[7]; + + return returnValue; + } + + + +#endif + + + +#if __BYTE_ORDER == __BIG_ENDIAN + + + +void TypeIO::doubleToBytes( double inDouble, unsigned char *outBytes ) { + + unsigned char *doubleBuffer = (unsigned char*)( &inDouble ); + + // output in stored order + outBytes[0] = doubleBuffer[0]; + outBytes[1] = doubleBuffer[1]; + outBytes[2] = doubleBuffer[2]; + outBytes[3] = doubleBuffer[3]; + + outBytes[4] = doubleBuffer[4]; + outBytes[5] = doubleBuffer[5]; + outBytes[6] = doubleBuffer[6]; + outBytes[7] = doubleBuffer[7]; + } + + + +double TypeIO::bytesToDouble( unsigned char *inBytes ) { + + double returnValue; + + unsigned char *doubleBuffer = (unsigned char*)( &returnValue ); + + // store in input order + doubleBuffer[0] = inBytes[0]; + doubleBuffer[1] = inBytes[1]; + doubleBuffer[2] = inBytes[2]; + doubleBuffer[3] = inBytes[3]; + + doubleBuffer[4] = inBytes[4]; + doubleBuffer[5] = inBytes[5]; + doubleBuffer[6] = inBytes[6]; + doubleBuffer[7] = inBytes[7]; + + return returnValue; + } + + + +#endif diff --git a/minorGems/io/pipedStreamTest.cpp b/minorGems/io/pipedStreamTest.cpp new file mode 100644 index 0000000..668c63e --- /dev/null +++ b/minorGems/io/pipedStreamTest.cpp @@ -0,0 +1,78 @@ +/* + * Modification History + * + * 2001-February-19 Jason Rohrer + * Created. + */ + +#include + +#include "PipedStream.h" + +#include "minorGems/util/random/StdRandomSource.h" + +// test function for piped streams +int main() { + + PipedStream *stream = new PipedStream; + InputStream *inStream = stream; + OutputStream *outStream = stream; + + StdRandomSource *randSource = new StdRandomSource(); + + unsigned char *bufferA = new unsigned char[10]; + unsigned char *bufferB = new unsigned char[15]; + + int i; + + printf( "bufferA = \n" ); + for( i=0; i<10; i++ ) { + bufferA[i] = (unsigned char)( + randSource->getRandomBoundedInt(0, 255) ); + printf( "%d\n", bufferA[i] ); + } + printf( "bufferB = \n" ); + for( i=0; i<15; i++ ) { + bufferB[i] = (unsigned char)( + randSource->getRandomBoundedInt(0, 255) ); + printf( "%d\n", bufferB[i] ); + } + + unsigned char *bufferC = new unsigned char[ 10 + 15]; + + + outStream->write( bufferA, 10 ); + outStream->write( bufferB, 15 ); + + inStream->read( bufferC, 10 + 10 ); + + char *error = inStream->getLastError(); + if( error != NULL ) { + printf( "Stream error %s\n", error ); + delete [] error; + } + printf( "bufferc = \n" ); + for( i=0; i<10 + 15; i++ ) { + printf( "%d\n", bufferC[i] ); + } + + inStream->read( bufferC, 10 ); + + error = inStream->getLastError(); + if( error != NULL ) { + printf( "Stream error %s\n", error ); + delete [] error; + } + printf( "bufferc = \n" ); + for( i=0; i<10 + 15; i++ ) { + printf( "%d\n", bufferC[i] ); + } + + + delete [] bufferA; + delete [] bufferB; + delete [] bufferC; + + delete randSource; + delete stream; + } diff --git a/minorGems/io/serialPort/SerialPort.h b/minorGems/io/serialPort/SerialPort.h new file mode 100644 index 0000000..5c187e8 --- /dev/null +++ b/minorGems/io/serialPort/SerialPort.h @@ -0,0 +1,108 @@ +/* + * Modification History + * + * 2003-February-17 Jason Rohrer + * Created. + * + * 2003-April-4 Jason Rohrer + * Added function for dumping the read buffer. + */ + + + +#ifndef SERIAL_PORT_INCLUDED +#define SERIAL_PORT_INCLUDED + + + +/** + * Serial port. + * + * Note: Implementation for the functions defined here is provided + * separately for each platform (in the mac/ linux/ and win32/ + * subdirectories). + * + * @author Jason Rohrer + */ +class SerialPort { + + + + public: + + + + static const int PARITY_NONE = 0; + static const int PARITY_EVEN = 1; + static const int PARITY_ODD = 2; + + + + /** + * Constructs a serial port. + * + * @param inBaud the baud rate. + * @param inParity the parity, one of SerialPort:: PARITY_NONE, + * PARITY_EVEN, or PARITY_ODD. + * @param inDataBits the number of data bits, 5, 6, 7, or 8. + * @param inStopBits the number of stop bits, 1 or 2. + */ + SerialPort( int inBaud, int inParity, int inDataBits, int inStopBits ); + + + + ~SerialPort(); + + + + /** + * Sends a line of text through this serial port. + * + * @param inLine the \0-terminated line of text to send. + * Should not contain newline characters. + * Must be destroyed by caller if non-const. + * + * @return 1 if the line was sent successfully, + * or -1 for a port error. + */ + int sendLine( char *inLine ); + + + + /** + * Receives a line of text from this serial port. + * + * @return the read line as a \0-terminated string with end of + * line characters included, or NULL for a port error. + * Must be destroyed by caller if non-NULL. + */ + char *receiveLine(); + + + + /** + * Discards all characters in the receive buffer, including + * unread characters. + * + * Can be used to recover from buffer overflow problems. + */ + void dumpReceiveBuffer(); + + + + private: + + + + /** + * Used for platform-specific implementations. + */ + void *mNativeObjectPointer; + + + + }; + + + +#endif diff --git a/minorGems/io/serialPort/SerialPortFromFile.cpp b/minorGems/io/serialPort/SerialPortFromFile.cpp new file mode 100644 index 0000000..9736b9c --- /dev/null +++ b/minorGems/io/serialPort/SerialPortFromFile.cpp @@ -0,0 +1,88 @@ +/* + * Modification History + * + * 2003-March-28 Jason Rohrer + * Created. + */ + + + +#include "minorGems/io/serialPort/SerialPort.h" +#include "minorGems/util/stringUtils.h" + + + +#include + + + +SerialPort::SerialPort( int inBaud, int inParity, int inDataBits, + int inStopBits ) { + + FILE *file = fopen( "gpscap.txt", "r" ); + + mNativeObjectPointer = file; + } + + + +SerialPort::~SerialPort() { + + if( mNativeObjectPointer != NULL ) { + FILE *file = (FILE *)mNativeObjectPointer; + + fclose( file ); + } + } + + + +int SerialPort::sendLine( char *inLine ) { + return 1; + } + + + +char *SerialPort::receiveLine() { + if( mNativeObjectPointer != NULL ) { + FILE *file = (FILE *)mNativeObjectPointer; + + char *buffer = new char[500]; + + // read up to first newline + int index = 0; + + char lastCharRead = (char)getc( file ); + + while( lastCharRead != '\n' && index < 499 ) { + buffer[index] = lastCharRead; + lastCharRead = (char)getc( file ); + index++; + } + + + char *returnString; + + if( index > 0 ) { + buffer[ index ] = '\0'; + + returnString = stringDuplicate( buffer ); + } + else { + returnString = NULL; + } + + delete [] buffer; + return returnString; + } + else { + return NULL; + } + } + + + + + + + diff --git a/minorGems/io/serialPort/linux/SerialPortLinux.cpp b/minorGems/io/serialPort/linux/SerialPortLinux.cpp new file mode 100644 index 0000000..c1ac44a --- /dev/null +++ b/minorGems/io/serialPort/linux/SerialPortLinux.cpp @@ -0,0 +1,277 @@ +/* + * Modification History + * + * 2003-February-17 Jason Rohrer + * Created. + * + * 2003-April-4 Jason Rohrer + * Added function for dumping the read buffer. + */ + + + +#include "minorGems/io/serialPort/SerialPort.h" +#include "minorGems/util/stringUtils.h" + + +// Much of the code in this implementation was copied +// from the Serial Programming FAQ, by Gary Frefking +// http://en.tldp.org/HOWTO/Serial-Programming-HOWTO/ + + + +#include +#include +#include +#include +#include +#include +// baudrate settings are defined in , which is +// included by + + + +class LinuxSerialPortObject { + + public: + int mFileHandle; + struct termios mOldTermIO; + }; + + + +SerialPort::SerialPort( int inBaud, int inParity, int inDataBits, + int inStopBits ) { + + int fileHandle = open( "/dev/ttyS0", O_RDWR | O_NOCTTY ); + + if( fileHandle < 0 ) { + + mNativeObjectPointer = NULL; + } + else { + + LinuxSerialPortObject *serialPortObject = new LinuxSerialPortObject(); + serialPortObject->mFileHandle = fileHandle; + + mNativeObjectPointer = (void *)serialPortObject; + + + struct termios newTermIO; + + //save current serial port settings + tcgetattr( fileHandle, &( serialPortObject->mOldTermIO ) ); + + // clear struct for new port settings + bzero( &newTermIO, sizeof( newTermIO ) ); + + unsigned int baudRate; + + switch( inBaud ) { + case 1200: + baudRate = B1200; + break; + case 2400: + baudRate = B2400; + break; + case 4800: + baudRate = B4800; + break; + case 9600: + baudRate = B9600; + break; + case 19200: + baudRate = B19200; + break; + case 38400: + baudRate = B38400; + break; + case 57600: + baudRate = B57600; + break; + default: + break; + } + + unsigned int parity = 0; + + switch( inParity ) { + case SerialPort::PARITY_NONE: + // disable parity + parity = 0; + break; + case SerialPort::PARITY_EVEN: + // enable parity, defaults to even + parity = PARENB; + break; + case SerialPort::PARITY_ODD: + // enable parity, and set to odd + parity = PARENB | PARODD; + break; + default: + break; + } + + unsigned int dataBits = 0; + switch( inDataBits ) { + case 5: + dataBits = CS5; + break; + case 6: + dataBits = CS6; + break; + case 7: + dataBits = CS7; + break; + case 8: + dataBits = CS8; + break; + default: + break; + } + + unsigned int stopBits = 0; + switch( inStopBits ) { + case 1: + stopBits = 0; + break; + case 2: + stopBits = CSTOPB; + break; + default: + break; + } + + newTermIO.c_cflag = baudRate | parity | dataBits | + stopBits | CLOCAL | CREAD; + + /* + IGNPAR : ignore bytes with parity errors + ICRNL : map CR to NL (otherwise a CR input on the other computer + will not terminate input) + IGNCR : ignore CR, so only one line is read when other + machine sends CRLF + otherwise make device raw (no other input processing) + */ + newTermIO.c_iflag = IGNPAR | ICRNL | IGNCR; + + /* + Raw output. + */ + newTermIO.c_oflag = 0; + + /* + ICANON : enable canonical input + disable all echo functionality, and don't send signals to + calling program + */ + newTermIO.c_lflag = ICANON; + + /* + now clean the modem line and activate the settings for the port + */ + tcflush( fileHandle, TCIFLUSH ); + tcsetattr( fileHandle, TCSANOW, &newTermIO ); + } + } + + + +SerialPort::~SerialPort() { + + if( mNativeObjectPointer != NULL ) { + LinuxSerialPortObject *serialPortObject = + (LinuxSerialPortObject *)mNativeObjectPointer; + + int fileHandle = serialPortObject->mFileHandle; + + + tcsetattr( fileHandle, TCSANOW, &( serialPortObject->mOldTermIO ) ); + + + delete serialPortObject; + + } + } + + + +int SerialPort::sendLine( char *inLine ) { + if( mNativeObjectPointer != NULL ) { + LinuxSerialPortObject *serialPortObject = + (LinuxSerialPortObject *)mNativeObjectPointer; + int fileHandle = serialPortObject->mFileHandle; + + int stringLength = strlen( inLine ); + int numWritten = write( fileHandle, inLine, stringLength ); + + char *endLineString = "\n"; + + int numWrittenEndLine = write( fileHandle, endLineString, 1 ); + + if( numWritten == stringLength && + numWrittenEndLine == 1 ) { + return 1; + } + else { + return -1; + } + } + else { + return -1; + } + } + + + +char *SerialPort::receiveLine() { + if( mNativeObjectPointer != NULL ) { + LinuxSerialPortObject *serialPortObject = + (LinuxSerialPortObject *)mNativeObjectPointer; + int fileHandle = serialPortObject->mFileHandle; + + char *buffer = new char[500]; + + int numRead = read( fileHandle, buffer, 500 ); + + char *returnString; + + if( numRead != -1 ) { + buffer[ numRead ] = '\0'; + + returnString = stringDuplicate( buffer ); + } + else { + returnString = NULL; + } + + delete [] buffer; + return returnString; + } + else { + return NULL; + } + } + + + +void SerialPort::dumpReceiveBuffer() { + if( mNativeObjectPointer != NULL ) { + LinuxSerialPortObject *serialPortObject = + (LinuxSerialPortObject *)mNativeObjectPointer; + int fileHandle = serialPortObject->mFileHandle; + + // from man page: + // flushes data received but not read + tcflush( fileHandle, TCIFLUSH ); + } + } + + + + + + + + + diff --git a/minorGems/io/serialPort/testSerialPort.cpp b/minorGems/io/serialPort/testSerialPort.cpp new file mode 100644 index 0000000..e1a734b --- /dev/null +++ b/minorGems/io/serialPort/testSerialPort.cpp @@ -0,0 +1,27 @@ + +#include "SerialPort.h" + +#include + +int main() { + + printf( "Constructing serial port.\n" ); + SerialPort *port = new SerialPort( 4800, SerialPort::PARITY_NONE, 8, 1 ); + + char *line = port->receiveLine(); + + // specific to GPS unit + port->sendLine( "ASTRAL" ); + + while( line != NULL ) { + printf( "received: %s\n", line ); + + delete [] line; + line = port->receiveLine(); + } + + printf( "Deleting serial port.\n" ); + delete port; + + return 0; + } diff --git a/minorGems/io/serialPort/testSerialPortCompile b/minorGems/io/serialPort/testSerialPortCompile new file mode 100755 index 0000000..2370947 --- /dev/null +++ b/minorGems/io/serialPort/testSerialPortCompile @@ -0,0 +1 @@ +g++ -o testSerialPort -I../../.. linux/SerialPortLinux.cpp testSerialPort.cpp \ No newline at end of file diff --git a/minorGems/io/serialPort/win32/COMPort.cpp b/minorGems/io/serialPort/win32/COMPort.cpp new file mode 100644 index 0000000..f06a8a0 --- /dev/null +++ b/minorGems/io/serialPort/win32/COMPort.cpp @@ -0,0 +1,441 @@ +/* + * Modification History + * + * 2003-April-4 Jason Rohrer + * Added function for dumping the read buffer. + */ + +//============================================================================= +// General component library for WIN32 +// Copyright (C) 2000, UAB BBDSoft ( http://www.bbdsoft.com/ ) +// +// This material is provided "as is", with absolutely no warranty expressed +// or implied. Any use is at your own risk. +// +// Permission to use or copy this software for any purpose is hereby granted +// without fee, provided the above notices are retained on all copies. +// Permission to modify the code and to distribute modified code is granted, +// provided the above notices are retained, and a notice that the code was +// modified is included with the above copyright notice. +// +// The author of this program may be contacted at developers@bbdsoft.com +//============================================================================= + + +#ifndef _COMPORT_ + #include "ComPort.h" +#endif + +#ifndef _WINDOWS_ + #define WIN32_LEAN_AND_MEAN + #include +#endif + +#ifndef _STDEXCEPT_ + #include +#endif + +using namespace std; + +//---------------------------------------------------------------------------- +COMPort::COMPort ( const char * const portName ) + : theDCB (NULL) +{ + +thePortHandle = (unsigned ) CreateFile ( portName + , GENERIC_READ | GENERIC_WRITE + , 0 + , NULL + , OPEN_EXISTING + , FILE_FLAG_NO_BUFFERING + , NULL + ); +if (thePortHandle == HFILE_ERROR) +{ + throw runtime_error ("COMPort: failed to open."); +} // endif + +theDCB = new char [sizeof(DCB)]; +getState(); +setBlockingMode(); +setHandshaking(); + +} // end constructor + + +//---------------------------------------------------------------------------- +COMPort::~COMPort() +{ + +delete [] theDCB; + +// close serial port device +if (CloseHandle ((HANDLE)thePortHandle) == FALSE ) +{ + throw runtime_error ("COMPort: failed to close."); +} // endif + +} // end destructor + + +//---------------------------------------------------------------------------- +void COMPort::getState () const +{ + +if (!GetCommState ( (HANDLE) thePortHandle + , (LPDCB) theDCB + ) + ) +{ + throw runtime_error ("COMPort: could not retrieve serial port state."); +} // endif + +} // end COMPort::getState () const + + +//---------------------------------------------------------------------------- +COMPort& COMPort::setState () +{ + +if (!SetCommState ( (HANDLE) thePortHandle + , (LPDCB) theDCB + ) + ) +{ + throw runtime_error ("COMPort: could not modify serial port state."); +} // endif + +return *this; +} // end COMPort::setState () + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setBitRate ( unsigned long Param ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +aDCB.BaudRate = Param; + +return setState(); +} // end COMPort::setBitRate (..) + + +//----------------------------------------------------------------------------- +unsigned long COMPort::bitRate() const +{ + +DCB & aDCB = *((LPDCB)theDCB); + +return aDCB.BaudRate; +} // end COMPort::bitRate () const + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setLineCharacteristics( char * inConfig ) +{ + +COMMTIMEOUTS aTimeout; +if ( !BuildCommDCBAndTimeouts ( inConfig + , (LPDCB)theDCB + , &aTimeout + ) + ) +{ + throw runtime_error ("COMPort: could not set line characteristics."); +} // endif + +if ( ! SetCommTimeouts ( (HANDLE(thePortHandle)) + , &aTimeout + ) + ) +{ + throw runtime_error ("COMPort: could not set line characteristics."); +} // endif + +return setState(); +} + + +//---------------------------------------------------------------------------- +char COMPort::read () +{ + +char buffer; +DWORD charsRead = 0; + +do +{ + if (! ReadFile ( (HANDLE(thePortHandle)) + , &buffer + , sizeof(char) + , &charsRead + , NULL + ) + ) + { + throw runtime_error ("COMPort: read failed."); + } // endif + +} while ( !charsRead ); + +return buffer; +} // end COMPort::read() + + +//---------------------------------------------------------------------------- +void COMPort::dumpReceiveBuffer () +{ + PurgeComm( (HANDLE(thePortHandle)) + , PURGE_RXCLEAR + ); +} // end COMPort::dumpReceiveBuffer() + + +//---------------------------------------------------------------------------- +unsigned long COMPort::read ( void *inBuffer + , const unsigned long inCharsReq + ) +{ + +DWORD charsRead = 0; +if ( !ReadFile ( (HANDLE(thePortHandle)) + , inBuffer + , inCharsReq + , &charsRead + , NULL + ) + ) +{ + throw runtime_error ("COMPort: read failed."); +} // endif + +return charsRead; +} // end COMPort::read (..) + + +//---------------------------------------------------------------------------- +COMPort & COMPort::write ( const char inChar ) +{ + +char buffer = inChar; +DWORD charsWritten = 0; + +if ( !WriteFile ( (HANDLE(thePortHandle)) + , &buffer + , sizeof(char) + , &charsWritten + , NULL + ) + ) +{ + throw runtime_error ("COMPort: write failed."); +} // endif + +return *this; +} // end COMPort::write (..) + + +//---------------------------------------------------------------------------- +unsigned long COMPort::write ( const void *inBuffer + , const unsigned long inBufSize + ) +{ + +DWORD charsWritten = 0; + +if ( !WriteFile ( (HANDLE(thePortHandle)) + , inBuffer + , inBufSize + , &charsWritten + , NULL + ) + ) +{ + throw runtime_error ("COMPort: write failed."); +} // endif + +return charsWritten; +} // end COMPort::write() + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setxONxOFF ( bool Param ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +aDCB.fOutX = Param ? 1 : 0; +aDCB.fInX = Param ? 1 : 0; + +return setState(); +} // end COMPort::setxONxOFF (..) + + +//----------------------------------------------------------------------------- +bool COMPort::isxONxOFF () const +{ + +DCB & aDCB = *((LPDCB)theDCB); + +return (aDCB.fOutX && aDCB.fInX); +} // end COMPort::isxONxOFF () const + + +//---------------------------------------------------------------------------- +COMPort& COMPort::setBlockingMode ( unsigned long inReadInterval + , unsigned long inReadMultiplyer + , unsigned long inReadConstant + ) +{ + +COMMTIMEOUTS commTimeout; +if ( !GetCommTimeouts ( (HANDLE(thePortHandle)) + , &commTimeout + ) + ) +{ + throw runtime_error ("COMPort: failed to retrieve timeouts."); +} // endif + +commTimeout.ReadIntervalTimeout = inReadInterval; + +if ( inReadInterval==MAXDWORD ) +{ + commTimeout.ReadTotalTimeoutMultiplier = 0; + commTimeout.ReadTotalTimeoutConstant = 0; +} +else +{ + commTimeout.ReadTotalTimeoutMultiplier = inReadMultiplyer; + commTimeout.ReadTotalTimeoutConstant = inReadConstant; +} // endifelse + +if ( !SetCommTimeouts ( (HANDLE(thePortHandle)) + , &commTimeout + ) + ) +{ + throw runtime_error ("COMPort: failed to modify timeouts."); +} // endif + +return *this; +} // end COMPort::setBlockingMode (..) + + +//----------------------------------------------------------------------------- +COMPort & COMPort::setHandshaking ( bool inHandshaking ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +if (inHandshaking) +{ + aDCB.fOutxCtsFlow = TRUE; + aDCB.fOutxDsrFlow = FALSE; + aDCB.fRtsControl = RTS_CONTROL_HANDSHAKE; +} +else +{ + aDCB.fOutxCtsFlow = FALSE; + aDCB.fOutxDsrFlow = FALSE; + aDCB.fRtsControl = RTS_CONTROL_ENABLE; +} // endifelse + +return setState(); +} // end COMPort::setHandshaking (..) + + +//----------------------------------------------------------------------------- +unsigned long COMPort::getMaximumBitRate() const +{ + +COMMPROP aProp; +if ( !GetCommProperties ( (HANDLE)thePortHandle + , &aProp ) + ) +{ + throw runtime_error ("COMPort: failed to retrieve port properties."); +} // endif + +return aProp.dwMaxBaud; +} // end COMPort::getMaximumBitRate () const + + +//----------------------------------------------------------------------------- +COMPort::MSPack COMPort::getModemSignals() const +{ + +MSPack aPack; +// 1 bit - DTR, 2 - bit RTS (output signals) +// 4 bit - CTS, 5 bit - DSR, 6 bit - RI, 7 bit - DCD (input signals) +if ( !GetCommModemStatus ( (HANDLE)thePortHandle + , (LPDWORD)&aPack ) + ) +{ + throw runtime_error ("COMPort: failed to retrieve modem signals."); +} // endif + +return aPack; +} // end COMPort::getModemSignals () const + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setParity ( Parity Param ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +aDCB.Parity = Param; + +return setState(); +} // end COMPort::setParity (..) + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setDataBits ( DataBits Param ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +aDCB.ByteSize = Param; + +return setState(); +} // end COMPort::setDataBits (..) + + +//----------------------------------------------------------------------------- +COMPort& COMPort::setStopBits ( StopBits Param ) +{ + +DCB & aDCB = *((LPDCB)theDCB); +aDCB.StopBits = Param; + +return setState(); +} // end COMPort::setStopBits (..) + + +//----------------------------------------------------------------------------- +COMPort::Parity COMPort::parity () const +{ + +DCB & aDCB = *((LPDCB)theDCB); + +return (COMPort::Parity)aDCB.Parity; +} // end COMPort::parity () const + + +//----------------------------------------------------------------------------- +COMPort::DataBits COMPort::dataBits () const +{ + +DCB & aDCB = *((LPDCB)theDCB); + +return (COMPort::DataBits)aDCB.ByteSize; +} // end COMPort::dataBits () const + + +//----------------------------------------------------------------------------- +COMPort::StopBits COMPort::stopBits () const +{ + +DCB & aDCB = *((LPDCB)theDCB); + +return (COMPort::StopBits)aDCB.StopBits; +} // end COMPort::stopBits () cosnt + diff --git a/minorGems/io/serialPort/win32/COMPort.h b/minorGems/io/serialPort/win32/COMPort.h new file mode 100644 index 0000000..a0ad455 --- /dev/null +++ b/minorGems/io/serialPort/win32/COMPort.h @@ -0,0 +1,150 @@ +/* + * Modification History + * + * 2003-April-4 Jason Rohrer + * Added function for dumping the read buffer. + */ + +//============================================================================= +// General component library for WIN32 +// Copyright (C) 2000, UAB BBDSoft ( http://www.bbdsoft.com/ ) +// +// This material is provided "as is", with absolutely no warranty expressed +// or implied. Any use is at your own risk. +// +// Permission to use or copy this software for any purpose is hereby granted +// without fee, provided the above notices are retained on all copies. +// Permission to modify the code and to distribute modified code is granted, +// provided the above notices are retained, and a notice that the code was +// modified is included with the above copyright notice. +// +// The author of this program may be contacted at developers@bbdsoft.com +//============================================================================= + + +#ifndef _COMPORT_ +#define _COMPORT_ + +//----------------------------------------------------------------------------- +class COMPort +{ +public: + + enum Parity + { + None = 0 + , Odd + , Even + , Mark + , Space + }; + + enum DataBits + { + db4 = 4 + , db5 + , db6 + , db7 + , db8 + }; + + enum StopBits + { + sb1 = 0, + sb15, + sb2 + }; + + enum BitRate + { + br110 = 110, + br300 = 300, + br600 = 600, + br1200 = 1200, + br2400 = 2400, + br4800 = 4800, + br9600 = 9600, + br19200 = 19200, + br38400 = 38400, + br56000 = 56000, + br57600 = 57600, + br115200 = 115200, + br256000 = 256000 + }; + + + // for function getModemSignals + struct MSPack + { + unsigned char DTR : 1; + unsigned char RTS : 1; + unsigned char : 2; + unsigned char CTS : 1; + unsigned char DSR : 1; + unsigned char RI : 1; + unsigned char DCD : 1; + }; + + COMPort ( const char * const portName ); + ~COMPort (); + + // I/O operations + char read (); + COMPort & write (const char inChar); + + unsigned long read ( void * + , const unsigned long count + ); + + unsigned long write ( const void * + , const unsigned long count + ); + // dumps unread characters in the receive buffer + void dumpReceiveBuffer(); + + + COMPort& setBitRate ( unsigned long Param ); + unsigned long bitRate () const; + + COMPort& setParity ( Parity Param ); + Parity parity () const; + + COMPort& setDataBits ( DataBits Param ); + DataBits dataBits () const; + + COMPort& setStopBits ( StopBits Param ); + StopBits stopBits () const; + + COMPort & setHandshaking ( bool inHandshaking = true ); + + COMPort& setLineCharacteristics ( char * Param ); + + unsigned long getMaximumBitRate () const; + + COMPort & setxONxOFF ( bool Param = true); + bool isxONxOFF () const; + + MSPack getModemSignals () const; + + COMPort& setBlockingMode ( unsigned long inReadInterval = 0 + , unsigned long inReadMultiplyer = 0 + , unsigned long inReadConstant = 0 + ); + +protected: + +private: + + // disable copy constructor and assignment operator + COMPort (const COMPort &); + COMPort& operator= (const COMPort &); + + void getState () const; + COMPort& setState (); + + unsigned thePortHandle; + char * theDCB; + +}; // End of COMPort class declaration + +#endif diff --git a/minorGems/io/serialPort/win32/SerialPortWin32.cpp b/minorGems/io/serialPort/win32/SerialPortWin32.cpp new file mode 100644 index 0000000..05efc3b --- /dev/null +++ b/minorGems/io/serialPort/win32/SerialPortWin32.cpp @@ -0,0 +1,199 @@ +/* + * Modification History + * + * 2003-March-24 Jason Rohrer + * Created. + * + * 2003-March-26 Jason Rohrer + * Fixed a line parsing bug (lines end with newline, not CR). + * + * 2003-April-4 Jason Rohrer + * Added function for dumping the read buffer. + */ + + + +#include "minorGems/io/serialPort/SerialPort.h" +#include "minorGems/util/stringUtils.h" + + +// For now, we are using BBDSoft's COM port code +#include "COMPort.h" + + + +SerialPort::SerialPort( int inBaud, int inParity, int inDataBits, + int inStopBits ) { + + COMPort *port = new COMPort( "COM1" ); + port->setHandshaking( false ); + + unsigned long baudRate = COMPort::br2400; + + switch( inBaud ) { + case 1200: + baudRate = COMPort::br1200; + break; + case 2400: + baudRate = COMPort::br2400; + break; + case 4800: + baudRate = COMPort::br4800; + break; + case 9600: + baudRate = COMPort::br9600; + break; + case 19200: + baudRate = COMPort::br19200; + break; + case 38400: + baudRate = COMPort::br38400; + break; + case 57600: + baudRate = COMPort::br57600; + break; + default: + break; + } + + port->setBitRate( baudRate ); + + switch( inParity ) { + case SerialPort::PARITY_NONE: + port->setParity( COMPort::None ); + break; + case SerialPort::PARITY_EVEN: + port->setParity( COMPort::Even ); + break; + case SerialPort::PARITY_ODD: + port->setParity( COMPort::Odd ); + break; + default: + port->setParity( COMPort::None ); + break; + } + + switch( inDataBits ) { + case 5: + port->setDataBits( COMPort::db5 ); + break; + case 6: + port->setDataBits( COMPort::db6 ); + break; + case 7: + port->setDataBits( COMPort::db7 ); + break; + case 8: + port->setDataBits( COMPort::db8 ); + break; + default: + port->setDataBits( COMPort::db8 ); + break; + } + + switch( inStopBits ) { + case 1: + port->setStopBits( COMPort::sb1 ); + break; + case 2: + port->setStopBits( COMPort::sb2 ); + break; + default: + port->setStopBits( COMPort::sb1 ); + break; + } + + mNativeObjectPointer = (void *)port; + } + + + +SerialPort::~SerialPort() { + + if( mNativeObjectPointer != NULL ) { + COMPort *port = (COMPort *)mNativeObjectPointer; + delete port; + } + } + + + +int SerialPort::sendLine( char *inLine ) { + if( mNativeObjectPointer != NULL ) { + COMPort *port = (COMPort *)mNativeObjectPointer; + + int stringLength = strlen( inLine ); + int numWritten = port->write( (void *)inLine, stringLength ); + + char *endLineString = "\n"; + + int numWrittenEndLine = port->write( (void *)endLineString, 1 ); + + if( numWritten == stringLength && + numWrittenEndLine == 1 ) { + return 1; + } + else { + return -1; + } + } + else { + return -1; + } + } + + + +char *SerialPort::receiveLine() { + if( mNativeObjectPointer != NULL ) { + COMPort *port = (COMPort *)mNativeObjectPointer; + + char *buffer = new char[500]; + + // read up to first newline + int index = 0; + + char lastCharRead = port->read(); + + while( lastCharRead != '\n' && index < 499 ) { + buffer[index] = lastCharRead; + lastCharRead = port->read(); + index++; + } + + + char *returnString; + + if( index > 0 ) { + buffer[ index ] = '\0'; + + returnString = stringDuplicate( buffer ); + } + else { + returnString = NULL; + } + + delete [] buffer; + return returnString; + } + else { + return NULL; + } + } + + + +void SerialPort::dumpReceiveBuffer() { + if( mNativeObjectPointer != NULL ) { + COMPort *port = (COMPort *)mNativeObjectPointer; + + port->dumpReceiveBuffer(); + } + } + + + + + + + diff --git a/minorGems/io/win32/TypeIOWin32.cpp b/minorGems/io/win32/TypeIOWin32.cpp new file mode 100644 index 0000000..df1b253 --- /dev/null +++ b/minorGems/io/win32/TypeIOWin32.cpp @@ -0,0 +1,60 @@ +/* + * Modification History + * + * 2001-February-3 Jason Rohrer + * Created. + * Fixed parameter names to match convention. + * + * 2001-February-4 Jason Rohrer + * Fixed a byte-order bug. + */ + + +#include "minorGems/io/TypeIO.h" + + +/* + * Win32-specific type input and output. + * Note that all types are output in the order that + * a big-endian linux machine outputs them with no conversion. + */ + +// windows machines are all little-endian + +void TypeIO::doubleToBytes( double inDouble, unsigned char *outBytes ) { + + unsigned char *doubleBuffer = (unsigned char*)( &inDouble ); + + // output second word first + outBytes[0] = doubleBuffer[7]; + outBytes[1] = doubleBuffer[6]; + outBytes[2] = doubleBuffer[5]; + outBytes[3] = doubleBuffer[4]; + + outBytes[4] = doubleBuffer[3]; + outBytes[5] = doubleBuffer[2]; + outBytes[6] = doubleBuffer[1]; + outBytes[7] = doubleBuffer[0]; + } + + + +double TypeIO::bytesToDouble( unsigned char *inBytes ) { + + double returnValue; + + unsigned char *doubleBuffer = (unsigned char*)( &returnValue ); + + // put first word at the end of this double + doubleBuffer[7] = inBytes[0]; + doubleBuffer[6] = inBytes[1]; + doubleBuffer[5] = inBytes[2]; + doubleBuffer[4] = inBytes[3]; + + doubleBuffer[3] = inBytes[4]; + doubleBuffer[2] = inBytes[5]; + doubleBuffer[1] = inBytes[6]; + doubleBuffer[0] = inBytes[7]; + + return returnValue; + } diff --git a/minorGems/numtest.cpp b/minorGems/numtest.cpp new file mode 100644 index 0000000..f4bfb19 --- /dev/null +++ b/minorGems/numtest.cpp @@ -0,0 +1,49 @@ +/* + * Modification History + * + * 2001-February-3 Jason Rohrer + * Created. + * + * 2001-February-4 Jason Rohrer + * Fixed a bug that made this test not compatible with TypeIO. + * Fixed comment. + */ + +#include + +// function for testing how doubles are stored + +/* + * The output from a big-endian linux is as follows: + * + * size of double = 8 + * byte 0 = 63 + * byte 1 = 255 + * byte 2 = 189 + * byte 3 = 137 + * byte 4 = 176 + * byte 5 = 126 + * byte 6 = 158 + * byte 7 = 168 + * + */ + +int main() { + printf( "size of double = %d\n", sizeof( double ) ); + + + + + + double x = 1.983773889; + + + unsigned char *doubleBuffer = (unsigned char*)( &x ); + + for( int i=0; i<8; i++ ) { + + printf( "byte %d = %d\n", i, doubleBuffer[i] ); + } + + return 0; + } diff --git a/minorGems/protocol/p2p/anonymousPublishing.txt b/minorGems/protocol/p2p/anonymousPublishing.txt new file mode 100644 index 0000000..e135897 --- /dev/null +++ b/minorGems/protocol/p2p/anonymousPublishing.txt @@ -0,0 +1,196 @@ +November 8, 2002 + +Ideas for low-overhead anonymous publishing. + +Freenet problem: +In a system where a data retrieval can take up to 10 network hops, redirects +are rediculous (10 hops to find the pointer file, 10 more hops to find +the actual data). In Freenet, since SSKs are common, redirects are used +for almost every piece of data in the system. + +From the start, the network should be designed to handle the most common +usage scenareo (fetching the latest version of an anonymous author's content), +not to handle all sorts of other possible scenareos. + +The goal of such a system should be usable anonymous publication and +retrieval. We should not focus (as Freenet does) on side issues such as +retrieval of past document versions or elimination of data redundancy. + + + +Simplest solution: + +Sign(K,D) signs data D with key K. +Encr(K,D) encrypts data D with key K. + +Hash(D) produces a fixed-length hash of data D. + + +A | B concatonates A and B. + + +An anonymous author generates a private key R and a public key U. + +The author wants to post a subspace file "index.html", with content C and +timestamp T. + +The author generates a random symmetric key K. + +The author generates the following pointer URL: + +K/U/index.html + + +The author generates the following insertion data: + +T | Encr(K,C) | Sign( U, T | Encr(K,C) ) + + +The author generates the following insertion URL: + +U/index.html + + +Problems: U, the author's public key, is visible in the insertion URL. Thus, +nodes can selectively block particular authors. The second problem is +that the "file name" is visible in the insertion URL, allowing nodes to +selectively block certain files. + + +What if the insertion URL looks like this: +Hash(U) + Hash("index.html") + +Problem: +There is no way for storage nodes to verify that the inserted data actually +matches the insertion URL. + + +We have several reasons for assuming that we have one priv/pub key pair per +author: + +1. This allows authors to build an anonymous publishing identity and +repuation, since all URLs that contain a particular U certainly correspond +to data posted by a particular author. + +2. Generating priv/pub key pairs is computationally expensive, so we +want to do this as infrequently as possible. + +3. Authors only need to manage one priv/pub key pair to publish in the +network. + + +Thus, given all of the assumptions and issues raised above, we can see why +the Freenet paradigm uses redirects. + +However, we should note the following points about this paradigm: + +1. Using redirects *doubles* data fetch time in the worst case. + +2. Generating key pairs, though expensive, only takes a few seconds on +modern computing hardware. + +3. Freenet data fetch times, in practice, are take tens of seconds. + +4. Each data post operation may be followed by many fetch operations for +that data. + +5. Readers, in general, do not compare the public keys in a URLs to judge +whether or not two pieces of content were posted by the same author. They +generally look at whether or not the two content "links" were grouped together +on the same "homepage". I.e., they assume that all data grouped together +into a "freesite" was posted by one author. (This is a fair assumption to make, +since only one author posted the main page of the freesite, so it is safe +to assume that the author chose to group the content links together.) + + + +Dropping the assumtions about keys stated earlier, we can devise a publishing +mechanism with better properties: + + +An anonymous author wants to post a new file "index.html", with content C and +timestamp T. + +The author generates a new private key R and a public key U. + +The author generates a random symmetric key K. + +The author generates the following pointer URL: + +K/U + + +The author generates the following insertion data: + +T | Encr(K,C) | Sign( U, T | Encr(K,C) ) + + +The author generates the following insertion URL: + +U + + +Whenever the author wants to post an updated version of this file, s/he uses +the same R/U pair, inserting new content C' with a new timestamp T'. Storage +nodes can check timestamps for key collisions and keep only the latest version +of a content unit. + + +This scheme has the following properties: + +1. Storage nodes can verify that content matches a key by checking the +signature included with the content. + +2. Storage nodes can obtain a secure timestamp for each unit of content. + +3. Storage nodes do not have access to unencrypted content. + +4. Storage nodes cannot tie a piece of content to any particular author. + +3. Readers, using the K/U URL form, can fetch the content (using U), verify +its signature and timestamp, and decrypt it (using K). + + + +We might observe that, with this scheme, readers have lost the ability to +associate a collection of content with a particular author, since each +unit of content is signed with a different private key. + +However, we propose the following simple solution to this problem: + +Authors can insert a "collection" document with links to all of their work. +This is similar to a homepage on the web. + +Since an author can securely update and maintain their collection document, +readers can be sure that all of the work pointed to by the collection document +was actually collected by the same author. + +Note that with *any* scheme, there is no way to guarentee authorship, so +nothing really is lost here. With Freenet, all we can know for sure is that +the same person *posted* each of a particular series of documents. In our +system, we know for sure only that the same person *linked to* each of a +particular series of documents. Since there is nothing all that sacred about +posting a document, we claim that nothing sacred is lost. + + +However, for each document posted, a new priv/pub key pair must be generated. +This can be computationally expensive and time-consuming for a poster. To +deal with this issue, each node can build a collection of fresh priv/pub key +pairs using spare computation cycles (or a thread with low priority) so that +fresh keys are ready whenever an author wishes to publish new content. + + +We should note the following trade-offs: + +We gain: +The ability to publish and retrieve content securely and anonymously without +using redirects, while still allowing for reputation building and anonymous +identity. + +We lose: +The ability to factor redundant content out of the network. + + +However, the only want to factor redundant content out of the network while +still allowing for high-level pointers to content is by using redirects +(a high-level pointer that redirects you to a low-level, content hash key). diff --git a/minorGems/protocol/p2p/genericProtocol.txt b/minorGems/protocol/p2p/genericProtocol.txt new file mode 100644 index 0000000..fbde6a1 --- /dev/null +++ b/minorGems/protocol/p2p/genericProtocol.txt @@ -0,0 +1,88 @@ +Generic peer-to-peer protocol. + +White space: +All white space is equivalent (tabs, line breaks, spaces, etc.) + + +Two types of commands, GET and PUT. + +Command form: +GET return_address id_number resource_descriptor +PUT id_number resource + + +return_address = {xxx.xxx.xxx.xxx | hostname}:port_number + +resource_descriptor = resource_type [description] + + +description = resource specific description data may be optional for certain resource types + + +resource = resource_type resource_data + + +A PUT command corresponding to a GET command (in other words, a put command sharing the same ID as the GET command) can be of one of three types: + +1. A resource matching the resource descriptor specified by the GET command. +2. A GET_REQUEST_FAILURE resource. +3. A MORE_KNOWLEDGEABLE_HOSTS resource. + + + +Examples: + +See resourceSpecifications.txt for information about the resource types used in these examples. + + +A GET request for a server list: + +GET myhost.mydomain.com:5157 1 SERVER_LIST + + +The corresponding PUT command: + +PUT 1 SERVER_LIST 3 server1.domain1.com:5157 server2.domain2.com:5157 server3.domain3.com:5157 + + + +A GET request for a search: + +GET myhost.mydomain.com:5157 2 SEARCH 1 FILE 3 termA termB termC + + +A PUT command from a successful search: + +PUT 2 SEARCH 2 FILE filehost1.domain1.com:5157 c:/files/termAtermBtermC.txt FILE filehost2.domain2.com:5157 /home/john/myfiles/termAtermBtermC.txt + + +A PUT command for a failed search: + +PUT 2 GET_REQUEST_FAILED SEARCH 1 FILE 3 termA termB termC + + +A PUT command informing us of a more knowledgeable search host: + +PUT 2 MORE_KNOWLEDGEABLE_HOSTS 1 searchhost.domain.com:5157 + + + +We can request more knowlegeable hosts explicitly: + +GET myhost.mydomain.com:5157 3 MORE_KNOWLEDGEABLE_HOSTS SEARCH 1 FILE 3 termA termB termC + + +The corresponding PUT command: + +PUT 3 MORE_KNOWLEDGEABLE_HOSTS 1 searchhost.domain.com:5157 + + + +A GET request for a file + +GET myhost.mydomain.com:5157 4 FILE filehost1.domain1.com:5157 c:/files/termAtermBtermC.txt + + +The corresponding PUT command: + +PUT 4 FILE c:/files/termAtermBtermC.txt 19 ###this is a text file diff --git a/minorGems/protocol/p2p/motivationEmail.txt b/minorGems/protocol/p2p/motivationEmail.txt new file mode 100644 index 0000000..0869c2a --- /dev/null +++ b/minorGems/protocol/p2p/motivationEmail.txt @@ -0,0 +1,34 @@ +Greetings, + +I had a bit of correspondence with you about a year ago concerning "konspire", a distributed file sharing system that I was developing. As you may be aware, konspire never caught on. konspire failed to be popular for several reasons, but the major reason was that it was written using Java and had no natively compiled client or server available. Most people don't like to mess with getting a Java application up and running. Another problem was ease of use: konspire didn't work immediately "out of the box", as users had to enter the address of a live server the first time they ran konspire. Finally, there was a problem that never rose to the surface, which was scalability: had konspire become popular, the indexing scheme being used would have limited the network to about 100,000 files. + +When I was developing konspire, I was focusing on "complete searching" capabilities. When you got search results back using konspire, you could be certain that you were seeing a list of all files in the system that matched your search. With Gnutella, results trickled back over time, and you were never sure when they would stop arriving. With Napster, you could only search through files on one of their sub-servers. + +Of course, both Gnutella and Napster scale very well: in theory, the number of nodes in the network has no limit (though you can only search through a fraction of the available files). + +With konspire, each designated "server" node keeps a complete copy of the system-wide file index. The size of the index is bounded by the size of the memory available to the most memory-limited server. Thus, konspire does not scale well at all. Furthermore, even if the limit is not hit, who wants to donate their system's entire memory to the konspire file index? Add the 20MiB+ base memory requirements of Java to this, and konspire servers are major memory hogs. + +For a while, plans for konspire v1.2 were in place, and the new protocol would attempt to fix the scaling problems by splitting each copy of the index among a cluster of server nodes. Keeping things organized gets very messy, though. In the mean time, much simpler systems like Gnutella started to work well, once they had a larger consistent user base and a more connected network. In fact, you can find almost anything these days using a BearShare client... why bother with a smarter protocol? + +FastTrack improved the scalability with ideas similar to those of konspire v1.2, but they threw in a nice self-organization concept: self-organization frees novice users from making decisions (many users were endlessly confused about the difference between clients and servers in konspire). FastTrack has a huge problem, though: they are commercial and proprietary. You would think that these companies would learn their lesson from the Napster debacle. Do they think the RIAA will look the other way? Notice that the RIAA has not even touched Gnutella, since they have no one to point a suit at. + +The FastTrack protocol (supposedly) is also very specific to the way FastTrack organizes a network. Network organization and peer communication protocol seem like they could be completely separated. The kind of messages sent in a Napster network are pretty much the same as the kind of messages sent in a Gnutella network. The differences lie in how nodes behave and what they do upon receipt of a particular message. Think of a search request message: Gnutella nodes search themselves and pass the request on to other nodes; FastTrack nodes pass the request on to super-nodes, who pass the request among themselves. Why do the messages and protocols for a search request need to be different for Gnutella and FastTrack? In fact, Gnutella nodes and FastTrack nodes could be joined into one common network if they only used the same protocol. + +In fact, even a network that differs from Gnutella as much as Freenet could use the same communication protocol. Gnutella has download requests as well as search requests. Freenet in essence only has download requests. When a Gnutella node receives a DL request, it sends back the file, or sends a rejection if it doesn't have the file. A Freenet node differs in that it forwards download (key) requests (much like the way Gnutella forwards search requests). Again, another example of networks that differ in node behavior and not communication protocol. Freenet's communication protocol is a subset of the Gnutella communication protocol. + +I'm currently toying with a general purpose peer-to-peer protocol. When I say "general purpose", I mean that the protocol is extraordinarily general and abstract. Ideally, any kind of p2p network could make use of this protocol. However, the protocol itself is very simple, so simple that anyone could understand it. One could imagine p2p network developers "adding on" support for this protocol to existing p2p node software, allowing all networks supporting the protocol to be joined together. + +One upshot of such a protocol is that it could be used to design a non-proprietary replacement for FastTrack: nodes would decide to become servers and then simply behave differently than the nodes that are clients. + +I'm contacting you because I'm wondering if you've run into similar ideas or initiatives out there. If so, could you point me towards them? + +Anyway, I'll keep you posted with news as I work on this more... What will the protocol be called? Is the name "konspire" taken yet? ;) + +Thanks, +Jason Rohrer +HC software + + + +-- +http://www.jasonrohrer.n3.net diff --git a/minorGems/protocol/p2p/notes.tex b/minorGems/protocol/p2p/notes.tex new file mode 100644 index 0000000..e8fe667 --- /dev/null +++ b/minorGems/protocol/p2p/notes.tex @@ -0,0 +1,50 @@ +% +% Modification History +% +% 2001-October-8 Jason Rohrer +% Created. +% +% 2001-December-2 Jason Rohrer +% Fixed a few typos and mistakes. +% + + + +\documentclass[12pt]{article} +\usepackage{fullpage} + +\begin{document} + +\title{Generic peer-to-peer protocol notes} +\date{Created: October 8, 2001; Last modified: December 1, 2001} + +\maketitle + +We can think of a peer-to-peer network as a group of hosts such that each host has a set of resources made available to the other hosts in the group. Peer $A$ can ask peer $B$ for a resource $r$ by sending $B$ a resource descriptor $D_r$, and peer $B$ can respond in one of \ref{response:count} ways: +\begin{enumerate} +\item Send a null response (equivalent to ``I know nothing about $r$''). +\item Return the requested resource $r$. +\item Return the address of a host that may know more about resource $r$ (referred to as informing $A$ of a more knowledgeable host). \label{response:inform} +\item Forward the request for $r$ on to other hosts in the network and inform $A$ that it is doing so.\label{response:forward} +\label{response:count} +\end{enumerate} +For response option \ref{response:forward} to make sense, we must assume that each resource descriptor $D_r$ contains a return address where the resource should be sent and a request identifier so that $A$ knows that a particular inbound transmission is a response to its request for $r$. To be compatible with request forwarding, all responses should be sent to the return address contained in the request. To make sense in this context, the null response should be embodied by no response at all. Because responses are not returned via the same two-way transmission channel on which the corresponding requests were sent, a responding host $B$ can in fact perform any subset of the response options simultaneously. Thus, the system can operate using only one-way transmissions. In this context, the null response corresponds to the empty response set. + +A network that avoids option \ref{response:forward} could still be a very usable network and in fact would require far fewer network transmissions to operate (as well as distributing the work load more heavily upon those hosts actually requesting resources). However, such a network would not take advantage of the work distribution possible with a forwarding network. Note that a non-forwarding network can be just as powerful as a forwarding network by clever use of response option \ref{response:inform} in place of \ref{response:forward}. We will deal only with forwarding networks throughout the rest of this document. + +As a simple example, consider the case where $r$ is file data. $A$ sends $D_r$ to $B$, and $B$ might execute a null response or return the requested resource by sending back the file data associated with $D_r$. We might assume that $D_r$ contains information that uniquely describes a file resource being offered by $B$, so neither response option \ref{response:inform} nor response option \ref{response:forward} would apply. + +For a more complex example, consider the case where $r$ is a search results set. Note that a search is itself a resource that might be offered by a host, and we might have specially-designated index nodes in the network that offer the resource of searching. Suppose $A$ sends $D_r$ to $B$. $D_r$ might contain information about the resource types to search for, as well as a set of resource descriptors. Suppose that $B$ does not have the capability to perform the requested search, but is aware of a super-node $B'$ that does have searching capabilities. $B$ has two options at this point: return a description of $B'$, or forward $D_r$ to $B'$. Since the method of executing the first option is obvious, consider the second option. $B$ forwards $D_r$ to $B'$, and $A$ waits for a response. $B'$ performs the search, constructing the set $R = \{D_{r'} \mid r'$ matches the search criteria of $r \}$. $B'$ attaches the identifier from $D_r$ to $R$ and then sends it to the return address found in $D_r$. $A$ receives $R$. By examining the identifier, $A$ knows $R$ is a response to $D_r$. + +In addition, $B'$ might forward $A$'s search request on to other indexing nodes. Thus, $A$ might receive multiple responses to $D_r$ and could track them all by their common identifier. + +As another example, we might think of an indexing node $B$ sending requests to non-indexing nodes to collect data about their resources. In this case, resource $r$ would be a resource list. $B$ might send $D_r$ to $A$, and $A$ might return its resource list as well as forward $D_r$ to the resource hosts that it knows about. In this case, $B$ might receive responses to $D_r$ from many different hosts, but all responses will contain an identifier that $B$ can recognize. Thus, $B$ can collect a substantial index of resources in the network simply by sending out a single request. $B$ might have a limit on its index size, so it might send out $D_r$ to various hosts until it builds a large enough index and then discard further responses. $B$ could update its index in the future by sending out a new $D_r$ with a different identifier. + +Because response option \ref{response:inform} and response option \ref{response:forward} are equally powerful, we can ignore option \ref{response:inform} in our further discussions. We choose to ignore \ref{response:inform} because we are primarily interested in the work distribution possible with forwarding networks. However, we should note that using option \ref{response:inform} wisely can result in drastic savings in terms of the number of network transmissions sent. For example, if only forwarding is used, the following situation might arise. Consider the host set $\{A, B, C, D\}$, and suppose that the only indexing node is $D$. Let $K$ be a binary knowledge relation such that $(X,Y)\in K \Rightarrow$ ``$X$ knows about $Y$''. In our system, assume $K=\{ (A,B), (B,C), (C,D) \}$. Suppose $A$ needs a search result set $r$. $A$ can only send $D_r$ to $B$. $B$ forwards the request to $C$, and $C$ forwards to $D$. $D$ fills the request and sends the results back to $A$ via the return address in $D_r$. When $A$ needs to search again, the same transmission process occurs, resulting in 4 transmissions for each search requested by $A$. By using response option \ref{response:inform}, $C$ might inform $A$ about $D$. Thus, $A$'s first search would require 5 transmissions, but each additional search would require only 2 transmissions. Even if response option \ref{response:inform} was used by $B$ during $A$'s first search to inform $A$ about $C$, $A$'s first search would only require 6 transmissions. + +For a set of hosts $S$ such that $|S|=n$ and for an arbitrary knowledge relation $K_s$, if $(X,Y)$ is in the transitive closure of $K$, then the worst case number of transmissions for each request fulfilled by $Y$ for $X$ is $O(n)$ if only forwarding is used. If the informing response option is always used along with forwarding, we have $2(n-1)$ transmissions in the worst case for the initial request, and $2$ transmissions for each subsequent request. For $O(n)$ requests, we execute $O(1)$ amortized transmissions per request. + + +\end{document} + + diff --git a/minorGems/protocol/p2p/resourceSpecifications.txt b/minorGems/protocol/p2p/resourceSpecifications.txt new file mode 100644 index 0000000..3dff2dc --- /dev/null +++ b/minorGems/protocol/p2p/resourceSpecifications.txt @@ -0,0 +1,74 @@ +This file contains specifications for resource types. For each type, we give a specification for the resource descriptor and then the resource. Newlines in this specification can be replaced by any kind of white space. + + + +GET_REQUEST_FAILURE +[no description] + +GET_REQUEST_FAILURE +resource_descriptor + + +(Note that a GET_REQUEST_FAILURE resource contains the descriptor from the failed request. The PUT request for a GET_REQUEST_FAILURE resource should use the ID number from the original resource request.) + + + +MORE_KNOWLEGEABLE_HOSTS +resource_descriptor + +MORE_KNOWLEDGEABLE_HOSTS +num_hosts +address_1:port_1 +address_2:port_2 +... +address_N:port_N + + + +SERVER_LIST +[no description] + +SERVER_LIST +num_servers +address_1:port_1 +address_2:port_2 +... +address_N:port_N + + + +SEARCH +num_allowed_resource_types +allowed_type_1 +allowed_type_2 +... +allowed_type_N +num_search_terms +search_term_1 +search_term_2 +... +search_term_N + +SEARCH +num_results +result_descriptor_1 +result_descriptor_2 +... +result_descriptor_N + + + +FILE +host_address:host_port +file_path + +FILE +file_path +file_length_bytes +###file_data + + +(Note that for the FILE type, the ### must occur immediately before the file data (no whitespace must separate ### from the start of the file). + + + diff --git a/minorGems/system/BinarySemaphore.h b/minorGems/system/BinarySemaphore.h new file mode 100644 index 0000000..b9d810d --- /dev/null +++ b/minorGems/system/BinarySemaphore.h @@ -0,0 +1,79 @@ +/* + * Modification History + * + * 2001-January-11 Jason Rohrer + * Created. + * + * 2001-January-27 Jason Rohrer + * Fixed a bug in the precompiler directives. + * + * 2003-August-26 Jason Rohrer + * Added support for timeouts on wait. + */ + +#include "minorGems/common.h" + + + +#ifndef BINARY_SEMAPHORE_CLASS_INCLUDED +#define BINARY_SEMAPHORE_CLASS_INCLUDED + +#include "MutexLock.h" + +/** + * Binary semaphore class. Semaphore starts out with a value of 0. + * + * Note: Implementation for the functions defined here is provided + * separately for each platform (in the mac/ linux/ and win32/ + * subdirectories). + * + * @author Jason Rohrer + */ +class BinarySemaphore { + + public: + + /** + * Constructs a binary semaphore. + */ + BinarySemaphore(); + + ~BinarySemaphore(); + + + /** + * Blocks on this semaphore until signal() is called by another thread. + * Note that if signal() has already been called before wait() is + * called, then this call will return immediately, though the semaphore + * is reset to 0 by this call. + * + * @param inTimeoutInMilliseconds the maximum time to wait in + * milliseconds, or -1 to wait forever. Defaults to -1. + * + * @return 1 if the semaphore was signaled, or 0 if it timed out. + */ + int wait( int inTimeoutInMilliseconds = -1 ); + + + /** + * Signals the semaphore, allowing a waiting thread to return from + * its call to wait(). (The semaphore is set to 1 by this call if + * no thread is waiting on the semaphore currently.) + */ + void signal(); + + + private: + + // starts at 0 + int mSemaphoreValue; + + /** + * Used by platform-specific implementations. + */ + void *mNativeObjectPointerA; + void *mNativeObjectPointerB; + }; + + +#endif diff --git a/minorGems/system/FinishedSignalThread.cpp b/minorGems/system/FinishedSignalThread.cpp new file mode 100644 index 0000000..db841f5 --- /dev/null +++ b/minorGems/system/FinishedSignalThread.cpp @@ -0,0 +1,59 @@ +/* + * Modification History + * + * 2002-March-9 Jason Rohrer + * Created. + * + * 2002-March-11 Jason Rohrer + * Changed so that destructor joins thread. + * + * 2002-April-4 Jason Rohrer + * Changed name of lock to avoid confusion with subclass-provided locks. + * + * 2002-August-5 Jason Rohrer + * Fixed member initialization order to match declaration order. + * + * 2004-April-1 Jason Rohrer + * Moved from konspire2b into minorGems. + * Changed so that destructor does not join the thread. + */ + + + +#include "FinishedSignalThread.h" + + +#include + + + +FinishedSignalThread::FinishedSignalThread() + : mFinishedLock( new MutexLock() ), mFinished( false ) { + + } + + + +FinishedSignalThread::~FinishedSignalThread() { + + delete mFinishedLock; + } + + + +char FinishedSignalThread::isFinished() { + mFinishedLock->lock(); + char finished = mFinished; + mFinishedLock->unlock(); + + return finished; + } + + + +void FinishedSignalThread::setFinished() { + mFinishedLock->lock(); + mFinished = true; + mFinishedLock->unlock(); + } + diff --git a/minorGems/system/FinishedSignalThread.h b/minorGems/system/FinishedSignalThread.h new file mode 100644 index 0000000..91c4fcc --- /dev/null +++ b/minorGems/system/FinishedSignalThread.h @@ -0,0 +1,100 @@ +/* + * Modification History + * + * 2002-March-9 Jason Rohrer + * Created. + * + * 2002-March-10 Jason Rohrer + * Made destructor public. + * + * 2002-March-11 Jason Rohrer + * Changed so that destructor joins thread. + * + * 2002-April-4 Jason Rohrer + * Changed name of lock to avoid confusion with subclass-provided locks. + * + * 2004-April-1 Jason Rohrer + * Moved from konspire2b into minorGems. + * Changed so that destructor does not join the thread. + * + * 2004-November-19 Jason Rohrer + * Changed to virtual inheritance from Thread class. + */ + + + +#ifndef FINISHED_SIGNAL_THREAD_INCLUDED +#define FINISHED_SIGNAL_THREAD_INCLUDED + + + +#include "minorGems/system/Thread.h" +#include "minorGems/system/MutexLock.h" + + + +/** + * Abstract subclass if thread that has a + * synchronized finished signal. + * + * @author Jason Rohrer + */ +class FinishedSignalThread : public virtual Thread { + + + + public: + + + /** + * Only destroys this thread. + * Does not join. + */ + virtual ~FinishedSignalThread(); + + + + /** + * Gets whether this thread is finished and + * ready to be destroyed. + * + * @return true iff this thread is finished. + */ + char isFinished(); + + + + + + protected: + + + + FinishedSignalThread(); + + + + /** + * Sets that this thread is finished and + * ready to be destroyed. + * + * For this class to work properly, the subclass + * MUST call this function at the end of its run method. + */ + void setFinished(); + + + + private: + + MutexLock *mFinishedLock; + + char mFinished; + + + + }; + + + +#endif diff --git a/minorGems/system/FinishedSignalThreadManager.cpp b/minorGems/system/FinishedSignalThreadManager.cpp new file mode 100644 index 0000000..1512d20 --- /dev/null +++ b/minorGems/system/FinishedSignalThreadManager.cpp @@ -0,0 +1,116 @@ +/* + * Modification History + * + * 2004-November-9 Jason Rohrer + * Created. + * Modified from MUTE's ChannelReceivingThreadManager. + * + * 2005-January-9 Jason Rohrer + * Changed to sleep on a semaphore to allow sleep to be interrupted. + */ + + + +#include "minorGems/system/FinishedSignalThreadManager.h" + + + +FinishedSignalThreadManager::FinishedSignalThreadManager() + : mLock( new MutexLock() ), + mThreadVector( new SimpleVector() ), + mStopSignal( false ), + mSleepSemaphore( new BinarySemaphore() ) { + + this->start(); + } + + + +FinishedSignalThreadManager::~FinishedSignalThreadManager() { + mLock->lock(); + mStopSignal = true; + mLock->unlock(); + + // signal the sleeping semaphore to wake up the thread + mSleepSemaphore->signal(); + + this->join(); + + mLock->lock(); + + // destroy all remaining threads + int numThreads = mThreadVector->size(); + + for( int i=0; igetElement( i ) ); + } + delete mThreadVector; + + + mLock->unlock(); + delete mLock; + + delete mSleepSemaphore; + } + + + +void FinishedSignalThreadManager::addThread( + FinishedSignalThread *inThread ) { + + mLock->lock(); + + mThreadVector->push_back( inThread ); + + mLock->unlock(); + } + + + +void FinishedSignalThreadManager::run() { + + char stopped; + + mLock->lock(); + stopped = mStopSignal; + mLock->unlock(); + + + while( !stopped ) { + // wait for 10 seconds + int wasSignaled = mSleepSemaphore->wait( 10000 ); + + if( wasSignaled == 1 ) { + // signaled... we should stop + return; + } + + char foundFinished = true; + + while( foundFinished ) { + foundFinished = false; + + mLock->lock(); + + int numThreads = mThreadVector->size(); + + for( int i=0; igetElement( i ) ); + + if( currentThread->isFinished() ) { + delete currentThread; + mThreadVector->deleteElement( i ); + foundFinished = true; + } + } + mLock->unlock(); + } + + mLock->lock(); + stopped = mStopSignal; + mLock->unlock(); + } + } + diff --git a/minorGems/system/FinishedSignalThreadManager.h b/minorGems/system/FinishedSignalThreadManager.h new file mode 100644 index 0000000..9151edd --- /dev/null +++ b/minorGems/system/FinishedSignalThreadManager.h @@ -0,0 +1,82 @@ +/* + * Modification History + * + * 2004-November-9 Jason Rohrer + * Created. + * Modified from MUTE's ChannelReceivingThreadManager. + * + * 2005-January-9 Jason Rohrer + * Changed to sleep on a semaphore to allow sleep to be interrupted. + */ + + + +#ifndef FINISHED_SIGNAL_THREAD_MANAGER_INCLUDED +#define FINISHED_SIGNAL_THREAD_MANAGER_INCLUDED + + + +#include "minorGems/system/FinishedSignalThread.h" + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/system/Thread.h" +#include "minorGems/system/MutexLock.h" +#include "minorGems/system/BinarySemaphore.h" + + + +/** + * A thread that manages the destruction of FinishedSignalThreads. + * + * @author Jason Rohrer. + */ +class FinishedSignalThreadManager : public Thread { + + + + public: + + /** + * Constructs and starts this manager. + */ + FinishedSignalThreadManager(); + + + + /** + * Stops and destroys this manager. + */ + ~FinishedSignalThreadManager(); + + + + /** + * Adds a thread to this manager. + * + * @param inThread the thread to add. + * Will be destroyed by this class. + */ + void addThread( FinishedSignalThread *inThread ); + + + + // implements the Thread interface + void run(); + + + + protected: + MutexLock *mLock; + + SimpleVector *mThreadVector; + + char mStopSignal; + + BinarySemaphore *mSleepSemaphore; + + }; + + + + +#endif diff --git a/minorGems/system/Launcher.h b/minorGems/system/Launcher.h new file mode 100644 index 0000000..8765501 --- /dev/null +++ b/minorGems/system/Launcher.h @@ -0,0 +1,51 @@ +/* + * Modification History + * + * 2003-January-10 Jason Rohrer + * Created. + */ + + + +#include "minorGems/common.h" + + + +#ifndef LAUNCHER_INCLUDED +#define LAUNCHER_INCLUDED + + + +/** + * Interface for launching processes. + * + * @author Jason Rohrer + */ +class Launcher { + public: + + + + /** + * Launches a command in a new process. + * + * @param inCommandName the name of the command to launch. + * Must be destroyed by caller if non-const. + * @param inArguments an array of argument strings for the command. + * This array must be terminated by a NULL pointer. + * Note that by convention, the first argument should be the + * command name. + * Must be destroyed by caller. + */ + static void launchCommand( char *inCommandName, + char **inArguments ); + + + + }; + + + +#endif + + diff --git a/minorGems/system/MutexLock.h b/minorGems/system/MutexLock.h new file mode 100644 index 0000000..e8dc2b3 --- /dev/null +++ b/minorGems/system/MutexLock.h @@ -0,0 +1,71 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2002-March-29 Jason Rohrer + * Added Fortify inclusion. + * + * 2002-October-18 Jason Rohrer + * Moved common include out of header and into platform-specific cpp files, + * since MemoryTrack uses a mutex lock. + */ + + + +#ifndef MUTEX_LOCK_CLASS_INCLUDED +#define MUTEX_LOCK_CLASS_INCLUDED + + + +#ifdef FORTIFY +#include "minorGems/util/development/fortify/fortify.h" +#endif + + + +/** + * Mutex lock class. + * + * Note: Implementation for the functions defined here is provided + * separately for each platform (in the mac/ linux/ and win32/ + * subdirectories). + * + * @author Jason Rohrer + */ +class MutexLock { + + public: + + /** + * Constructs a mutex lock; + */ + MutexLock(); + + ~MutexLock(); + + + /** + * Locks the mutex. Blocks until mutex available if it's + * already locked by another thread. + */ + void lock(); + + /** + * Unlocks the mutex. + */ + void unlock(); + + + private: + + /** + * Used by platform-specific implementations. + */ + void *mNativeObjectPointer; + + }; + + +#endif diff --git a/minorGems/system/Semaphore.h b/minorGems/system/Semaphore.h new file mode 100644 index 0000000..1835320 --- /dev/null +++ b/minorGems/system/Semaphore.h @@ -0,0 +1,201 @@ +/* + * Modification History + * + * 2001-January-11 Jason Rohrer + * Created. + * + * 2001-January-11 Jason Rohrer + * Added a willBlock() function. + * + * 2001-February-24 Jason Rohrer + * Fixed incorrect delete usage. + * + * 2002-February-11 Jason Rohrer + * Fixed a mistake in the signal() comment. + * + * 2003-August-26 Jason Rohrer + * Added support for timeouts on wait. + * + * 2003-December-28 Jason Rohrer + * Fixed a bug in semaphore value when we timeout on wait. + * + * 2004-January-9 Jason Rohrer + * Fixed a preprocessor error. + */ + +#include "minorGems/common.h" + + + +#ifndef SEMAPHORE_CLASS_INCLUDED +#define SEMAPHORE_CLASS_INCLUDED + +#include "MutexLock.h" +#include "BinarySemaphore.h" + + +/** + * General semaphore with an unbounded value. + * + * This class uses BinarySemaphores to implement general semaphores, + * so it relies on platform-specific BinarySemaphore implementations, + * but this class itself is platform-independent. + * + * @author Jason Rohrer + */ +class Semaphore { + + public: + + /** + * Constructs a semaphore. + * + * @param inStartingValue the starting value for this semaphore. + * Defaults to 0 if unspecified. + */ + Semaphore( int inStartingValue = 0 ); + + ~Semaphore(); + + + /** + * If this semaphore's current value is 0, then this call blocks + * on this semaphore until signal() is called by another thread. + * If this semaphore's value is >0, then it is decremented by this + * call. + * + * @param inTimeoutInMilliseconds the maximum time to wait in + * milliseconds, or -1 to wait forever. Defaults to -1. + * + * @return 1 if the semaphore was signaled, or 0 if it timed out. + */ + int wait( int inTimeoutInMilliseconds = -1 ); + + + + /** + * If a thread is waiting on this semaphore, then the thread + * becomes unblocked. + * If no thread is waiting, then the semaphore is incremented. + */ + void signal(); + + + /** + * Returns true if a call to wait would have blocked. + */ + char willBlock(); + + + private: + + // starts at 0 + int mSemaphoreValue; + + // mutex semaphore starts at 1 + BinarySemaphore *mMutexSemaphore; + // blocking semaphore starts at 0 + BinarySemaphore *mBlockingSemaphore; + }; + + + +inline Semaphore::Semaphore( int inStartingValue ) + : mSemaphoreValue( inStartingValue ), + mMutexSemaphore( new BinarySemaphore() ), + mBlockingSemaphore( new BinarySemaphore() ) { + + // increment the mutex semaphore to 1 + mMutexSemaphore->signal(); + } + + + +inline Semaphore::~Semaphore() { + delete mMutexSemaphore; + delete mBlockingSemaphore; + } + + + +inline int Semaphore::wait( int inTimeoutInMilliseconds ) { + int returnValue; + // this implementation copied from _Operating System Concepts_, p. 172 + + // lock the mutex + mMutexSemaphore->wait(); + // decrement the semaphore + mSemaphoreValue--; + if( mSemaphoreValue < 0 ) { + // we should block + + // release the mutex + mMutexSemaphore->signal(); + + // block + returnValue = mBlockingSemaphore->wait( inTimeoutInMilliseconds ); + + if( returnValue != 1 ) { + // timed out + + // increment the semaphore, since we never got signaled + // lock the mutex + mMutexSemaphore->wait(); + mSemaphoreValue++; + + // we will unlock the mutex below + } + } + else { + returnValue = 1; + } + + // release the mutex + // ( if we were signaled, then the signaller left the mutex locked ) + // ( if we timed out, then we re-locked the mutex above ) + mMutexSemaphore->signal(); + + return returnValue; + } + + + +inline char Semaphore::willBlock() { + char returnValue = false; + + // lock the mutex + mMutexSemaphore->wait(); + + // check if we will block + if( mSemaphoreValue <= 0 ) { + returnValue = true; + } + + // release the mutex + mMutexSemaphore->signal(); + + return returnValue; + } + + + +inline void Semaphore::signal() { + // lock the mutex + mMutexSemaphore->wait(); + // increment the semaphore + mSemaphoreValue++; + if( mSemaphoreValue <= 0 ) { + // we need to wake up a waiting thread + mBlockingSemaphore->signal(); + // let the waiting thread unlock the mutex + } + else { + // no threads are waiting, so we need to unlock the mutex + mMutexSemaphore->signal(); + } + } + + + + +#endif diff --git a/minorGems/system/StopSignalThread.cpp b/minorGems/system/StopSignalThread.cpp new file mode 100644 index 0000000..cd60685 --- /dev/null +++ b/minorGems/system/StopSignalThread.cpp @@ -0,0 +1,65 @@ +/* + * Modification History + * + * 2002-April-4 Jason Rohrer + * Created. + * Changed to reflect the fact that the base class + * destructor is called *after* the derived class destructor. + * + * 2002-August-5 Jason Rohrer + * Fixed member initialization order to match declaration order. + * + * 2003-September-5 Jason Rohrer + * Moved into minorGems. + * + * 2005-January-9 Jason Rohrer + * Changed to sleep on a semaphore to make sleep interruptable by stop. + */ + + + +#include "StopSignalThread.h" + + + +StopSignalThread::StopSignalThread() + : mStopLock( new MutexLock() ), mStopped( false ), + mSleepSemaphore( new BinarySemaphore() ) { + + } + + + +StopSignalThread::~StopSignalThread() { + + delete mStopLock; + delete mSleepSemaphore; + } + + + +void StopSignalThread::sleep( unsigned long inTimeInMilliseconds ) { + mSleepSemaphore->wait( inTimeInMilliseconds ); + } + + + +char StopSignalThread::isStopped() { + mStopLock->lock(); + char stoped = mStopped; + mStopLock->unlock(); + + return stoped; + } + + + +void StopSignalThread::stop() { + mStopLock->lock(); + mStopped = true; + mStopLock->unlock(); + + // signal the semaphore to wake up the thread, if it is sleeping + mSleepSemaphore->signal(); + } + diff --git a/minorGems/system/StopSignalThread.h b/minorGems/system/StopSignalThread.h new file mode 100644 index 0000000..30b2a54 --- /dev/null +++ b/minorGems/system/StopSignalThread.h @@ -0,0 +1,104 @@ +/* + * Modification History + * + * 2002-April-4 Jason Rohrer + * Created. + * Changed to reflect the fact that the base class + * destructor is called *after* the derived class destructor. + * + * 2003-September-5 Jason Rohrer + * Moved into minorGems. + * + * 2004-November-19 Jason Rohrer + * Changed to virtual inheritance from Thread class. + * + * 2005-January-9 Jason Rohrer + * Changed to sleep on a semaphore to make sleep interruptable by stop. + */ + + + +#ifndef STOP_SIGNAL_THREAD_INCLUDED +#define STOP_SIGNAL_THREAD_INCLUDED + + + +#include "minorGems/system/Thread.h" +#include "minorGems/system/MutexLock.h" +#include "minorGems/system/BinarySemaphore.h" + + + +/** + * Abstract subclass of thread that has a stop signal. + * + * Note that subclasses MUST check the isStopped() function + * periodically in their run() function for this class to work + * properly. + * + * @author Jason Rohrer + */ +class StopSignalThread : public virtual Thread { + + + + public: + + + + /** + * Only destroys this thread. + * Does not stop or join. + */ + virtual ~StopSignalThread(); + + + + protected: + + + + StopSignalThread(); + + + + // overrides Thread::sleep to make it interruptable by our stop call + virtual void sleep( unsigned long inTimeInMilliseconds ); + + + + /** + * Signals this thread to stop, interrupting it if it is sleeping. + * + * Thread safe. + * + * Thread must be joined after this call returns. + */ + void stop(); + + + + /** + * Gets whether this thread has been signaled to stop. + * + * Thread safe. + * + * @return true if this thread should stop. + */ + char isStopped(); + + + + private: + + MutexLock *mStopLock; + char mStopped; + + BinarySemaphore *mSleepSemaphore; + + + }; + + + +#endif diff --git a/minorGems/system/TestThread.cpp b/minorGems/system/TestThread.cpp new file mode 100644 index 0000000..7187106 --- /dev/null +++ b/minorGems/system/TestThread.cpp @@ -0,0 +1,54 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2002-November-14 Jason Rohrer + * Added more verbose printouts. + * + * 2004-March-31 Jason Rohrer + * Added test of detached threads. + */ + +#include "TestThread.h" + +int numToCount = 1000; + + +/** + * Main method that spawns two TestThreads. + * + * @author Jason Rohrer + */ +int main() { + + TestThread *thread1 = new TestThread( 1, numToCount ); + TestThread *thread2 = new TestThread( 2, numToCount ); + TestThread *thread3 = new TestThread( 3, numToCount ); + + + ThreadSafePrinter::printf( "Starting thread 1\n" ); + thread1->start(); + ThreadSafePrinter::printf( "Starting thread 2\n" ); + thread2->start(); + ThreadSafePrinter::printf( "Starting thread 3 in detached mode\n" ); + thread3->start( true ); + + Thread::sleep( 5000 ); + + ThreadSafePrinter::printf( "Joining thread 1\n" ); + thread1->join(); + ThreadSafePrinter::printf( "Joining thread 2\n" ); + thread2->join(); + + ThreadSafePrinter::printf( "Destroying thread 1\n" ); + delete thread1; + ThreadSafePrinter::printf( "Destroying thread 2\n" ); + delete thread2; + + ThreadSafePrinter::printf( + "Thread 3 should handle its own destruction.\n" ); + + return 0; + } diff --git a/minorGems/system/TestThread.h b/minorGems/system/TestThread.h new file mode 100644 index 0000000..5cb2412 --- /dev/null +++ b/minorGems/system/TestThread.h @@ -0,0 +1,77 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2001-January-27 Jason Rohrer + * Switched to a ThreadSafePrinter in attempt to get it to work on Win32. + * Changed print call to printf. + * + * 2002-November-14 Jason Rohrer + * Added missing destructor. + */ + +#include "minorGems/common.h" + + + +#ifndef TEST_THREAD_CLASS_INCLUDED +#define TEST_THREAD_CLASS_INCLUDED + +#include "Thread.h" + +#include "ThreadSafePrinter.h" + +#include + +/** + * Test subclass of Thread class. Useful for testing if platform-specific + * thread implmentations are working. + * + * @author Jason Rohrer + */ +class TestThread : public Thread { + + public: + /** + * Constructs a test thread and tells it how high to count to. + * + * @param inID id number thread will print along with count. + * @param inNumToCount thread will count from 0 to this number. + */ + TestThread( int inID, int inNumToCount ); + + ~TestThread(); + + // override the run method from PThread + void run(); + + private: + int mID; + int mNumToCount; + + }; + + + +inline TestThread::TestThread( int inID, int inNumToCount ) + : mID( inID ), mNumToCount( inNumToCount ) { + + } + + + +inline TestThread::~TestThread() { + + } + + + +inline void TestThread::run() { + for( int i=0; i<=mNumToCount; i++ ) { + ThreadSafePrinter::printf( "Thread %d counting %d.\n", mID, i ); + } + } + +#endif diff --git a/minorGems/system/Thread.h b/minorGems/system/Thread.h new file mode 100644 index 0000000..c00547d --- /dev/null +++ b/minorGems/system/Thread.h @@ -0,0 +1,136 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2001-March-4 Jason Rohrer + * Made sleep() static so it can be called by non-Thread classes. + * + * 2001-May-12 Jason Rohrer + * Added comments about joining before destroying. + * + * 2002-March-29 Jason Rohrer + * Added Fortify inclusion. + * + * 2002-August-5 Jason Rohrer + * Made destructor virtual. + * + * 2004-March-31 Jason Rohrer + * Added support for detatched mode. + * + * 2005-January-9 Jason Rohrer + * Made sleep function virtual to allow overrides. + * + * 2005-January-22 Jason Rohrer + * Added a static sleep function. + */ + +#include "minorGems/common.h" + + + +#ifndef THREAD_CLASS_INCLUDED +#define THREAD_CLASS_INCLUDED + + + +#ifdef FORTIFY +#include "minorGems/util/development/fortify/fortify.h" +#endif + + + +/** + * Base class to be subclassed by all threads. + * + * Note: Implementation for the functions defined here is provided + * separately for each platform (in the mac/ linux/ and win32/ + * subdirectories). + * + * @author Jason Rohrer + */ +class Thread { + + public: + + Thread(); + virtual ~Thread(); + + + /** + * Starts this Thread. + * + * Note that after starting a non-detached thread, it _must_ be + * joined before being destroyed to avoid memory leaks. + * + * Threads running in detatched mode handle their own destruction + * as they terminate and do not need to be joined at all. + * + * @param inDetach true if this thread should run in detatched mode, + * or false to run in non-detached mode. Defaults to false. + */ + void start( char inDetach = false ); + + + /** + * To be overriden by subclasses. + * This method will be run by the Thread after start() has been called. + */ + virtual void run() = 0; + + + /** + * Blocks until this thread finishes executing its run() method. + * + * Must be called before destroying this thread, if this thread + * has been started. + */ + void join(); + + + /** + * Puts the current thread to sleep for a specified amount of time. + * + * Note that given a thread instance threadA, calling threadA.sleep() + * will put the calling thread to sleep. + * + * @param inTimeInMilliseconds the number of milliseconds to sleep. + */ + virtual void sleep( unsigned long inTimeInMilliseconds ) { + staticSleep( inTimeInMilliseconds ); + } + + + + /** + * Same as sleep, but can be called without constructing a thread. + */ + static void staticSleep( unsigned long inTimeInMilliseconds ); + + + + /** + * Gets whether this thread is detached. + * + * @return true if this thread is detached. + */ + char isDetatched() { + return mIsDetached; + } + + + + private: + + /** + * Used by platform-specific implementations. + */ + void *mNativeObjectPointer; + + + char mIsDetached; + + }; + +#endif diff --git a/minorGems/system/ThreadSafePrinter.h b/minorGems/system/ThreadSafePrinter.h new file mode 100644 index 0000000..3327580 --- /dev/null +++ b/minorGems/system/ThreadSafePrinter.h @@ -0,0 +1,71 @@ +/* + * Modification History + * + * 2000-October-14 Jason Rohrer + * Created. + * + * 2001-January-27 Jason Rohrer + * Converted to use MutexLock and added to minorGems source tree. + * Changed tprintf to be static (the mutexes don't work otherwise). + * Now we're closing the argument list. + * Fixed so that it works with any number of arguments. + * Changed name of print function to printf. + * + * 2004-March-31 Jason Rohrer + * Fixed static memory leak. + */ + +#include "minorGems/common.h" + + + +#ifndef THREAD_SAFE_PRINTER_INCLUDED +#define THREAD_SAFE_PRINTER_INCLUDED + +#include "MutexLock.h" +#include + +// for variable argument lists +#include + +/** + * Thread safe printf function. Note that printf is actually thread safe + * anyway, so this is just to demonstrate and test locks. It seems as + * though printf _isn't_ thread safe on certain platforms, so this class + * may be useful. + * + * @author Jason Rohrer + */ +class ThreadSafePrinter { + + public: + + static int printf( const char* inFormatString, ... ); + + private: + static MutexLock sLock; + + }; + +// initialize static members +MutexLock ThreadSafePrinter::sLock; + +inline int ThreadSafePrinter::printf( const char*inFormatString, ... ) { + + va_list argList; + va_start( argList, inFormatString ); + + sLock.lock(); + + int returnVal = vprintf( inFormatString, argList ); + fflush( stdout ); + + sLock.unlock(); + + va_end( argList ); + + return returnVal; + } + + +#endif diff --git a/minorGems/system/Time.h b/minorGems/system/Time.h new file mode 100644 index 0000000..fca4bb5 --- /dev/null +++ b/minorGems/system/Time.h @@ -0,0 +1,112 @@ +/* + * Modification History + * + * 2001-October-29 Jason Rohrer + * Created. + * + * 2004-October-14 Jason Rohrer + * Fixed sign bug. + * + * 2005-February-10 Jason Rohrer + * Added function to get time in floating point format. + */ + +#include "minorGems/common.h" + + + +#ifndef TIME_INCLUDED +#define TIME_INCLUDED + + + +/** + * Interface for platform-independent, high-resolution time access. + * + * @author Jason Rohrer + */ +class Time { + public: + + + + /** + * Gets the current time in seconds and milliseconds. + * + * No guarentee about when absolute 0 of this time + * scale is for particular systems. + * + * @param outSeconds pointer to where the time in seconds + * will be returned. + * @param outMilliseconds pointer to where the extra + * milliseconds will be returned. Value returned is in [0,999]. + */ + static void getCurrentTime( unsigned long *outSeconds, + unsigned long *outMilliseconds ); + + + + /** + * Gets the current time in fractional (double) seconds. + * + * @return the current time in seconds. + */ + static double getCurrentTime(); + + + + /** + * Gets the number of milliseconds that have passed + * since a time in seconds and milliseconds. + * + * @param inSeconds the start time, in seconds. + * @param inMilliseconds the start time's additional milliseconds. + * + * @return the number of milliseconds that have passed + * since inSeconds:inMilliseconds. May overflow if + * more than 49 days have passed (assuming 32-bit longs). + */ + static unsigned long getMillisecondsSince( + unsigned long inSeconds, unsigned long inMilliseconds ); + + + + }; + + + +inline double Time::getCurrentTime() { + unsigned long currentTimeS; + unsigned long currentTimeMS; + getCurrentTime( ¤tTimeS, ¤tTimeMS ); + + return currentTimeS + currentTimeMS / 1000.0; + } + + + +inline unsigned long Time::getMillisecondsSince( + unsigned long inSeconds, unsigned long inMilliseconds ) { + + unsigned long currentTimeS; + unsigned long currentTimeMS; + getCurrentTime( ¤tTimeS, ¤tTimeMS ); + + + unsigned long deltaS = ( currentTimeS - inSeconds ); + long deltaMS = ( (long)currentTimeMS - (long)inMilliseconds ); + + // carry, if needed + if( deltaMS < 0 ) { + deltaS--; + deltaMS += 1000; + } + + return 1000 * deltaS + deltaMS; + } + + + +#endif + + diff --git a/minorGems/system/endian.h b/minorGems/system/endian.h new file mode 100644 index 0000000..e0021fe --- /dev/null +++ b/minorGems/system/endian.h @@ -0,0 +1,108 @@ +/* + * Modification History + * + * 2002-May-25 Jason Rohrer + * Created. + * + * 2004-January-12 Jason Rohrer + * Added support for metrowerks win32 compiler. + * + * 2009-April-3 Jason Rohrer + * OpenBSD support. + */ + +#include "minorGems/common.h" + + + +/** + * Include this file to define __BYTE_ORDER + * + * After this has been included, __BYTE_ORDER will be either + * __LITTLE_ENDIAN or + * __BIG_ENDIAN + */ + + + +#ifdef __FreeBSD__ +#include + + +#elif defined(__NetBSD__) +#include + + +#elif defined(__OpenBSD__) +#include +#include + + +// default BSD case +#elif defined(BSD) +#include + + + +#elif defined(SOLARIS) +// Code for Solaris defs adapted from: +// MD5 message-digest algorithm. +// by Colin Plumb in 1993, no copyright is claimed. + +//each solaris is different -- this won't work on 2.6 or 2.7 +# include + +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 + +#ifdef _LITTLE_ENDIAN +#define __BYTE_ORDER __LITTLE_ENDIAN + +#else // default to big endian +#define __BYTE_ORDER __BIG_ENDIAN +#endif + +// end solaris case + + + +#elif defined(WIN_32) || \ + ( defined(__MWERKS__) && defined(__INTEL__) ) // windows case +#define __LITTLE_ENDIAN 1234 +#define __BYTE_ORDER __LITTLE_ENDIAN + +// end windows case + + + +#else +// linux case +#include + +// end linux case + + + +#endif +// end of all system-specific cases + + + + + +// BSD calls it BYTE_ORDER, linux calls it __BYTE_ORDER +#ifndef __BYTE_ORDER +#define __BYTE_ORDER BYTE_ORDER +#endif + +#ifndef __LITTLE_ENDIAN +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#endif + +#ifndef __BIG_ENDIAN +#define __BIG_ENDIAN BIG_ENDIAN +#endif + + + + diff --git a/minorGems/system/linux/BinarySemaphoreLinux.cpp b/minorGems/system/linux/BinarySemaphoreLinux.cpp new file mode 100644 index 0000000..3ab9447 --- /dev/null +++ b/minorGems/system/linux/BinarySemaphoreLinux.cpp @@ -0,0 +1,192 @@ +/* + * Modification History + * + * 2001-January-11 Jason Rohrer + * Created. + * + * 2003-August-26 Jason Rohrer + * Added support for timeouts on wait. + * + * 2006-February-28 Jason Rohrer + * Fixed bug in sub-second timeout computation. + */ + +#include "minorGems/system/BinarySemaphore.h" +#include "minorGems/system/Time.h" +#include + + +/** + * Linux-specific implementation of the BinarySemaphore class member functions. + * + * May also be compatible with other POSIX-like systems. + * + * To compile: + * g++ -lpthread + */ + +/** + * Native object pointer A is the condition variable. + * Pointer B is the mutex that must go along with it. + */ + + +BinarySemaphore::BinarySemaphore() : + mSemaphoreValue( 0 ) { + + // allocate a condition variable structure on the heap + mNativeObjectPointerA = (void *)( new pthread_cond_t[1] ); + + // get a pointer to the cond + pthread_cond_t *condPointer = + (pthread_cond_t *)mNativeObjectPointerA; + + // init the cond + pthread_cond_init( &( condPointer[0] ), NULL ); + + + // allocate a mutex structure on the heap + mNativeObjectPointerB = (void *)( new pthread_mutex_t[1] ); + + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointerB; + + // init the mutex + pthread_mutex_init( &( mutexPointer[0] ), NULL ); + + } + +BinarySemaphore::~BinarySemaphore() { + + // get a pointer to the cond + pthread_cond_t *condPointer = + (pthread_cond_t *)mNativeObjectPointerA; + + // destroy the cond + pthread_cond_destroy( &( condPointer[0] ) ); + + // de-allocate the cond structure from the heap + delete [] condPointer; + + + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointerB; + + // destroy the mutex + pthread_mutex_destroy( &( mutexPointer[0] ) ); + + // de-allocate the mutex structure from the heap + delete [] mutexPointer; + } + + + +int BinarySemaphore::wait( int inTimeoutInMilliseconds ) { + + int returnValue = 1; + + // get a pointer to the cond + pthread_cond_t *condPointer = + (pthread_cond_t *)mNativeObjectPointerA; + + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointerB; + + + // lock the mutex + pthread_mutex_lock( &( mutexPointer[0] ) ); + + if( mSemaphoreValue == 0 ) { + // wait on condition variable, which automatically unlocks + // the passed-in mutex + + if( inTimeoutInMilliseconds == -1 ) { + // no timeout + pthread_cond_wait( &( condPointer[0] ), &( mutexPointer[0] ) ); + } + else { + // use timeout version + + unsigned long nsecPerSecond = 1000000000; + unsigned long nsecPerMillisecond = 1000000; + + + unsigned long currentSec; + unsigned long currentMS; + + Time::getCurrentTime( ¤tSec, ¤tMS ); + + unsigned long currentNS = currentMS * nsecPerMillisecond; + + + long timeoutSec = inTimeoutInMilliseconds / 1000; + long extraMS = inTimeoutInMilliseconds % 1000; + + long extraNS = extraMS * nsecPerMillisecond; + + + + unsigned long absTimeoutSec = currentSec + timeoutSec; + unsigned long absTimeoutNsec = currentNS + extraNS; + + // check for nsec overflow + if( absTimeoutNsec > nsecPerSecond ) { + absTimeoutSec += 1; + absTimeoutNsec -= nsecPerSecond; + } + + struct timespec abstime; + abstime.tv_sec = absTimeoutSec; + abstime.tv_nsec = absTimeoutNsec; + + int result = pthread_cond_timedwait( &( condPointer[0] ), + &( mutexPointer[0] ), + &abstime ); + + if( result != 0 ) { + // timed out + returnValue = 0; + } + } + + // mutex is apparently re-locked when we return from cond_wait + + } + + // decrement the semaphore value + mSemaphoreValue = 0; + + // unlock the mutex again + pthread_mutex_unlock( &( mutexPointer[0] ) ); + + return returnValue; + } + + + +void BinarySemaphore::signal() { + + // get a pointer to the cond + pthread_cond_t *condPointer = + (pthread_cond_t *)mNativeObjectPointerA; + + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointerB; + + + // lock the mutex + pthread_mutex_lock( &( mutexPointer[0] ) ); + + // increment the semaphore value + mSemaphoreValue = 1; + + pthread_cond_signal( &( condPointer[0] ) ); + + + // unlock the mutex + pthread_mutex_unlock( &( mutexPointer[0] ) ); + } diff --git a/minorGems/system/linux/MutexLockLinux.cpp b/minorGems/system/linux/MutexLockLinux.cpp new file mode 100644 index 0000000..9c60cb9 --- /dev/null +++ b/minorGems/system/linux/MutexLockLinux.cpp @@ -0,0 +1,76 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2002-October-18 Jason Rohrer + * Moved common include out of header and into platform-specific cpp files, + * since MemoryTrack uses a mutex lock. + * Changed to use malloc instead of new internally to work with debugMemory. + * Made use of mNativeObjectPointer a bit cleaner. + */ + +#include "minorGems/common.h" + + + +#include +#include +#include + +/** + * Linux-specific implementation of the MutexLock class member functions. + * + * May also be compatible with other POSIX-like systems. + * + * To compile: + * g++ -lpthread + */ + + +MutexLock::MutexLock() { + // allocate a mutex structure on the heap + mNativeObjectPointer = malloc( sizeof( pthread_mutex_t ) ); + + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointer; + + // init the mutex + pthread_mutex_init( mutexPointer, NULL ); + } + + + +MutexLock::~MutexLock() { + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointer; + + // destroy the mutex + pthread_mutex_destroy( mutexPointer ); + + // de-allocate the mutex structure from the heap + free( mNativeObjectPointer ); + } + + + +void MutexLock::lock() { + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointer; + + pthread_mutex_lock( mutexPointer ); + } + + + +void MutexLock::unlock() { + // get a pointer to the mutex + pthread_mutex_t *mutexPointer = + (pthread_mutex_t *)mNativeObjectPointer; + + pthread_mutex_unlock( mutexPointer ); + } diff --git a/minorGems/system/linux/ThreadLinux.cpp b/minorGems/system/linux/ThreadLinux.cpp new file mode 100644 index 0000000..ad6c4ed --- /dev/null +++ b/minorGems/system/linux/ThreadLinux.cpp @@ -0,0 +1,247 @@ +/* + * Modification History + * + * 2000-December-13 Jason Rohrer + * Created. + * + * 2001-January-11 Jason Rohrer + * Added missing sleep() implementation. + * + * 2002-March-27 Jason Rohrer + * Added support for gprof-friendly thread wrappers. + * Fixed a compile bug when gprof threads disabled. + * + * 2002-August-5 Jason Rohrer + * Removed an unused variable. + * + * 2003-February-3 Jason Rohrer + * Fixed sleep to be thread safe (signals were interrupting thread sleeps). + * + * 2004-March-31 Jason Rohrer + * Added support for detatched mode. + * + * 2005-January-22 Jason Rohrer + * Added a static sleep function. + */ + + + +#include +#include + +#include +#include +#include + + + +/** + * Linux-specific implementation of the Thread class member functions. + * + * May also be compatible with other POSIX-like systems. + * + * To compile: + * g++ -lpthread + * If thread profiling is desired for gprof on linux, compile + * with -DUSE_GPROF_THREADS (otherwise, only main thread is profiled). + */ + + + +#ifdef USE_GPROF_THREADS +// prototype +int gprof_pthread_create( pthread_t * thread, pthread_attr_t * attr, + void * (*start_routine)(void *), void * arg ); +#endif + + + +// prototype +/** + * A wrapper for the run method, since pthreads won't take + * a class's member function. Takes a pointer to the Thread to run, + * cast as a void*; + */ +void *linuxThreadFunction( void * ); + + + +Thread::Thread() { + // allocate a pthread structure on the heap + mNativeObjectPointer = (void *)( new pthread_t[1] ); + } + + + +Thread::~Thread() { + // de-allocate the pthread structure from the heap + pthread_t *threadPointer = (pthread_t *)mNativeObjectPointer; + delete [] threadPointer; + } + + + +void Thread::start( char inDetach ) { + + mIsDetached = inDetach; + + // get a pointer to the pthread + pthread_t *threadPointer = (pthread_t *)mNativeObjectPointer; + + // create the pthread, which also sets it running +#ifdef USE_GPROF_THREADS + gprof_pthread_create( &( threadPointer[0] ), NULL, + linuxThreadFunction, (void*)this ); +#else + pthread_create( &( threadPointer[0] ), NULL, + linuxThreadFunction, (void*)this ); +#endif + + if( mIsDetached ) { + pthread_detach( threadPointer[0] ); + } + } + + + +void Thread::join() { + void *joinStat; + + pthread_t *threadPointer = (pthread_t *)mNativeObjectPointer; + + pthread_join( threadPointer[0], &joinStat ); + } + + + +void Thread::staticSleep( unsigned long inTimeInMilliseconds ) { + + unsigned long seconds = inTimeInMilliseconds / 1000; + unsigned long milliseconds = inTimeInMilliseconds % 1000; + + struct timespec remainingSleepTimeStruct; + remainingSleepTimeStruct.tv_sec = seconds; + remainingSleepTimeStruct.tv_nsec = milliseconds * 1000000; + + struct timespec timeToSleepStruct; + + // sleep repeatedly, ignoring signals, untill we use up all of the time + int sleepReturn = -1; + while( sleepReturn == -1 ) { + + timeToSleepStruct.tv_sec = remainingSleepTimeStruct.tv_sec; + timeToSleepStruct.tv_nsec = remainingSleepTimeStruct.tv_nsec; + + sleepReturn = + nanosleep( &timeToSleepStruct, &remainingSleepTimeStruct ); + } + } + + + +// takes a pointer to a Thread object as the data value +void *linuxThreadFunction( void *inPtrToThread ) { + Thread *threadToRun = (Thread *)inPtrToThread; + threadToRun->run(); + + if( threadToRun->isDetatched() ) { + // thread detached, so we must destroy it + delete threadToRun; + } + + return inPtrToThread; + } + + + + + +#ifdef USE_GPROF_THREADS + + + +// found at http://sam.zoy.org/doc/programming/gprof.html +#include +/* + * pthread_create wrapper for gprof compatibility + * + * needed headers: + * + */ +typedef struct wrapper_s { + void * (*start_routine)(void *); + void * arg; + pthread_mutex_t lock; + pthread_cond_t wait; + struct itimerval itimer; + } wrapper_t; + + + +static void * wrapper_routine(void *); + + + +/** + * Same prototype as pthread_create; use some #define magic to + * transparently replace it in other files + */ +int gprof_pthread_create( pthread_t * thread, pthread_attr_t * attr, + void * (*start_routine)(void *), void * arg ) { + + wrapper_t wrapper_data; + int i_return; + + /* Initialize the wrapper structure */ + wrapper_data.start_routine = start_routine; + wrapper_data.arg = arg; + getitimer(ITIMER_PROF, &wrapper_data.itimer); + pthread_cond_init(&wrapper_data.wait, NULL); + pthread_mutex_init(&wrapper_data.lock, NULL); + pthread_mutex_lock(&wrapper_data.lock); + + /* The real pthread_create call */ + i_return = pthread_create(thread, attr, &wrapper_routine, + &wrapper_data); + + /* If the thread was successfully spawned, wait for the data + * to be released */ + if( i_return == 0 ) { + pthread_cond_wait(&wrapper_data.wait, &wrapper_data.lock); + } + + pthread_mutex_unlock(&wrapper_data.lock); + pthread_mutex_destroy(&wrapper_data.lock); + pthread_cond_destroy(&wrapper_data.wait); + return i_return; + } + + + +/** + * The wrapper function in charge for setting the itimer value + */ +static void * wrapper_routine( void * data ) { + + /* Put user data in thread-local variables */ + void * (*start_routine)(void *) = ((wrapper_t*)data)->start_routine; + void * arg = ((wrapper_t*)data)->arg; + + /* Set the profile timer value */ + setitimer(ITIMER_PROF, &((wrapper_t*)data)->itimer, NULL); + + /* Tell the calling thread that we don't need its data anymore */ + pthread_mutex_lock(&((wrapper_t*)data)->lock); + pthread_cond_signal(&((wrapper_t*)data)->wait); + pthread_mutex_unlock(&((wrapper_t*)data)->lock); + + /* Call the real function */ + return start_routine(arg); + } + + + +#endif + + + diff --git a/minorGems/system/semaphoreTest.cpp b/minorGems/system/semaphoreTest.cpp new file mode 100644 index 0000000..53afdaa --- /dev/null +++ b/minorGems/system/semaphoreTest.cpp @@ -0,0 +1,115 @@ +/* + * Modification History + * + * 2001-January-11 Jason Rohrer + * Created. + * + * 2001-January-27 Jason Rohrer + * Made printing in threads thread-safe. + */ + +#include "BinarySemaphore.h" +#include "Semaphore.h" +#include "Thread.h" +#include "ThreadSafePrinter.h" + +#include + +/** + * Thread that waits on a semaphore. + * + * @author Jason Rohrer + */ +class WaitingThread : public Thread { + + public: + + WaitingThread( int inID, Semaphore *inSemaphore ); + + // override the run method from PThread + void run(); + + private: + Semaphore *mSemaphore; + int mID; + }; + + +inline WaitingThread::WaitingThread( int inID, Semaphore *inSemaphore ) + : mID( inID ), mSemaphore( inSemaphore ) { + + } + + +inline void WaitingThread::run() { + for( int i=0; i<10; i++ ) { + ThreadSafePrinter::printf( "%d waiting for signal %d...\n", mID, i ); + mSemaphore->wait(); + ThreadSafePrinter::printf( "%d received signal %d.\n", mID, i ); + } + } + + +/** + * Thread that signals on a semaphore. + * + * @author Jason Rohrer + */ +class SignalingThread : public Thread { + + public: + + SignalingThread( Semaphore *inSemaphore ); + + // override the run method from PThread + void run(); + + private: + Semaphore *mSemaphore; + + }; + + +inline SignalingThread::SignalingThread( Semaphore *inSemaphore ) + : mSemaphore( inSemaphore ) { + + } + + +inline void SignalingThread::run() { + for( int i=0; i<5; i++ ) { + sleep( 5000 ); + ThreadSafePrinter::printf( "Signaling 20 times\n" ); + for( int j=0; j<20; j++ ) { + mSemaphore->signal(); + } + } + } + + + +int main() { + int i; + Semaphore *semph = new Semaphore(); + + SignalingThread *threadS = new SignalingThread( semph ); + + WaitingThread **threadW = new WaitingThread*[10]; + for( i=0; i<10; i++ ) { + threadW[i] = new WaitingThread( i, semph ); + threadW[i]->start(); + } + + threadS->start(); + + for( i=0; i<10; i++ ) { + threadW[i]->join(); + delete threadW[i]; + } + threadS->join(); + + delete semph; + delete threadS; + delete [] threadW; + return 0; + } diff --git a/minorGems/system/unix/LauncherUnix.cpp b/minorGems/system/unix/LauncherUnix.cpp new file mode 100644 index 0000000..2581bea --- /dev/null +++ b/minorGems/system/unix/LauncherUnix.cpp @@ -0,0 +1,33 @@ +/* + * Modification History + * + * 2003-January-10 Jason Rohrer + * Created. + */ + + + +#include "minorGems/system/Launcher.h" + +#include +#include + + +void Launcher::launchCommand( char *inCommandName, + char **inArguments ) { + + + int forkValue = fork(); + + if( forkValue == 0 ) { + // we're in child process, so exec command + + execvp( inCommandName, inArguments ); + + // we'll never return from this call + } + + } + + + diff --git a/minorGems/system/unix/TimeUnix.cpp b/minorGems/system/unix/TimeUnix.cpp new file mode 100644 index 0000000..6684bad --- /dev/null +++ b/minorGems/system/unix/TimeUnix.cpp @@ -0,0 +1,40 @@ +/* + * Modification History + * + * 2001-October-29 Jason Rohrer + * Created. + * + * 2002-March-13 Jason Rohrer + * Added include of time.h so that FreeBSD compile will work. + * Changed to use newer gettimeofday that should work on all unix platforms. + * Fixed a conversion bug. + */ + + +#include "minorGems/system/Time.h" + + +#include + +#include + +#include + +#include + + + +void Time::getCurrentTime( unsigned long *outSeconds, + unsigned long *outMilliseconds ) { + + struct timeval currentTime; + + gettimeofday( ¤tTime, NULL ); + + + *outMilliseconds = currentTime.tv_usec / 1000; + *outSeconds = currentTime.tv_sec; + + } + + diff --git a/minorGems/system/win32/BinarySemaphoreWin32.cpp b/minorGems/system/win32/BinarySemaphoreWin32.cpp new file mode 100644 index 0000000..16ceb10 --- /dev/null +++ b/minorGems/system/win32/BinarySemaphoreWin32.cpp @@ -0,0 +1,94 @@ +/* + * Modification History + * + * 2001-January-27 Jason Rohrer + * Created. + * + * 2001-March-4 Jason Rohrer + * Replaced include of and with + * to fix compile bugs encountered with newer windows compilers. + * + * 2003-August-26 Jason Rohrer + * Added support for timeouts on wait. + */ + +#include "minorGems/system/BinarySemaphore.h" + +#include + + +/** + * Win32-specific implementation of the BinarySemaphore class member functions. + */ + + +/** + * Native object pointer A is the semaphore handle. + * Pointer B is not used. + */ + + +BinarySemaphore::BinarySemaphore() : + mSemaphoreValue( 0 ) { + + // allocate a handle on the heap + mNativeObjectPointerA = (void *)( new HANDLE[1] ); + + // retrieve handle from the heap + HANDLE *semaphorePointer = (HANDLE *)mNativeObjectPointerA; + + + semaphorePointer[0] = CreateSemaphore( + (LPSECURITY_ATTRIBUTES) NULL, // no attributes + 0, // initial count + 1, // maximum count + (LPCTSTR) NULL ); // no name + + } + +BinarySemaphore::~BinarySemaphore() { + + // retrieve handle from the heap + HANDLE *semaphorePointer = (HANDLE *)mNativeObjectPointerA; + + // destroy the semaphore + CloseHandle( semaphorePointer[0] ); + + // de-allocate the handle from the heap + delete [] semaphorePointer; + } + + + +int BinarySemaphore::wait( int inTimeoutInMilliseconds ) { + + // retrieve handle from the heap + HANDLE *semaphorePointer = (HANDLE *)mNativeObjectPointerA; + + if( inTimeoutInMilliseconds == -1 ) { + WaitForSingleObject( semaphorePointer[0], INFINITE ); + return 1; + } + else { + // timeout + int result = WaitForSingleObject( semaphorePointer[0], + inTimeoutInMilliseconds ); + + if( result == WAIT_TIMEOUT ) { + return 0; + } + else { + return 1; + } + } + } + + + +void BinarySemaphore::signal() { + + // retrieve handle from the heap + HANDLE *semaphorePointer = (HANDLE *)mNativeObjectPointerA; + + ReleaseSemaphore( semaphorePointer[0], 1, (LPLONG) NULL ); + } diff --git a/minorGems/system/win32/LauncherWin32.cpp b/minorGems/system/win32/LauncherWin32.cpp new file mode 100644 index 0000000..4b5be9a --- /dev/null +++ b/minorGems/system/win32/LauncherWin32.cpp @@ -0,0 +1,29 @@ +/* + * Modification History + * + * 2003-January-10 Jason Rohrer + * Created. + * + * 2003-March-24 Jason Rohrer + * Fixed a syntax typo. + */ + + + +#include "minorGems/system/Launcher.h" + +#include +#include + + + +void Launcher::launchCommand( char *inCommandName, + char **inArguments ) { + + _spawnvp( _P_NOWAIT, + inCommandName, + inArguments ); + } + + + diff --git a/minorGems/system/win32/MutexLockWin32.cpp b/minorGems/system/win32/MutexLockWin32.cpp new file mode 100644 index 0000000..938c857 --- /dev/null +++ b/minorGems/system/win32/MutexLockWin32.cpp @@ -0,0 +1,81 @@ +/* + * Modification History + * + * 2001-January-27 Jason Rohrer + * Created. + * + * 2001-March-4 Jason Rohrer + * Replaced include of and with + * to fix compile bugs encountered with newer windows compilers. + * + * 2002-October-18 Jason Rohrer + * Moved common include out of header and into platform-specific cpp files, + * since MemoryTrack uses a mutex lock. + * + * 2002-October-19 Jason Rohrer + * Changed to use malloc instead of new internally to work with debugMemory. + * Made use of mNativeObjectPointer a bit cleaner. + * Fixed a few bugs with new use of mNativeObjectPointer. + */ + +#include "minorGems/common.h" + + + +#include "minorGems/system/MutexLock.h" + +#include +#include + + + +/** + * Win32-specific implementation of the MutexLock class member functions. + */ + + +MutexLock::MutexLock() { + // allocate a handle on the heap + mNativeObjectPointer = malloc( sizeof( HANDLE ) ); + + // retrieve handle from the heap + HANDLE *mutexPointer = (HANDLE *)mNativeObjectPointer; + + // create the mutex + *mutexPointer = CreateMutex( + (LPSECURITY_ATTRIBUTES) NULL, // no attributes + (BOOL) false, // not initially locked + (LPCTSTR) NULL ); // no name + } + + + +MutexLock::~MutexLock() { + // retrieve handle from the heap + HANDLE *mutexPointer = (HANDLE *)mNativeObjectPointer; + + // destroy the mutex + CloseHandle( *mutexPointer ); + + // de-allocate the mutex structure from the heap + free( mutexPointer ); + } + + + +void MutexLock::lock() { + // retrieve handle from the heap + HANDLE *mutexPointer = (HANDLE *)mNativeObjectPointer; + + WaitForSingleObject( *mutexPointer, INFINITE ); + } + + + +void MutexLock::unlock() { + // retrieve handle from the heap + HANDLE *mutexPointer = (HANDLE *)mNativeObjectPointer; + + ReleaseMutex( *mutexPointer ); + } + diff --git a/minorGems/system/win32/ThreadWin32.cpp b/minorGems/system/win32/ThreadWin32.cpp new file mode 100644 index 0000000..88e8d18 --- /dev/null +++ b/minorGems/system/win32/ThreadWin32.cpp @@ -0,0 +1,108 @@ +/* + * Modification History + * + * 2001-January-27 Jason Rohrer + * Created. + * + * 2001-March-4 Jason Rohrer + * Replaced include of and with + * to fix compile bugs encountered with newer windows compilers. + * + * 2004-March-31 Jason Rohrer + * Added missing call to CloseHandle in destructor. + * Added support for detatched mode. + * + * 2004-April-1 Jason Rohrer + * Fixed a bug in CloseHandle call pointed out by Mycroftxxx. + * + * 2005-January-22 Jason Rohrer + * Added a static sleep function. + */ + +#include "minorGems/system/Thread.h" + +#include + + +/** + * Win32-specific implementation of the Thread class member functions. + * + */ + + +// prototype +/** + * A wrapper for the run method, since windows thread (perhaps) won't take + * a class's member function. Takes a pointer to the Thread to run, + * cast as a void*; + */ +DWORD WINAPI win32ThreadFunction( void * ); + + + +Thread::Thread() { + // allocate a handle on the heap + mNativeObjectPointer = (void *)( new HANDLE[1] ); + } + + + +Thread::~Thread() { + // get a pointer to the allocated handle + HANDLE *threadPointer = (HANDLE *)mNativeObjectPointer; + + // close the handle to ensure that the thread resources are freed + CloseHandle( threadPointer[0] ); + + // de-allocate the thread handle from the heap + delete [] threadPointer; + } + + + +void Thread::start( char inDetach ) { + + mIsDetached = inDetach; + + // get a pointer to the allocated handle + HANDLE *threadPointer = (HANDLE *)mNativeObjectPointer; + + DWORD threadID; + + threadPointer[0] = CreateThread( + (LPSECURITY_ATTRIBUTES)NULL, // no attributes + (DWORD)0, // default stack size + win32ThreadFunction, // function + (LPVOID)this, // function arg + (DWORD)0, // no creation flags (start thread immediately) + &threadID ); + } + + + +void Thread::join() { + + HANDLE *threadPointer = (HANDLE *)mNativeObjectPointer; + + WaitForSingleObject( threadPointer[0], INFINITE ); + } + + +void Thread::staticSleep( unsigned long inTimeInMilliseconds ) { + Sleep( inTimeInMilliseconds ); + } + + +// takes a pointer to a Thread object as the data value +DWORD WINAPI win32ThreadFunction( void *inPtrToThread ) { + Thread *threadToRun = (Thread *)inPtrToThread; + threadToRun->run(); + + + if( threadToRun->isDetatched() ) { + // thread detached, so we must destroy it + delete threadToRun; + } + + return 0; + } diff --git a/minorGems/system/win32/TimeWin32.cpp b/minorGems/system/win32/TimeWin32.cpp new file mode 100644 index 0000000..5bbb7e7 --- /dev/null +++ b/minorGems/system/win32/TimeWin32.cpp @@ -0,0 +1,77 @@ +/* + * Modification History + * + * 2001-November-7 Jason Rohrer + * Created. + * + * 2002-April-11 Jason Rohrer + * Added missing include, and fixed a bug. + * + * 2004-January-29 Jason Rohrer + * Fixed so that 0-point of time is the same as on other platforms. + * + * 2004-October-14 Jason Rohrer + * Fixed bug in second/millisecond callibration. + * Fixed bug in win32 time to ANSI time translation. + * Fixed daylight savings time bug. + */ + + +#include "minorGems/system/Time.h" + +#include +#include +#include +#include + + + +/** + * Windows implementation of Time.h. + * + * The 0-point should match the ANSI standard. + */ + + + +void Time::getCurrentTime( unsigned long *outSeconds, + unsigned long *outMilliseconds ) { + // convert from win32 broken-down time (which has msec resolution) + // to an ANSI time struct and then convert to an absolute time in + // seconds + // This procedure ensures that the 0-point matches the ANSI standard. + + // note: + // we cannot simply call ANSI time() to get the seconds and then rely + // on GetLocalTime to get the milliseconds, since the seconds value + // used by GetLocalTime is (strangely enough) not calibrated to the seconds + // value of time(). + // In other words, it is possible for the time() seconds to advance + // at a different clock cycle than the GetLocalTime seconds. + + // get time using a win32 call + SYSTEMTIME win32TimeStruct; + GetLocalTime( &win32TimeStruct ); + + // convert this win32 structure to the ANSI standard structure + struct tm ansiTimeStruct; + + ansiTimeStruct.tm_sec = win32TimeStruct.wSecond; + ansiTimeStruct.tm_min = win32TimeStruct.wMinute; + ansiTimeStruct.tm_hour = win32TimeStruct.wHour; + ansiTimeStruct.tm_mday = win32TimeStruct.wDay; + // ANSI time struct has month in range [0..11] + ansiTimeStruct.tm_mon = win32TimeStruct.wMonth - 1; + // ANSI time struct has year that is an offset from 1900 + ansiTimeStruct.tm_year = win32TimeStruct.wYear - 1900; + // unknown daylight savings time (dst) status + // if we fail to init this value, we can get inconsistent results + ansiTimeStruct.tm_isdst = -1; + + unsigned long secondsSinceEpoch = mktime( &ansiTimeStruct ); + + *outSeconds = secondsSinceEpoch; + *outMilliseconds = (unsigned long)( win32TimeStruct.wMilliseconds ); + } + + diff --git a/minorGems/ui/GUIComponent.h b/minorGems/ui/GUIComponent.h new file mode 100644 index 0000000..3bdf0ba --- /dev/null +++ b/minorGems/ui/GUIComponent.h @@ -0,0 +1,31 @@ +/* + * Modification History + * + * 2001-September-15 Jason Rohrer + * Created. + */ + + +#ifndef GUI_COMPONENT_INCLUDED +#define GUI_COMPONENT_INCLUDED + + +#include "minorGems/ui/GUIComponent.h" + + +/** + * An generic superclass for all gui components. + * Allows for easy event source determination during event passing. + * + * @author Jason Rohrer + */ +class GUIComponent { + + }; + + + +#endif + + + diff --git a/minorGems/ui/Keyboard.h b/minorGems/ui/Keyboard.h new file mode 100644 index 0000000..3ec3328 --- /dev/null +++ b/minorGems/ui/Keyboard.h @@ -0,0 +1,77 @@ +/* + * Modification History + * + * 2000-December-7 Jason Rohrer + * Created. + * + * 2000-December-8 Jason Rohrer + * Changed so that key state functions take a string instead of + * an integer vkey code. + * + * 2006-June-26 Jason Rohrer + * Added function to get events that are waiting in the queue. + */ + +#ifndef KEYBOARD_INCLUDED +#define KEYBOARD_INCLUDED + + + +/** + * Interface for accessing keyboard input. + * + * Note: + * Certain implementations may require a ScreenGraphics object to + * be constructed before accessing the keyboard (i.e., to give the + * keyboard input a context). + */ +class Keyboard { + + public: + + /* + * Key descriptions: + * + * For the standard ascii characters, pass in the string containing + * the lower case character, for example, "a" for the character A. + * + * For other keys, the descriptors have not been defined yet. + */ + + + /** + * Gets whether a key is down. + * + * @param inKeyDescription string describing the queried key. + * + * @return true if key represented by given key code is down. + */ + //char getKeyDown( int vKeyCode ); + char getKeyDown( const char *inKeyDescription ); + + + /** + * Gets whether a key is up. + * + * @param inKeyDescription string describing the queried key. + * + * @return true if key represented by given key code is up. + */ + //char getKeyUp( int vKeyCode ); + char getKeyUp( const char *inKeyDescription ); + + + + /** + * Gets the next keyboard event. + * + * @return the ASCII encoding of the pressed key, or -1 if no + * keyboard events are waiting. + */ + int getKeyPressedEvent(); + + + }; + + +#endif diff --git a/minorGems/ui/Mouse.h b/minorGems/ui/Mouse.h new file mode 100644 index 0000000..96d2c23 --- /dev/null +++ b/minorGems/ui/Mouse.h @@ -0,0 +1,97 @@ +/* + * Modification History + * + * 2000-November-28 Jason Rohrer + * Created. + */ + +#ifndef MOUSE_INCLUDED +#define MOUSE_INCLUDED + + + +/** + * Interface for accessing mouse input. + * + * Note: + * Certain implementations may require a ScreenGraphics object to + * be constructed before accessing the mouse (i.e., to give the + * mouse coordinates a context). + */ +class Mouse { + + public: + + + /** + * Constructs a Mouse. + * + * @param inNumButtons the number of buttons on the mouse that should + * be monitored. Default = 1. + */ + Mouse( int inNumButtons ); + + ~Mouse(); + + + /** + * Gets the location of the mouse. + * + * @param outX pointer to location where x component will be returned. + * @param outY pointer to location where y component will be returned. + */ + void getLocation( int *outX, int *outY ); + + + /** + * Gets whether the main mouse button is down. + * + * @return true if the main mouse button is down. + */ + char isButtonDown(); + + + /** + * Gets whether a specified mouse button is down. + * + * @param inButtonNumber the number of the button to check for + * (0 is the main mouse button). + * + * @return true if the specified mouse button is down. + */ + char isButtonDown( int inButtonNumber ); + + + private: + int mXLocation; + int mYLocation; + + int mNumButtons; + + // array of booleans that represent the current + // (or last known) state of each button + char *mButtonDown; + }; + + + +inline Mouse::Mouse( int inNumButtons = 1 ) + : mNumButtons( inNumButtons ), + mButtonDown( new char[inNumButtons] ) { + + } + + + +inline Mouse::~Mouse() { + delete [] mButtonDown; + } + + + +inline char Mouse::isButtonDown() { + return isButtonDown( 0 ); + } + + +#endif diff --git a/minorGems/ui/Plot.cpp b/minorGems/ui/Plot.cpp new file mode 100644 index 0000000..700e399 --- /dev/null +++ b/minorGems/ui/Plot.cpp @@ -0,0 +1,131 @@ +// Jason Rohrer +// Plot.cpp + +/** +* +* Scrolling Plot Gui element implementation +* +* +* Created 11-7-99 +* Mods: +* Jason Rohrer 11-8-99 Changed to use GraphicBuffer object as screen buffer +* +*/ + + +#include "Plot.h" + + +Plot::Plot( int x, int y, int w, int h, Color bordC, Color bC, Color lnC ) { + + startX = x; + startY = y; + wide = w; + high = h; + + innerWide = wide - 2*borderWide; + innerHigh = high - 2*borderWide; + + + borderC = bordC; + bgC = bC; + lineC = lnC; + + + imageMap = new unsigned long[high * wide]; + + mapYOffset = new int[high]; + + // precalc y offsets into 2d image map + for( int y=0; yhigh-borderWide-1 || x>wide-borderWide-1 ) { + imageMap[ yContrib + x ] = borderC.composite; // border + } + else { + imageMap[ yContrib + x ] = bgC.composite; // background + } + + } + + } + + + plotVals = new float[innerWide]; + + for( int i=0; i 0) { + invLargest = 1/largest; + } + + + // fill plot with bg color + for( int y=borderWide; y innerHigh + borderWide ) y = innerHigh + borderWide -1; + if( y < borderWide ) y = borderWide; + + imageMap[ mapYOffset[y] + x ] = lineC.composite; // line + } + + +/* for( int x=0; x +#include "Color.h" +#include "GraphicBuffer.h" + +class Plot { + + public: + // construct a plot in a specific location with + // specific border, background, and line colors + Plot( int x, int y, int w, int h, Color bordC, Color bC, Color lnC ); + ~Plot(); + + // add a point to the right of plot, cause plot to scroll left + void addPoint( float p ); + + // draw plot into a graphic buffer + void draw( GraphicBuffer &buff ); + + // erase plot from buffer + void erase( GraphicBuffer &buff, Color &bgColor ); + + private: + + unsigned long *imageMap; + + int startX; + int startY; + int high; + int wide; + + int innerHigh; // width and heigth of area inside borders + int innerWide; + + int *mapYOffset; + + Color borderC; // color of border + Color bgC; // color of progress bar max + + Color lineC; // color of plot line + + + + static const int borderWide = 2; + + + float *plotVals; // values of plot line + + }; + + + + +inline void Plot::draw( GraphicBuffer &buff ) { + buff.drawImage(imageMap, mapYOffset, startX, startY, wide, high); + } + +inline void Plot::erase( GraphicBuffer &buff, Color &bgColor ) { + buff.eraseImage(startX, startY, wide, high, bgColor); + } + + + +#endif \ No newline at end of file diff --git a/minorGems/ui/ProgressBar.cpp b/minorGems/ui/ProgressBar.cpp new file mode 100644 index 0000000..ed118e7 --- /dev/null +++ b/minorGems/ui/ProgressBar.cpp @@ -0,0 +1,122 @@ +// Jason Rohrer +// ProgressBar.cpp + +/** +* +* ProgressBar Gui element implementation +* +* +* Created 11-6-99 +* Mods: +* Jason Rohrer 11-8-99 Changed to use GraphicBuffer object as screen buffer +* +*/ + + + +#include "ProgressBar.h" + + +ProgressBar::ProgressBar(int x, int y, int w, int h, Color bordC, Color hC, Color tipC) { + + startX = x; + startY = y; + wide = w; + high = h; + + barWide = wide - 2*borderWide; + barHigh = high - 2*borderWide; + + + borderC = bordC; + highC = hC; + barTipC = tipC; + + + imageMap = new unsigned long[high * wide]; + + mapYOffset = new int[high]; + + // precalc y offsets into 2d image map + for( int y=0; yhigh-borderWide-1 || x>wide-borderWide-1 ) { + imageMap[ yContrib + x ] = borderC.composite; // border + } + else { + imageMap[ yContrib + x ] = 0xFF000000; // black inside + } + + } + + } + + lastProgress = 0; + } + + +ProgressBar::~ProgressBar() { + delete [] imageMap; + delete [] mapYOffset; + } + + +void ProgressBar::setProgress(float fractionFull) { + + if( fractionFull < 0) fractionFull = 0; + if( fractionFull > 1) fractionFull = 1; + + if( fractionFull < lastProgress ) { // decreasing proress, erase part of bar + + + int deleteXStart = (int)((fractionFull) * barWide + borderWide); + + int deleteXEnd = (int)((lastProgress) * barWide + borderWide); + + + for( int y=borderWide; y lastProgress) { //progress has increased + + int addXStart = (int)((lastProgress) * barWide + borderWide); + int addXEnd = (int)((fractionFull) * barWide + borderWide); + + float weight = lastProgress; + float deltaWeight = (fractionFull - lastProgress) / (addXEnd - addXStart); + + for( int x=addXStart; x +#include "SetMouse.h" + +void SetMouse (int x, int y) { + Point base; + // This routine donated to MacMAME by John Stiles + // Picked up for RadiosGL from the Mac GLQuake site + Point *RawMouse = (Point*) 0x82C; + Point *MTemp = (Point*) 0x828; + Ptr CrsrNew = (Ptr) 0x8CE; + Ptr CrsrCouple = (Ptr) 0x8CF; + + base.v = y; + base.h = x; + LocalToGlobal(&base); + + *RawMouse = base; + *MTemp = base; + *CrsrNew = *CrsrCouple; + } + +// do nothing, these are needed in windows only +void CaptureMouse() { + } +void ReleaseMouse() { + } \ No newline at end of file diff --git a/minorGems/ui/SetMouseWin32.cpp b/minorGems/ui/SetMouseWin32.cpp new file mode 100644 index 0000000..ff82bf3 --- /dev/null +++ b/minorGems/ui/SetMouseWin32.cpp @@ -0,0 +1,50 @@ +// Jason Rohrer +// SetMouseWin32.cpp + +/** +* +* implementation of SetMouse on Win32 +* This uses an "official" os feature (unlike the hacked mac version +* +* Created 1-16-2000 +* Mods: +* Jason Rohrer 1-18-2000 Added conversion from client to screen coords. +* GLUT tracks mouse motion relative to window +* Windows can only set the cursor position using +* screen coordinates. +*/ + + + +#include + +#include "SetMouse.h" + +char captured = false; + +void SetMouse( int x, int y ) { + + POINT p; + p.x = x; + p.y = y; + + HWND window = GetActiveWindow(); + + ClientToScreen( window, &p ); + + SetCursorPos( p.x, p.y ); + + //SetCursorPos( x, y ); + + } + + +// send all mouse movements to our window, even those outside the border +void CaptureMouse() { + HWND window = GetActiveWindow(); + SetCapture( window ); + } + +void ReleaseMouse() { + ReleaseCapture(); + } \ No newline at end of file diff --git a/minorGems/ui/event/ActionListener.h b/minorGems/ui/event/ActionListener.h new file mode 100644 index 0000000..5b5dd15 --- /dev/null +++ b/minorGems/ui/event/ActionListener.h @@ -0,0 +1,54 @@ +/* + * Modification History + * + * 2001-September-15 Jason Rohrer + * Created. + * + * 2006-July-3 Jason Rohrer + * Fixed warnings. + */ + + +#ifndef ACTION_LISTENER_INCLUDED +#define ACTION_LISTENER_INCLUDED + + +#include "minorGems/ui/GUIComponent.h" + + +/** + * An interface for a class that can handle a GUI action. + * + * @author Jason Rohrer + */ +class ActionListener { + + + public: + + + virtual ~ActionListener(); + + + /** + * Tells this class that an action has been performed. + * + * @param inTarget the GUI component on which the action + * is being performed. + */ + virtual void actionPerformed( GUIComponent *inTarget ) = 0; + + + }; + + + +inline ActionListener::~ActionListener() { + } + + + +#endif + + + diff --git a/minorGems/ui/event/ActionListenerList.h b/minorGems/ui/event/ActionListenerList.h new file mode 100644 index 0000000..a233892 --- /dev/null +++ b/minorGems/ui/event/ActionListenerList.h @@ -0,0 +1,143 @@ +/* + * Modification History + * + * 2001-September-15 Jason Rohrer + * Created. + * + * 2001-September-17 Jason Rohrer + * Fixed a missing include. + * + * 2006-July-3 Jason Rohrer + * Fixed warnings. + * + * 2009-December-22 Jason Rohrer + * New SimpleVector delete call. + * + * 2010-May-19 Jason Rohrer + * Added call to check if a listener is on the list. + */ + + +#ifndef ACTION_LISTENER_LIST_INCLUDED +#define ACTION_LISTENER_LIST_INCLUDED + + +#include "ActionListener.h" +#include "minorGems/ui/GUIComponent.h" +#include "minorGems/util/SimpleVector.h" + + +/** + * A utility class to be subclassed by classes needing + * to handle a list of action listeners. + * + * @author Jason Rohrer + */ +class ActionListenerList : protected SimpleVector { + + + public: + + + + virtual ~ActionListenerList(); + + + + /** + * Adds an action listener. + * + * @param inListener the listener to add. Must + * be destroyed by caller after this class has been destroyed. + */ + virtual void addActionListener( ActionListener *inListener ); + + + + /** + * Removes an action listener. + * + * @param inListener the listener to remove. Must + * be destroyed by caller. + */ + virtual void removeActionListener( ActionListener *inListener ); + + + + /** + * Checks if action listener added + * + * @param inListener the listener to look for. Must + * be destroyed by caller. + * + * @return true iff listener is on the list + */ + virtual char isListening( ActionListener *inListener ); + + + + /** + * Tells all registered listeners that an action has been + * performed. + * + * @param inTarget the GUI component on which the action + * is being performed. + */ + virtual void fireActionPerformed( GUIComponent *inTarget ); + + + + }; + + + +inline ActionListenerList::~ActionListenerList() { + + } + + + +inline void ActionListenerList::addActionListener( + ActionListener *inListener ) { + + push_back( inListener ); + } + + + +inline void ActionListenerList::removeActionListener( + ActionListener *inListener ) { + + deleteElementEqualTo( inListener ); + } + + + +inline char ActionListenerList::isListening( + ActionListener *inListener ) { + + int index = getElementIndex( inListener ); + + if( index == -1 ) { + return false; + } + else { + return true; + } + } + + + +inline void ActionListenerList::fireActionPerformed( GUIComponent *inTarget ) { + for( int i=0; iactionPerformed( inTarget ); + } + } + + + +#endif + + + diff --git a/minorGems/ui/linux/KeyboardLinux.cpp b/minorGems/ui/linux/KeyboardLinux.cpp new file mode 100644 index 0000000..30d9332 --- /dev/null +++ b/minorGems/ui/linux/KeyboardLinux.cpp @@ -0,0 +1,180 @@ +/* + * Modification History + * + * 2000-December-7 Jason Rohrer + * Created. + * + * 2000-December-8 Jason Rohrer + * Changed so that key state functions take a string instead of + * an integer vkey code. + * + * 2001-May-2 Jason Rohrer + * Changed to use more standard SDL include location. + * + * 2006-June-26 Jason Rohrer + * Added function to get events that are waiting in the queue. + */ + +#include "minorGems/ui/Keyboard.h" + +#include + +#include + +/** + * Note: Linux implementation: + * Requires that a ScreenGraphics be constructed before accessing the keyboard. + */ + +// prototypes: + +/** + * Maps an ascii string description of a key, such as "a", to an SDL keycode. + * + * @param inKeyDescription an ascii description of a key. + * + * @return the SDL keycode. + */ +int getKeyCode( const char *inKeyDescription ); + + + +/** + * Maps a keycode to an ascii character. + * + * @param inSDLKeycode the keycode. + * + * @return the ascii character, or -1 if the keycode is not mappable to ascii. + */ +int getKeyASCII( int inSDLKeycode ); + + + + +#define M_KEY SDLK_m +#define N_KEY SDLK_n + +#define S_KEY SDLK_s + +#define Q_KEY SDLK_q + +#define L_KEY SDLK_l + +#define R_KEY SDLK_r + +#define T_KEY SDLK_t + + + +//char Keyboard::getKeyDown( int vKeyCode ) { +char Keyboard::getKeyDown( const char *inKeyDescription ) { + SDL_PumpEvents(); + Uint8 *keys; + keys = SDL_GetKeyState( NULL ); + return keys[ getKeyCode( inKeyDescription ) ] == SDL_PRESSED; + } + + + +//char Keyboard::getKeyUp( int vKeyCode ) { +char Keyboard::getKeyUp( const char *inKeyDescription ) { + SDL_PumpEvents(); + Uint8 *keys; + keys = SDL_GetKeyState( NULL ); + return keys[ getKeyCode( inKeyDescription ) ] == SDL_RELEASED; + } + + + +int Keyboard::getKeyPressedEvent() { + + SDL_Event event; + + if( SDL_PollEvent( &event ) ) { + switch( event.type ) { + case SDL_KEYDOWN: + return getKeyASCII( event.key.keysym.sym ); + break; + } + } + else { + return -1; + } + } + + + +int getKeyCode( const char *inKeyDescription ) { + + // note that strcmp functions return 0 if strings match + + if( !strcasecmp( inKeyDescription, "m" ) ) { + return SDLK_m; + } + else if( !strcasecmp( inKeyDescription, "n" ) ) { + return SDLK_n; + } + else if( !strcasecmp( inKeyDescription, "s" ) ) { + return SDLK_s; + } + else if( !strcasecmp( inKeyDescription, "q" ) ) { + return SDLK_q; + } + else if( !strcasecmp( inKeyDescription, "l" ) ) { + return SDLK_l; + } + else if( !strcasecmp( inKeyDescription, "r" ) ) { + return SDLK_r; + } + else if( !strcasecmp( inKeyDescription, "t" ) ) { + return SDLK_t; + } + + } + + + +int getKeyASCII( int inSDLKeycode ) { + switch( inSDLKeycode ) { + case SDLK_m: + return 'm'; + break; + case SDLK_n: + return 'n'; + break; + case SDLK_s: + return 's'; + break; + case SDLK_q: + return 'a'; + break; + case SDLK_l: + return 'l'; + break; + case SDLK_r: + return 'r'; + break; + case SDLK_t: + return 't'; + break; + default: + return -1; + break; + } + } + + +/* +#define M_KEY SDLK_m +#define N_KEY SDLK_n + +#define S_KEY SDLK_s + +#define Q_KEY SDLK_q + +#define L_KEY SDLK_l + +#define R_KEY SDLK_r + +#define T_KEY SDLK_t +*/ diff --git a/minorGems/ui/linux/MouseLinux.cpp b/minorGems/ui/linux/MouseLinux.cpp new file mode 100644 index 0000000..8647c4e --- /dev/null +++ b/minorGems/ui/linux/MouseLinux.cpp @@ -0,0 +1,73 @@ +/* + * Modification History + * + * 2000-November-28 Jason Rohrer + * Created. + * + * 2001-May-2 Jason Rohrer + * Changed to use more standard SDL include location. + */ + +#include "minorGems/ui/Mouse.h" + +#include + +/** + * Note: Linux implementation: + * Requires that a ScreenGraphics be constructed before accessing the mouse. + */ + + + +void Mouse::getLocation( int *outX, int *outY ) { + SDL_PumpEvents(); + SDL_GetMouseState( outX, outY ); + /* + SDL_PumpEvents( void ); + + int numEventsToGet = 99; + + SDL_Event *events = new SDL_Event[numEventsToGet]; + + // get events from the queue + int numEventsRetrieved = SDL_PeepEvents( events, numEventsToGet, + SDL_GETEVENT, SDL_MOUSEMOTIONMASK ); + + // for mouse motion, we only care about the last event + SDL_Event lastEvent = events[ numEventsRetrieved - 1 ]; + + delete [] events; + */ + } + + +char Mouse::isButtonDown( int inButtonNumber ) { + SDL_PumpEvents(); + + int x, y; + + Uint8 buttonState = SDL_GetMouseState( &x, &y ); + + if( inButtonNumber == 0 ) { + return( buttonState & SDL_BUTTON_LMASK ); + } + + if( mNumButtons >=3 ) { + // if we care about 3 buttons, then count the middle button + // as button 1 + if( inButtonNumber == 1 ) { + return( buttonState & SDL_BUTTON_MMASK ); + } + if( inButtonNumber == 2 ) { + return( buttonState & SDL_BUTTON_RMASK ); + } + } + else { + // we care about 2 or fewer buttons + if( inButtonNumber == 1 ) { + return( buttonState & SDL_BUTTON_RMASK ); + } + } + + return false; + } diff --git a/minorGems/util/CircularBuffer.h b/minorGems/util/CircularBuffer.h new file mode 100644 index 0000000..b279957 --- /dev/null +++ b/minorGems/util/CircularBuffer.h @@ -0,0 +1,167 @@ +/* + * Modification History + * + * 2001-Janary-11 Jason Rohrer + * Created. + * + * 2001-Janary-12 Jason Rohrer + * Added canRead and canWrite functions. + * + * 2001-February-24 Jason Rohrer + * Fixed incorrect delete usage. + */ + +#include "minorGems/common.h" + + + +#ifndef CIRCULAR_BUFFER_INCLUDED +#define CIRCULAR_BUFFER_INCLUDED + + +#include "minorGems/system/Semaphore.h" + + +/** + * Thread-safe circular buffer. + * + * @author Jason Rohrer + */ +class CircularBuffer { + + public: + + /** + * Constructs a CircularBuffer. + * + * @param inSize the number of objects in this buffer. + */ + CircularBuffer( unsigned long inSize ); + + + ~CircularBuffer(); + + + /** + * Writes an object into the next free position in the buffer. + * Blocks if no free positions are available. + * + * @param inObject the object pointer to write. + */ + void writeObject( void *inObject ); + + + /** + * Reads the next available object from the buffer. + * Blocks if now objects are available. + * + * @return the object pointer read. + */ + void *readNextObject(); + + + /** + * Returns true if an object can be read from this buffer + * without blocking. + */ + char canRead(); + + + /** + * Returns true if an object can be written to this buffer + * without blocking. + */ + char canWrite(); + + + private: + unsigned long mBufferSize; + + void **mObjects; + + unsigned long mReadIndex, mWriteIndex; + + // initialized to 0 + Semaphore *mReadSemaphore; + + // initialized to mBufferSize; + Semaphore *mWriteSemaphore; + + MutexLock *mLock; + }; + + + +inline CircularBuffer::CircularBuffer( unsigned long inSize ) + : mBufferSize( inSize ), mObjects( new void*[inSize] ), + mReadIndex( 0 ), mWriteIndex( 0 ), + mReadSemaphore( new Semaphore( 0 ) ), + mWriteSemaphore( new Semaphore( inSize ) ), + mLock( new MutexLock() ) { + + } + + + +inline CircularBuffer::~CircularBuffer() { + delete [] mObjects; + delete mReadSemaphore; + delete mWriteSemaphore; + delete mLock; + } + + + +// note that in this implementation, no mutex is needed, since +// reader and writer are never accessing the same data members +// simultaneously + +inline void CircularBuffer::writeObject( void *inObject ) { + // wait to for a space to write into + mWriteSemaphore->wait(); + + // write, and increment our write location + mObjects[ mWriteIndex ] = inObject; + mWriteIndex++; + mWriteIndex = mWriteIndex % mBufferSize; + + // signal the reader that an new object is ready + mReadSemaphore->signal(); + } + + +inline void *CircularBuffer::readNextObject() { + void *returnObject; + + // wait for an object to read + mReadSemaphore->wait(); + + // read the object, and increment our read location + returnObject = mObjects[ mReadIndex ]; + mReadIndex++; + mReadIndex = mReadIndex % mBufferSize; + + // signal the writer + mWriteSemaphore->signal(); + + // now return the object that we retrieved from the buffer + return returnObject; + } + + + +inline char CircularBuffer::canRead() { + // can read if the read semaphore won't block + return ! mReadSemaphore->willBlock(); + } + + + +inline char CircularBuffer::canWrite() { + // can write if the write semaphore won't block + return ! mWriteSemaphore->willBlock(); + } + + + +#endif diff --git a/minorGems/util/SettingsManager.cpp b/minorGems/util/SettingsManager.cpp new file mode 100644 index 0000000..c8d0895 --- /dev/null +++ b/minorGems/util/SettingsManager.cpp @@ -0,0 +1,393 @@ +/* + * Modification History + * + * 2002-September-16 Jason Rohrer + * Created. + * Fixed a memory leak. + * + * 2002-September-25 Jason Rohrer + * Added a setSetting function that takes a string value. + * + * 2002-September-26 Jason Rohrer + * Added functions for integer values. + * + * 2003-January-11 Jason Rohrer + * Added default values for float and int settings. + * + * 2003-August-24 Jason Rohrer + * Fixed to remove 499-character limit for a setting value. + * + * 2009-February-12 Jason Rohrer + * Added support for secure hashing. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + + +#include "SettingsManager.h" + + +#include "minorGems/util/stringUtils.h" +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/Path.h" + +#include "minorGems/crypto/hashes/sha1.h" + + + +// will be destroyed automatically at program termination +SettingsManagerStaticMembers SettingsManager::mStaticMembers; + +char SettingsManager::mHashingOn = false; + + + +void SettingsManager::setDirectoryName( const char *inName ) { + delete [] mStaticMembers.mDirectoryName; + mStaticMembers.mDirectoryName = stringDuplicate( inName ); + } + + + +char *SettingsManager::getDirectoryName() { + return stringDuplicate( mStaticMembers.mDirectoryName ); + } + + + +void SettingsManager::setHashSalt( const char *inSalt ) { + delete [] mStaticMembers.mHashSalt; + mStaticMembers.mHashSalt = stringDuplicate( inSalt ); + } + + + +char *SettingsManager::getHashSalt() { + return stringDuplicate( mStaticMembers.mHashSalt ); + } + + + +void SettingsManager::setHashingOn( char inOn ) { + mHashingOn = inOn; + } + + + + +SimpleVector *SettingsManager::getSetting( + const char *inSettingName ) { + + char *fileName = getSettingsFileName( inSettingName ); + File *settingsFile = new File( NULL, fileName ); + + delete [] fileName; + + char *fileContents = settingsFile->readFileContents(); + + delete settingsFile; + + + + if( fileContents == NULL ) { + // return empty vector + return new SimpleVector(); + } + + if( mHashingOn ) { + + char *hashFileName = getSettingsFileName( inSettingName, "hash" ); + + File *hashFile = new File( NULL, hashFileName ); + + delete [] hashFileName; + + char *savedHash = hashFile->readFileContents(); + + delete hashFile; + + if( savedHash == NULL ) { + printf( "Hash missing for setting %s\n", inSettingName ); + + delete [] fileContents; + return new SimpleVector(); + } + + + // compute hash + char *stringToHash = autoSprintf( "%s%s", + fileContents, + mStaticMembers.mHashSalt ); + + char *hash = computeSHA1Digest( stringToHash ); + + delete [] stringToHash; + + int difference = strcmp( hash, savedHash ); + + delete [] hash; + delete [] savedHash; + + + if( difference != 0 ) { + printf( "Hash mismatch for setting %s\n", inSettingName ); + + delete [] fileContents; + return new SimpleVector(); + } + } + + + // else tokenize the file contents + SimpleVector *returnVector = tokenizeString( fileContents ); + + delete [] fileContents; + + return returnVector; + } + + + +char *SettingsManager::getStringSetting( const char *inSettingName ) { + char *value = NULL; + + SimpleVector *settingsVector = getSetting( inSettingName ); + + int numStrings = settingsVector->size(); + if( numStrings >= 1 ) { + + char *firstString = *( settingsVector->getElement( 0 ) ); + + value = stringDuplicate( firstString ); + } + + for( int i=0; igetElement( i ) ); + + delete [] nextString; + } + + delete settingsVector; + + return value; + } + + + +float SettingsManager::getFloatSetting( const char *inSettingName, + char *outValueFound ) { + + char valueFound = false; + float value = 0; + + + char *stringValue = getStringSetting( inSettingName ); + + if( stringValue != NULL ) { + + int numRead = sscanf( stringValue, "%f", + &value ); + + if( numRead == 1 ) { + valueFound = true; + } + + delete [] stringValue; + } + + *outValueFound = valueFound; + + return value; + } + + + +int SettingsManager::getIntSetting( const char *inSettingName, + char *outValueFound ) { + + char valueFound = false; + int value = 0; + + + char *stringValue = getStringSetting( inSettingName ); + + if( stringValue != NULL ) { + + int numRead = sscanf( stringValue, "%d", + &value ); + + if( numRead == 1 ) { + valueFound = true; + } + + delete [] stringValue; + } + + *outValueFound = valueFound; + + return value; + } + + + +void SettingsManager::setSetting( const char *inSettingName, + SimpleVector *inSettingVector ) { + + + + + char **settingParts = inSettingVector->getElementArray(); + + char *settingString = join( settingParts, inSettingVector->size(), + "\n" ); + delete [] settingParts; + + + if( mHashingOn ) { + + // compute hash + char *stringToHash = autoSprintf( "%s%s", + settingString, + mStaticMembers.mHashSalt ); + + char *hash = computeSHA1Digest( stringToHash ); + + delete [] stringToHash; + + char *hashFileName = getSettingsFileName( inSettingName, "hash" ); + + FILE *file = fopen( hashFileName, "w" ); + + delete [] hashFileName; + + if( file != NULL ) { + fprintf( file, "%s", hash ); + + fclose( file ); + } + + delete [] hash; + } + + + + + FILE *file = getSettingsFile( inSettingName, "w" ); + + if( file != NULL ) { + + fprintf( file, "%s", settingString ); + + fclose( file ); + } + + delete [] settingString; + + // else do nothing + } + + + +void SettingsManager::setSetting( const char *inSettingName, + float inSettingValue ) { + + char *valueString = new char[ 15 ]; + sprintf( valueString, "%f", inSettingValue ); + + setSetting( inSettingName, valueString ); + + delete [] valueString; + } + + + +void SettingsManager::setSetting( const char *inSettingName, + int inSettingValue ) { + + char *valueString = new char[ 15 ]; + sprintf( valueString, "%d", inSettingValue ); + + setSetting( inSettingName, valueString ); + + delete [] valueString; + } + + + +void SettingsManager::setSetting( const char *inSettingName, + const char *inSettingValue ) { + SimpleVector *settingsVector = new SimpleVector( 1 ); + + settingsVector->push_back( (char *)inSettingValue ); + + setSetting( inSettingName, settingsVector ); + + delete settingsVector; + } + + + +FILE *SettingsManager::getSettingsFile( const char *inSettingName, + const char *inReadWriteFlags ) { + char *fullFileName = getSettingsFileName( inSettingName ); + + FILE *file = fopen( fullFileName, inReadWriteFlags ); + + delete [] fullFileName; + + return file; + } + + + +char *SettingsManager::getSettingsFileName( const char *inSettingName ) { + return getSettingsFileName( inSettingName, "ini" ); + } + + + +char *SettingsManager::getSettingsFileName( const char *inSettingName, + const char *inExtension ) { + char **pathSteps = new char*[1]; + + pathSteps[0] = mStaticMembers.mDirectoryName; + + char *fileName = new char[ strlen( inSettingName ) + + strlen( inExtension ) + + 2 ]; + + sprintf( fileName, "%s.%s", inSettingName, inExtension ); + + File *settingsFile = new File( new Path( pathSteps, 1, false ), + fileName ); + + delete [] fileName; + + // pathSteps copied internally by Path constructor + delete [] pathSteps; + + + char *fullFileName = settingsFile->getFullFileName(); + + delete settingsFile; + + return fullFileName; + } + + + +SettingsManagerStaticMembers::SettingsManagerStaticMembers() + : mDirectoryName( stringDuplicate( "settings" ) ), + mHashSalt( stringDuplicate( "default_salt" ) ) { + + } + + + +SettingsManagerStaticMembers::~SettingsManagerStaticMembers() { + delete [] mDirectoryName; + delete [] mHashSalt; + } + diff --git a/minorGems/util/SettingsManager.h b/minorGems/util/SettingsManager.h new file mode 100644 index 0000000..38775e2 --- /dev/null +++ b/minorGems/util/SettingsManager.h @@ -0,0 +1,308 @@ +/* + * Modification History + * + * 2002-September-16 Jason Rohrer + * Created. + * Fixed a memory leak. + * + * 2002-September-25 Jason Rohrer + * Added a setSetting function that takes a string value. + * + * 2002-September-26 Jason Rohrer + * Added functions for integer values. + * + * 2003-August-24 Jason Rohrer + * Fixed to remove 499-character limit for a setting value. + * + * 2009-February-11 Jason Rohrer + * Made getSettingsFile public to support arbitry binary data in settings. + * + * 2009-February-12 Jason Rohrer + * Added support for secure hashing. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef SETTINGS_MANAGER_INCLUDED +#define SETTINGS_MANAGER_INCLUDED + + + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/system/MutexLock.h" + +#include + + + +// utility class for dealing with static member dealocation +class SettingsManagerStaticMembers; + + + +/** + * Class that manages program settings. + * + * @author Jason Rohrer + */ +class SettingsManager { + + + + public: + + + + /** + * Sets the directory name where settings are stored. + * + * @param inName the name of the directory (relative to the + * program's working directory). + * Must be destroyed by caller if non-const. + */ + static void setDirectoryName( const char *inName ); + + + + /** + * Gets the directory name where settings are stored. + * + * @return the name of the directory (relative to the + * program's working directory). + * Must be destroyed by caller. + */ + static char *getDirectoryName(); + + + + /** + * Sets the salt to be used when hashing settings. + * + * @param inSalt the salt as an ASCII string. + * Must be destroyed by caller if non-const. + */ + static void setHashSalt( const char *inSalt ); + + + + /** + * Gets the salt to be used when hashing settings. + * + * @return the hash salt. + * Must be destroyed by caller. + */ + static char *getHashSalt(); + + + + /** + * Turns hashing on or off. If off, manager will reject + * settings in folder that do not have proper hashes. + * + * @param inOn true to turn hashing on. + */ + static void setHashingOn( char inOn ); + + + + /** + * Gets a setting. + * + * @param inSettingName the name of the setting to get. + * Must be destroyed by caller if non-const. + * + * @return a vector of strings representing the setting value. + * The vector and the strings it contains must be destroyed + * by the caller. + */ + static SimpleVector *getSetting( const char *inSettingName ); + + + + /** + * Gets a string setting. + * + * @param inSettingName the name of the setting to get. + * Must be destroyed by caller if non-const. + * + * @return the setting value string, or NULL if no setting + * value can be read. + * Must be destroyed by caller if non-NULL. + */ + static char *getStringSetting( const char *inSettingName ); + + + /** + * Gets a float setting. + * + * @param inSettingName the name of the setting to get. + * Must be destroyed by caller if non-const. + * @param outValueFound pointer to where flag should be returned + * indicating whether or not the value was found. + * Set to true if the value was found, false otherwise. + * + * @return the setting value. + */ + static float getFloatSetting( const char *inSettingName, + char *outValueFound ); + + + + /** + * Gets an integer setting. + * + * @param inSettingName the name of the setting to get. + * Must be destroyed by caller if non-const. + * @param outValueFound pointer to where flag should be returned + * indicating whether or not the value was found. + * Set to true if the value was found, false otherwise. + * + * @return the setting value. + */ + static int getIntSetting( const char *inSettingName, + char *outValueFound ); + + + + /** + * Sets a setting. + * + * @param inSettingName the name of the setting to set. + * Must be destroyed by caller if non-const. + * @param inSettingVector a vector of strings representing the + * setting value. + * The vector and the strings it contains must be destroyed + * by the caller. + */ + static void setSetting( const char *inSettingName, + SimpleVector *inSettingVector ); + + + + /** + * Sets a setting to a single float value. + * + * @param inSettingName the name of the setting to set. + * Must be destroyed by caller if non-const. + * @param inSettingValue the value to set. + */ + static void setSetting( const char *inSettingName, + float inSettingValue ); + + + + /** + * Sets a setting to a single int value. + * + * @param inSettingName the name of the setting to set. + * Must be destroyed by caller if non-const. + * @param inSettingValue the value to set. + */ + static void setSetting( const char *inSettingName, + int inSettingValue ); + + + + /** + * Sets a setting to a single string value. + * + * @param inSettingName the name of the setting to set. + * Must be destroyed by caller if non-const. + * @param inSettingValue the value to set. + * Must be destroyed by caller. + */ + static void setSetting( const char *inSettingName, + const char *inSettingValue ); + + + + /** + * Gets the file for a setting name. + * + * @param inSettingName the name of the setting. + * Must be destroyed by caller if non-const. + * @param inReadWriteFlags the flags to pass into the + * fopen call. For example, "r" or "w". + * + * @return the file descriptor, or NULL if the open failed. + * Must be fclose()'d by caller if non-NULL. + */ + static FILE *getSettingsFile( const char *inSettingName, + const char *inReadWriteFlags ); + + + + protected: + + + + static SettingsManagerStaticMembers mStaticMembers; + + static char mHashingOn; + + + /** + * Gets the file name for a setting with the default ini extension. + * The .ini extension is added automatically by this call. + * + * @param inSettingName the name of the setting. + * Must be destroyed by caller if non-const. + * + * @return the name of the settings file. + * Must be destroyed by caller. + */ + static char *getSettingsFileName( const char *inSettingName ); + + + /** + * Gets the file name for a setting with a custom extension. + * The extension should be passed in without a period in it. + * + * @param inSettingName the name of the setting. + * Must be destroyed by caller if non-const. + * @param inExtension the extension to add to the name. + * Example: "ini" + * Must be destroyed by caller if non-const. + * + * @return the name of the settings file. + * Must be destroyed by caller. + */ + static char *getSettingsFileName( const char *inSettingName, + const char *inExtension ); + + }; + + + +/** + * Container for static members to allow for their proper destruction + * on program termination. + * + * @author Jason Rohrer + */ +class SettingsManagerStaticMembers { + + + + public: + + + + SettingsManagerStaticMembers(); + ~SettingsManagerStaticMembers(); + + char *mDirectoryName; + char *mHashSalt; + + + + }; + + + +#endif diff --git a/minorGems/util/SimpleVector.h b/minorGems/util/SimpleVector.h new file mode 100644 index 0000000..82e7aa0 --- /dev/null +++ b/minorGems/util/SimpleVector.h @@ -0,0 +1,464 @@ +// Jason Rohrer +// SimpleVector.h + +/** +* +* Simple vector template class. Supports pushing at end and random-access deletions. +* Dynamically sized. +* +* +* Created 10-24-99 +* Mods: +* Jason Rohrer 12-11-99 Added deleteAll function +* Jason Rohrer 1-30-2000 Changed to return NULL if get called on non-existent element +* Jason Rohrer 12-20-2000 Added a function for deleting a particular +* element. +* Jason Rohrer 12-14-2001 Added a function for getting the index of +* a particular element. +* Jason Rohrer 1-24-2003 Added a functions for getting an array or +* string of all elements. +* Jason Rohrer 7-26-2005 Added template <> to explicitly specialized +* getElementString. +* Jason Rohrer 1-9-2009 Added setElementString method. +* Jason Rohrer 9-7-2009 Fixed int types. +* Added appendElementString. +* Jason Rohrer 11-11-2009 Changed second deleteElement to allow +* SimpleVectors containing ints. +* Jason Rohrer 12-23-2009 New push_back for arrays. +* Jason Rohrer 1-5-2010 Reduced default size to conserve memory. +* Jason Rohrer 1-12-2010 Added copy constructor and assignment +* operator. +* Jason Rohrer 1-16-2010 Fixed bugs in new constructor/operator. +* Jason Rohrer 1-18-2010 Fixed memmove/memcpy bugs. +* Jason Rohrer 1-28-2010 Data protected for subclass access. +* Jason Rohrer 4-28-2010 Fast version of getElement. +* Jason Rohrer 5-14-2010 String parameters as const to fix warnings. +*/ + +#include "minorGems/common.h" + + + +#ifndef SIMPLEVECTOR_INCLUDED +#define SIMPLEVECTOR_INCLUDED + +#include // for memory moving functions + + +const int defaultStartSize = 2; + +template +class SimpleVector { + public: + + SimpleVector(); // create an empty vector + SimpleVector(int sizeEstimate); // create an empty vector with a size estimate + + ~SimpleVector(); + + + // copy constructor + SimpleVector( const SimpleVector &inCopy ); + + // assignment operator + SimpleVector & operator = (const SimpleVector &inOther ); + + + + + void push_back(Type x); // add x to the end of the vector + + // add array of elements to the end of the vector + void push_back(Type *inArray, int inLength); + + + Type *getElement(int index); // get a ptr to element at index in vector + + Type *getElementFast(int index); // no bounds checking + + + int size(); // return the number of allocated elements in the vector + + bool deleteElement(int index); // delete element at an index in vector + + /** + * Deletes a particular element. Deletes the first element + * in vector == to inElement. + * + * @param inElement the element to delete. + * + * @return true iff an element was deleted. + */ + bool deleteElementEqualTo( Type inElement ); + + + + /** + * Gets the index of a particular element. Gets the index of the + * first element in vector == to inElement. + * + * @param inElement the element to get the index of. + * + * @return the index if inElement, or -1 if inElement is not found. + */ + int getElementIndex( Type inElement ); + + + + void deleteAll(); // delete all elements from vector + + + + /** + * Gets the elements as an array. + * + * @return the a new array containing all elements in this vector. + * Must be destroyed by caller, though elements themselves are + * not copied. + */ + Type *getElementArray(); + + + + /** + * Gets the char elements as a \0-terminated string. + * + * @return a \0-terminated string containing all elements in this + * vector. + * Must be destroyed by caller. + */ + char *getElementString(); + + + /** + * Sets the char elements as a \0-terminated string. + * + * @param inString a \0-terminated string containing all elements to + * set this vector with. + * Must be destroyed by caller. + */ + void setElementString( const char *inString ); + + + + /** + * Appends chars from a \0-terminated string. + * + * @param inString a \0-terminated string containing all elements to + * append to this vector. + * Must be destroyed by caller. + */ + void appendElementString( const char *inString ); + + + + protected: + Type *elements; + int numFilledElements; + int maxSize; + int minSize; // number of allocated elements when vector is empty + }; + + +template +inline SimpleVector::SimpleVector() { + elements = new Type[defaultStartSize]; + numFilledElements = 0; + maxSize = defaultStartSize; + minSize = defaultStartSize; + } + +template +inline SimpleVector::SimpleVector(int sizeEstimate) { + elements = new Type[sizeEstimate]; + numFilledElements = 0; + maxSize = sizeEstimate; + minSize = sizeEstimate; + } + +template +inline SimpleVector::~SimpleVector() { + delete [] elements; + } + + + +// copy constructor +template +inline SimpleVector::SimpleVector( const SimpleVector &inCopy ) + : elements( new Type[ inCopy.maxSize ] ), + numFilledElements( inCopy.numFilledElements ), + maxSize( inCopy.maxSize ), minSize( inCopy.minSize ) { + + // if these objects contain pointers to stack, etc, this is not + // going to work (not a deep copy) + // because it won't invoke the copy constructors of the objects! + //memcpy( elements, inCopy.elements, sizeof( Type ) * numFilledElements ); + + for( int i=0; i +inline SimpleVector & SimpleVector::operator = ( + const SimpleVector &inOther ) { + + // pattern found on wikipedia: + + // avoid self-assignment + if( this != &inOther ) { + + // 1: allocate new memory and copy the elements + Type *newElements = new Type[ inOther.maxSize ]; + + // again, memcpy doesn't work here, because it doesn't invoke + // copy constructor on contained object + /*memcpy( newElements, inOther.elements, + sizeof( Type ) * inOther.numFilledElements ); + */ + for( int i=0; i +inline int SimpleVector::size() { + return numFilledElements; + } + +template +inline Type *SimpleVector::getElement(int index) { + if( index < numFilledElements && index >=0 ) { + return &(elements[index]); + } + else return NULL; + } + + +template +inline Type *SimpleVector::getElementFast(int index) { + return &(elements[index]); + } + + +template +inline bool SimpleVector::deleteElement(int index) { + if( index < numFilledElements) { // if index valid for this vector + + if( index != numFilledElements - 1) { + // this spot somewhere in middle + + + + // memmove NOT okay here, because it leaves shallow copies + // behind that cause errors when the whole element array is + // destroyed. + + /* + // move memory towards front by one spot + int sizeOfElement = sizeof(Type); + + int numBytesToMove = sizeOfElement*(numFilledElements - (index+1)); + + Type *destPtr = &(elements[index]); + Type *srcPtr = &(elements[index+1]); + + memmove( (void *)destPtr, (void *)srcPtr, + (unsigned int)numBytesToMove); + */ + + + for( int i=index+1; i +inline bool SimpleVector::deleteElementEqualTo( Type inElement ) { + int index = getElementIndex( inElement ); + if( index != -1 ) { + return deleteElement( index ); + } + else { + return false; + } + } + + + +template +inline int SimpleVector::getElementIndex( Type inElement ) { + // walk through vector, looking for first match. + for( int i=0; i +inline void SimpleVector::deleteAll() { + numFilledElements = 0; + if( maxSize > minSize ) { // free memory if vector has grown + delete [] elements; + elements = new Type[minSize]; // reallocate an empty vector + maxSize = minSize; + } + } + + +template +inline void SimpleVector::push_back(Type x) { + if( numFilledElements < maxSize) { // still room in vector + elements[numFilledElements] = x; + numFilledElements++; + } + else { // need to allocate more space for vector + int newMaxSize = maxSize << 1; // double size + + // NOTE: memcpy does not work here, because it does not invoke + // copy constructors on elements. + // And then "delete []" below causes destructors to be invoked + // on old elements, which are shallow copies of new objects. + + Type *newAlloc = new Type[newMaxSize]; + /* + unsigned int sizeOfElement = sizeof(Type); + unsigned int numBytesToMove = sizeOfElement*(numFilledElements); + + + // move into new space + memcpy((void *)newAlloc, (void *) elements, numBytesToMove); + */ + + // must use element-by-element assignment to invoke constructors + for( int i=0; i +inline void SimpleVector::push_back(Type *inArray, int inLength) { + + for( int i=0; i +inline Type *SimpleVector::getElementArray() { + Type *newAlloc = new Type[ numFilledElements ]; + + // shallow copy not good enough! + /* + unsigned int sizeOfElement = sizeof( Type ); + unsigned int numBytesToCopy = sizeOfElement * numFilledElements; + + // copy into new space + //memcpy( (void *)newAlloc, (void *)elements, numBytesToCopy ); + */ + + // use assignment to ensure that constructors are invoked on element copies + for( int i=0; i +inline char *SimpleVector::getElementString() { + char *newAlloc = new char[ numFilledElements + 1 ]; + unsigned int sizeOfElement = sizeof( char ); + unsigned int numBytesToCopy = sizeOfElement * numFilledElements; + + // memcpy fine here, since shallow copy good enough for chars + // copy into new space + memcpy( (void *)newAlloc, (void *)elements, numBytesToCopy ); + + newAlloc[ numFilledElements ] = '\0'; + + return newAlloc; + } + + +template <> +inline void SimpleVector::appendElementString( const char *inString ) { + // slow but correct + + unsigned int numChars = strlen( inString ); + for( unsigned int i=0; i +inline void SimpleVector::setElementString( const char *inString ) { + deleteAll(); + + appendElementString( inString ); + } + + + + + + +#endif diff --git a/minorGems/util/StringBufferOutputStream.cpp b/minorGems/util/StringBufferOutputStream.cpp new file mode 100644 index 0000000..16c33d4 --- /dev/null +++ b/minorGems/util/StringBufferOutputStream.cpp @@ -0,0 +1,64 @@ +/* + * Modification History + * + * 2002-August-1 Jason Rohrer + * Created. + * + * 2004-May-9 Jason Rohrer + * Added function for getting data as a byte array. + */ + + + +#include "minorGems/util/StringBufferOutputStream.h" + + + +StringBufferOutputStream::StringBufferOutputStream() + : mCharacterVector( new SimpleVector() ) { + + } + + + +StringBufferOutputStream::~StringBufferOutputStream() { + + delete mCharacterVector; + } + + + +char *StringBufferOutputStream::getString() { + + int numChars = mCharacterVector->size(); + + char *returnArray = new char[ numChars + 1 ]; + + for( int i=0; igetElement( i ) ) ); + } + returnArray[ numChars ] = '\0'; + + return returnArray; + } + + + +unsigned char *StringBufferOutputStream::getBytes( int *outNumBytes ) { + *outNumBytes = mCharacterVector->size(); + + return mCharacterVector->getElementArray(); + } + + + +long StringBufferOutputStream::write( unsigned char *inBuffer, + long inNumBytes ) { + + for( int i=0; ipush_back( inBuffer[ i ] ); + } + + return inNumBytes; + } + diff --git a/minorGems/util/StringBufferOutputStream.h b/minorGems/util/StringBufferOutputStream.h new file mode 100644 index 0000000..545a625 --- /dev/null +++ b/minorGems/util/StringBufferOutputStream.h @@ -0,0 +1,86 @@ +/* + * Modification History + * + * 2002-August-1 Jason Rohrer + * Created. + * + * 2004-May-9 Jason Rohrer + * Added function for getting data as a byte array. + */ + +#include "minorGems/common.h" + + + +#ifndef STRING_BUFFER_OUTPUT_STREAM_INCLUDED +#define STRING_BUFFER_OUTPUT_STREAM_INCLUDED + + + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/io/OutputStream.h" + + + +/** + * An output stream that fills a string buffer. + * + * @author Jason Rohrer + */ +class StringBufferOutputStream : public OutputStream { + + + + public: + + + + StringBufferOutputStream(); + + ~StringBufferOutputStream(); + + + + /** + * Gets the data writen to this stream since construction as a + * \0-terminated string. + * + * @return a string containing all data written to this stream. + * Must be destroyed by caller. + */ + char *getString(); + + + + /** + * Gets the data writen to this stream since construction as a + * byte array. + * + * @param outNumBytes pointer to where the array length should be + * returned. + * + * @return an array containingdata written to this stream. + * Must be destroyed by caller. + */ + unsigned char *getBytes( int *outNumBytes ); + + + + // implements the OutputStream interface + long write( unsigned char *inBuffer, long inNumBytes ); + + + + protected: + + + + SimpleVector *mCharacterVector; + + + + }; + + + +#endif diff --git a/minorGems/util/TranslationManager.cpp b/minorGems/util/TranslationManager.cpp new file mode 100644 index 0000000..6b84da8 --- /dev/null +++ b/minorGems/util/TranslationManager.cpp @@ -0,0 +1,360 @@ +/* + * Modification History + * + * 2004-October-7 Jason Rohrer + * Created. + * + * 2006-February-19 Jason Rohrer + * Fixed an inconsistency in memory management. + * + * 2008-September-17 Jason Rohrer + * Support for setting language data directly. + * + * 2010-March-21 Jason Rohrer + * Fixed crash if default language file not found. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "TranslationManager.h" + +#include + +#include "minorGems/io/file/File.h" + + + +// will be destroyed automatically at program termination +TranslationManagerStaticMembers TranslationManager::mStaticMembers; + + + +void TranslationManager::setDirectoryName( const char *inName ) { + mStaticMembers.setDirectoryAndLanguage( inName, + mStaticMembers.mLanguageName ); + } + + + +char *TranslationManager::getDirectoryName() { + return stringDuplicate( mStaticMembers.mDirectoryName ); + } + + + +char **TranslationManager::getAvailableLanguages( int *outNumLanguages ) { + File *languageDirectory = new File( NULL, mStaticMembers.mDirectoryName ); + + if( languageDirectory->exists() && languageDirectory->isDirectory() ) { + + int numChildFiles; + File **childFiles = languageDirectory->getChildFiles( &numChildFiles ); + + if( childFiles != NULL ) { + SimpleVector *languageNames = new SimpleVector(); + + for( int i=0; igetFileName(); + + char *extensionPointer = strstr( name, ".txt" ); + + if( extensionPointer != NULL ) { + // terminate string, cutting off extension + extensionPointer[0] = '\0'; + + languageNames->push_back( stringDuplicate( name ) ); + } + + delete [] name; + delete childFiles[i]; + } + delete [] childFiles; + + char **returnArray = languageNames->getElementArray(); + + *outNumLanguages = languageNames->size(); + + delete languageNames; + + + delete languageDirectory; + return returnArray; + } + + } + + delete languageDirectory; + + // default, if we didn't return above + *outNumLanguages = 0; + return new char*[0]; + } + + + +void TranslationManager::setLanguage( const char *inLanguageName ) { + mStaticMembers.setDirectoryAndLanguage( mStaticMembers.mDirectoryName, + inLanguageName ); + } + + + +void TranslationManager::setLanguageData( const char *inData ) { + mStaticMembers.setTranslationData( inData ); + } + + + +const char *TranslationManager::translate( const char *inTranslationKey ) { + + char *translatedString = NULL; + + SimpleVector *keys = + mStaticMembers.mTranslationKeys; + + SimpleVector *naturalLanguageStrings = + mStaticMembers.mNaturalLanguageStrings; + + + if( keys != NULL ) { + int numKeys = keys->size(); + + for( int i=0; igetElement( i ) ) ) == 0 ) { + // keys match + translatedString = + *( naturalLanguageStrings->getElement( i ) ); + } + } + } + + + if( translatedString == NULL ) { + // no translation exists + + // the translation for this key is the key itself + + // add it to our translation table + + char *key = stringDuplicate( inTranslationKey ); + char *value = stringDuplicate( inTranslationKey ); + + keys->push_back( key ); + naturalLanguageStrings->push_back( value ); + + // thus, we return a value from our table, just as if a translation + // had existed for this string + translatedString = value; + } + + return translatedString; + } + + + +TranslationManagerStaticMembers::TranslationManagerStaticMembers() + : mDirectoryName( NULL ), + mLanguageName( NULL ), + mTranslationKeys( NULL ), + mNaturalLanguageStrings( NULL ) { + + // default + setDirectoryAndLanguage( "languages", "English" ); + } + + + +TranslationManagerStaticMembers::~TranslationManagerStaticMembers() { + if( mDirectoryName != NULL ) { + delete [] mDirectoryName; + } + + if( mLanguageName != NULL ) { + delete [] mLanguageName; + } + + if( mTranslationKeys != NULL ) { + int numKeys = mTranslationKeys->size(); + + for( int i=0; igetElement( i ) ); + } + delete mTranslationKeys; + } + + if( mNaturalLanguageStrings != NULL ) { + int numKeys = mNaturalLanguageStrings->size(); + + for( int i=0; igetElement( i ) ); + } + delete mNaturalLanguageStrings; + } + + } + + + +void TranslationManagerStaticMembers::setDirectoryAndLanguage( + const char *inDirectoryName, + const char *inLanguageName ) { + + // save temp copies first to allow caller to pass our own members in to us + char *tempDirectoryName = stringDuplicate( inDirectoryName ); + char *tempLanguageName = stringDuplicate( inLanguageName ); + + + if( mDirectoryName != NULL ) { + delete [] mDirectoryName; + } + + if( mLanguageName != NULL ) { + delete [] mLanguageName; + } + + mDirectoryName = tempDirectoryName; + mLanguageName = tempLanguageName; + + char dataSet = false; + + + File *directoryFile = new File( NULL, mDirectoryName ); + + if( directoryFile->exists() && directoryFile->isDirectory() ) { + + char *languageFileName = autoSprintf( "%s.txt", mLanguageName ); + + File *languageFile = directoryFile->getChildFile( languageFileName ); + + delete [] languageFileName; + + + if( languageFile != NULL ) { + + + char *languageData = languageFile->readFileContents(); + + if( languageData != NULL ) { + + dataSet = true; + + setTranslationData( languageData ); + delete [] languageData; + } + delete languageFile; + } + } + + delete directoryFile; + + + if( !dataSet ) { + // failed to open file... + + // set blank data to ensure that vectors are created + setTranslationData( "" ); + } + + } + + +static inline char *stringSkip( const char *inString, int inNumChars ) { + return (char *)( &( inString[ inNumChars ] ) ); + } + + + + +void TranslationManagerStaticMembers::setTranslationData( const char *inData ) { + + // clear the old translation table + if( mTranslationKeys != NULL ) { + int numKeys = mTranslationKeys->size(); + + for( int i=0; igetElement( i ) ); + } + delete mTranslationKeys; + } + + if( mNaturalLanguageStrings != NULL ) { + int numKeys = mNaturalLanguageStrings->size(); + + for( int i=0; igetElement( i ) ); + } + delete mNaturalLanguageStrings; + } + + + // now read in the translation table + mTranslationKeys = new SimpleVector(); + mNaturalLanguageStrings = new SimpleVector(); + + + + char readError = false; + + while( ! readError ) { + + char *key = new char[ 100 ]; + + int numRead = sscanf( inData, "%99s", key ); + + if( numRead == 1 ) { + + inData = stringSkip( inData, strlen( key ) ); + + + // skip the first " + int readChar = ' '; + + while( readChar != '"' && readChar != '\0' ) { + readChar = inData[0]; + inData = stringSkip( inData, 1 ); + } + if( readChar != EOF ) { + char *naturalLanguageString = new char[1000]; + // scan a string of up to 999 characters, stopping + // at the first " character + numRead = sscanf( inData, "%999[^\"]", + naturalLanguageString ); + + if( numRead == 1 ) { + inData = stringSkip( inData, + strlen( naturalLanguageString ) ); + + // trim the key and string and save them + mTranslationKeys->push_back( + stringDuplicate( key ) ); + mNaturalLanguageStrings->push_back( + stringDuplicate( naturalLanguageString ) ); + } + else { + readError = true; + } + delete [] naturalLanguageString; + + // skip the trailing " + readChar = ' '; + + while( readChar != '"' && readChar != '\0' ) { + readChar = inData[0]; + inData = stringSkip( inData, 1 ); + } + } + else { + readError = true; + } + + } + else { + readError = true; + } + delete [] key; + + } + } diff --git a/minorGems/util/TranslationManager.h b/minorGems/util/TranslationManager.h new file mode 100644 index 0000000..b3d9a47 --- /dev/null +++ b/minorGems/util/TranslationManager.h @@ -0,0 +1,203 @@ +/* + * Modification History + * + * 2004-October-7 Jason Rohrer + * Created. + * Copied structure from SettingsManager. + * + * 2006-February-19 Jason Rohrer + * Fixed an inconsistency in memory management. + * + * 2008-September-17 Jason Rohrer + * Support for setting language data directly. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef TRANSLATION_MANAGER_INCLUDED +#define TRANSLATION_MANAGER_INCLUDED + + + +#include "minorGems/util/SimpleVector.h" + + + +// utility class for dealing with static member dealocation +class TranslationManagerStaticMembers; + + + +/** + * Class that manages natural language translation of user interface strings. + * + * @author Jason Rohrer + */ +class TranslationManager { + + + + public: + + + + /** + * Sets the directory name where translation files are stored. + * Defaults to "languages". + * + * @param inName the name of the directory (relative to the + * program's working directory). + * Must be destroyed by caller. + */ + static void setDirectoryName( const char *inName ); + + + + /** + * Gets the directory name where translation files are stored. + * + * @return the name of the directory (relative to the + * program's working directory). + * Must be destroyed by caller. + */ + static char *getDirectoryName(); + + + + /** + * Gets a list of available languages. + * + * @param outNumLanguages pointer to where the number of languages + * should be returned. + * + * @return an array of language names. + * Array and the strings it contains must be destroyed by caller. + */ + static char **getAvailableLanguages( int *outNumLanguages ); + + + + /** + * Sets the natural language to translate keys into. + * Defaults to "English". + * + * @param inLanguageName the name of the language. + * This name, when .txt is appended, is the name of + * the translation file. Thus, setLanguage( "English" ) would + * select the English.txt language file. + * Language names must not contain spaces. + * Must be destroyed by caller. + */ + static void setLanguage( const char *inLanguageName ); + + + + // Sets the language data directly without reading it from + // the file system. + // Data string formated the same as a language file. + // Data string destroyed by caller + static void setLanguageData( const char *inData ); + + + + + /** + * Gets the natural language translation of a key. + * + * NOTE: if a translation does not exist for the key, the key + * itself will be returned. (A copy of the key is returned, so + * the original key passed in to translate can be destroyed by caller + * if needed). + * + * @param inTranslationKey the translation key string. + * Must be destroyed by caller if non-const. + * + * @return the translated natural language string. + * The string MUST NOT be destroyed by the caller, as it will + * be destroyed by this class upon program termination. + * + * This specification allows the translate function to be used + * inline, whether or not a correct translation exists. Thus
+         *
+         * printf( "%s", translate( myKey ) );
+         * delete [] myKey;
+         * 
+ * + * will always be correct, whether or not a translation exists, as + * will
+         *
+         * printf( "%s", translate( "MY_KEY" ) );
+         * 
+ */ + static const char *translate( const char *inTranslationKey ); + + + + protected: + + + + static TranslationManagerStaticMembers mStaticMembers; + + + }; + + + +/** + * Container for static members to allow for their proper destruction + * on program termination. + * + * @author Jason Rohrer + */ +class TranslationManagerStaticMembers { + + + + public: + + + + TranslationManagerStaticMembers(); + ~TranslationManagerStaticMembers(); + + + + /** + * Sets the directory name and language name to use, and reads + * the translation table from file. + * + * @param inDirectoryName the directory name. + * Must be destroyed by caller. + * @param inLanguageName the language name. + * Must be destroyed by caller. + */ + void setDirectoryAndLanguage( const char *inDirectoryName, + const char *inLanguageName ); + + + // sets the data from a string + // string contains same contents as a language file + // string destroyed by caller + void setTranslationData( const char *inData ); + + + + char *mDirectoryName; + char *mLanguageName; + + // vectors mapping keys to strings + SimpleVector *mTranslationKeys; + SimpleVector *mNaturalLanguageStrings; + + + }; + + + +#endif diff --git a/minorGems/util/development/fortify/FORTIFY.DOC b/minorGems/util/development/fortify/FORTIFY.DOC new file mode 100644 index 0000000..2d3b468 --- /dev/null +++ b/minorGems/util/development/fortify/FORTIFY.DOC @@ -0,0 +1,746 @@ + Fortify - A fortified memory allocation shell for C and C++ + ----------------------------------------------------------- + written by Simon P. Bullen + + Version 2.2, 1 November 1995 + + + + This software is not public domain. All material in + this archive is (C) Copyright 1995 Simon P. Bullen. The + software is freely distributable, with the condition that + no more than a nominal fee is charged for media. + Everything in this distribution must be kept together, in + original, unmodified form. + The software may be modified for your own personal use, + but modified files may not be distributed. + The material is provided "as is" without warranty of + any kind. The author accepts no responsibility for damage + caused by this software. + This software may not be used in any way by Microsoft + Corporation or its subsidiaries, or current employees of + Microsoft Corporation or its subsidiaries. + This software may not be used for the construction, + development, production, or testing of weapon systems of + any kind. + This software may not be used for the construction, + development, production, or use of plants/installations + which include the processing of radioactive/fissionable + material. + + + + If you use this software at all, I'd love to hear from you. All +questions, criticisms, suggestions, praise and postcards are most welcome. + + email: sbullen@cybergraphic.com.au + + snail: Simon P. Bullen + PO BOX 12138 + A'Beckett St. + Melbourne 3000 + Australia + + + + CONTENTS + -------- + Your archive should have the following files: + + fortify.doc - This file. Read it first + fortify.cxx - Actual code. Rename to fortify.c for C only development + fortify.h - Each file in your program should #include this + ufortify.h - User configuration file. Here is where you set your options + + test.c - C tester + test2.cxx - C++ tester + makefile - Makefile for GCC to build the testers. + + + + OVERVIEW + -------- + Fortify is a (fortified) shell for memory allocations. It supports +malloc/calloc/realloc/strdup/free and new/delete. It traps memory leaks, +writes beyond and before memory blocks, writes to freed memory, free twice, +freeing memory never allocated, and removes all randomness from reading +uninitialized or free memory. + It is a descendant of a library I wrote way back in 1990 called SafeMem. +It can be adapted to most memory management functions; the original version +of SafeMem worked only with the Amiga's AllocMem/FreeMem. + Fortify works by allocating extra space on each block. This includes a +private header (which is used to keep track of Fortify's list of allocated +memory), and two "fortification zones" or "sentinels" (which are used to +detect writing outside the bound of the user's memory). + + + + + PORTABILITY + ----------- + Fortify is intended to be a highly portable tool. It should work quite +happily in any vaguely ANSI C/C++ environment, on any operating system or +processor architecture without modification. + It does not, however, support bizarre memory models. A pointer is a +pointer is a pointer. I have no intention of ever supporting any of the +non-flat memory models used by some pretend operating systems. + + + + + Fortify INSTALLATION (C++) + -------------------------- + To install Fortify, each source file will need to #include "fortify.h". +To enable Fortify, define the symbol FORTIFY. The makefile is the best +place to do this. The symbol FORTIFY will need to be defined for every +module, this includes "fortify.c" or "fortify.cxx". + If FORTIFY is not defined, it will compile away to nothing - it will be +as if Fortify was never installed at all (you should recompile all +sourcecode with Fortify disabled for a release of your software). + If you do not have stdout available, you may wish to set an alternative +output function. See Fortify_SetOutputFunc() and +FORTIFY_AUTOMATIC_LOGFILE, below. + Your program will need to link in "fortify.cxx" (after it's been +compiled, of course). + You may also need to adjust some options in "ufortify.h" before Fortify +will work correctly. See "Compile Time Customizations", below. + + + + Fortify INSTALLATION (C) + ------------------------ + To install Fortify into a C program, simply rename "fortify.cxx" to +"fortify.c", and follow the C++ instructions above. "fortify.cxx" will +compile quite happily as C, as all the core Fortify code is C, and the C++ +specific code is surrounded by #ifdef __cplusplus/#endif. + You may also need to adjust some options in "ufortify.h" before Fortify +will work correctly. See "Compile Time Customizations", below. + + + + + + COMPILE TIME CUSTOMIZATIONS + --------------------------- + The file "ufortify.h" contains many #defines that you can use to +customize Fortify's behavior. Fortify will do it's best to provide +sensible defaults for most of these options, if for some reason they are +not present in "ufortify.h". + Most of the options are merely user preferences that you may wish to +change or turn on only when you are trying to track down a particularly +nasty bug. There are a couple of configuration items, however, that will +need to be set correctly for Fortify to compile on your particular system. +When it can, Fortify will detect a compiler and set the appropriate +options. + Ideally, Fortify would be able to be built under all compilers without +modification, but due to differences in compilers and the devious nature of +some of the stuff Fortify has to acheive, this is sadly not possible. It +is my aim to make Fortify automatically configure itself for all major +compilers out there, so if you need to adjust some of these +compiler-specific options to get Fortify to work under your system, I would +greatly appreciate the code fragment that detects your compiler and sets up +the correct options (these live in "fortify.h" - search for __GNUG__ for an +example). + + + + #define FORTIFY_PROVIDE_ARRAY_NEW + #define FORTIFY_PROVIDE_ARRAY_DELETE + + Some C++ compilers have separate operators for newing and deleting +arrays. If your compiler does, you will need to define this symbol. If +you are unsure, turn them both on. Your program won't compile or link if +they should be off (providing your test code actually news and deletes an +array). GCC 2.6.3 and Borland C++ 4.5 both need these symbols. Microsoft +C++ 1.5 and SAS 6.5 C++ both don't. Fortify will automatically turn on +this option for GCC and Borland.. + If both array new and array delete are enabled, Fortify will be able to +ensure that you always use the correct form of delete, and will issue an +error message if you use the wrong one. + + Fortify: Incorrect deallocator "delete[]" detected at test2.cxx.17 + (0x7438ac2,1,test2.cxx.13) was allocated with "new" + + + #define FORTIFY_STRDUP + + strdup() is a non-ANSI function that is essentially a malloc() and a +strcpy(). If you use this function, you will need to enable +FORTIFY_STRDUP, so that the memory you get back from strdup() will be +allocated by Fortify. + If you don't do this, Fortify will generate an error message when you +attempt to free the memory. + + + #define FORTIFY_NO_PERCENT_P + + Some non-ANSI versions of sprintf() do not recognize "%p" as a valid +conversion specification. Fortify uses "%p" to output all of it's +pointers. If your sprintf() does not support "%p", define +FORTIFY_NO_PERCENT_P, and Fortify will use "%lx" instead. + + + #define FORTIFY_STORAGE + + You can use this to apply a storage type to all Fortify's exportable +functions. If you are putting Fortify in an export library for example, +you may need to put __export here, for example. + + + #define FORTIFY_BEFORE_SIZE 32 + #define FORTIFY_BEFORE_VALUE 0xA3 + #define FORTIFY_AFTER_SIZE 32 + #define FORTIFY_AFTER_VALUE 0xA5 + + These values define how much "fortification" is placed around each +memory block you allocate. Fortify will place FORTIFY_BEFORE_SIZE bytes +worth of memory right before your memory block, and FORTIFY_AFTER_SIZE +bytes after your memory block, and these will be initialized to +FORTIFY_BEFORE_VALUE and FORTIFY_AFTER_VALUE respectively. If your program +then accidentally writes into either of these regions, Fortify will detect +this, and issue an error message. + If you don't want these fortifications to be allocated, specify a size +of 0. Note that the value parameters are 8 bits. + + Fortify: Underwrite detected before block (0x743b462,123,test.c.14) at + test.c.16 + Memory integrity was last verified at test.c.14 + Address Offset Data (a3) + ...64 bytes skipped... + 0x743b45a 64 a3a3a3a3 a3a3a341 "£££££££A" + + + #define FORTIFY_ALIGNMENT sizeof(double) + + Many processors require data objects to have a specific alignment in +memory. The memory allocators on these machines will only return memory +blocks aligned correctly for the largest of these requirements. Fortify, +however, adds some magic cookies to the start of it's memory blocks. +Fortify will guarantee that the amount of memory it adds to the front of a +memory block will be a multiple of FORTIFY_ALIGNMENT (It does this by +increasing the FORTIFY_BEFORE_SIZE, after taking into account the size of +it's private header). The default of sizeof(double) should work on most +systems. + + + #define FORTIFY_FILL_ON_ALLOCATE + #define FORTIFY_FILL_ON_ALLOCATE_VALUE 0xA7 + + Programs often rely on uninitialized memory being certain values +(usually 0). If you define FORTIFY_FILL_ON_ALLOCATE, all memory that you +allocate will be initialized to FORTIFY_FILL_ON_ALLOCATE_VALUE, which you +should define to be some horrid value (definitely NOT 0). This will +encourage all code that relies on uninitialized memory to behave rather +differently when Fortify is running. If you ever see a pointer that is +0xA7A7A7A7, you can be certain that it is uninitialized. + + + #define FORTIFY_FILL_ON_DEALLOCATE + #define FORTIFY_FILL_ON_DEALLOCATE_VALUE 0xA9 + + Programmers often try to use memory after they've freed it, which can +sometimes work (so long as nobody else has modified the memory before you +look at it), but is incredibly dangerous and definitely bad practice. If +FORTIFY_FILL_ON_DEALLOCATE is defined, all memory you free will be stomped +out with FORTIFY_FILL_ON_DEALLOCATE_VALUE, which ensures that any attempt +to read freed memory will give incorrect results. If you ever see a +pointer that is 0xA9A9A9A9, it definately came from free'd memory. + + + #define FORTIFY_FILL_ON_CORRUPTION + + Fortify will never free memory that has had it's fortifications +overwritten. This is so that you have ample opportunity to examine this +memory in a debugger. If the memory remains corrupted, Fortify will report +this each time it does a check of memory, which can be annoying (but +useful, if the program continues to modify the memory). If you define +FORTIFY_FILL_ON_CORRUPTION, Fortify will re-initialize a corrupted +fortification after it issues it's message. Fortify will then no longer +complain about this memory, until it is corrupted again. + This has the extra advantage that software that tries to read this +memory will probably break. + + + #define FORTIFY_CHECK_ALL_MEMORY_ON_ALLOCATE + #define FORTIFY_CHECK_ALL_MEMORY_ON_DEALLOCATE + + CHECK_ALL_MEMORY_ON... means that for every single memory allocation +or deallocation, every single block of memory will be checked. This can +slow down programs considerably if you have a large number of blocks +allocated. You would normally only need to turn this on if you are trying +to pinpoint where a corruption was occurring. + A block of memory is always checked when it is freed, so if +CHECK_ALL... isn't turned on, corruptions will still be detected, +eventually. + You can also force Fortify to check all memory with a call to +Fortify_CheckAllMemory(). If you have a memory corruption you can't find, +sprinkling these through the suspect code will help narrow it down. When +you get a message regarding a corruption, part of the output will be a line +that says "Memory integrity was last verified at file.line". This +corresponds to when the "CheckAllMemory" operation was last successfully +performed, either automatically, or by an explicit call to +Fortify_CheckAllMemory(). + + + #define FORTIFY_PARANOID_DEALLOCATE + + FORTIFY_PARANOID_DEALLOCATE causes Fortify to traverse the memory list +to ensure the memory you are about to free was really allocated. This +option is recommended in a protected memory operating system. If a free() +of a garbage pointer is attempted, Fortify will generate a segmentation +fault when it attempts to checksum the memory pointed to by the garbage +pointer. If, however, FORTIFY_PARANOID_DELETE is on, Fortify will be able +to determine that the pointer is garbage without crashing the system, and +will be able to generate an error message. + Note that in C++, however, if you try to "delete" a garbage pointer, +you will most likely crash before Fortify gets a look in. When you do a +"delete" in C++, the first thing that happens is the object's destructor +gets called. Depending on what the destructor actually does, it may crash +due to having a bogus "this". The memory isn't freed until AFTER the +destructor, which is when Fortify would be able to detect the invalid +pointer (too late). If you are having problems with a weird crash in a +destructor, you may wish to add Fortify_CheckPointer(this) to the start of +the destructor, but be warned that Fortify will generate an error message +if you destruct an object that was created on the stack. + FORTIFY_PARANOID_DEALLOCATE adds a small amount of overhead to freeing +memory (though not nearly as much as +FORTIFY_CHECK_ALL_MEMORY_ON_DEALLOCATE). Paranoid mode would be quicker if +Fortify didn't use a naive linked list to store it's memory on (see TO DO +LIST, below). + + + #define FORTIFY_WARN_ON_ZERO_MALLOC + #define FORTIFY_FAIL_ON_ZERO_MALLOC + + In some implementations, a malloc(0) will always fail (This is +non-ANSI). If Fortify is turned on, malloc(0) won't actually reach malloc +as a malloc(0) - Fortify adds fortifications, which means it won't be a +zero byte allocation anymore, and will probably succeed. + Thus it is possible to write code that would work if Fortify was turned +on, but fail when Fortify is turned off. If the version of malloc() you +are using does not allow malloc(0) to succeed, you must enable +FORTIFY_FAIL_ON_ZERO_MALLOC to enable your program to function properly +with Fortify turned on. + + + #define FORTIFY_WARN_ON_ALLOCATE_FAIL + + This causes a debug to be issued whenever an allocation fails. This +can be very useful when identifying the cause for a crash. + + + #define FORTIFY_WARN_ON_FALSE_FAIL + + A debug will be issued when an allocation is "false failed". See +Fortify_SetAllocateFailRate() and Fortify_SetAllocationLimit() for more +information. + + Fortify: A "malloc()" of 128 bytes "false failed" at test.c.27 + + + + #define FORTIFY_WARN_ON_SIZE_T_OVERFLOW + + This causes Fortify to check for breaking the size_t limit. This is a +problem in 16-bit applications where breaking the 16 bit limit is +reasonably likely. + The problem is that Fortify adds a small amount of overhead to a memory +block; so in a 16-bit size_t environment, if you tried to allocate 64K, +Fortify would make that block bigger than 64K and your allocation would +fail due to the presence of Fortify. With size_t being 32 bits for all +environments worth programming in, this problem is extremely unlikely +(Unless you plan to allocate 4 gigabytes). + + + #define FORTIFY_AUTOMATIC_LOG_FILE + #define FORTIFY_LOG_FILENAME "fortify.log" + #define FORTIFY_FIRST_ERROR_FUNCTION cout << "\a\a\aOh Dear!\n" + + If FORTIFY_AUTOMATIC_LOG_FILE is defined (C++ only), then Fortify will +output to a log file. It will also automatically call Fortify_EnterScope() +at program initialization, and Fortify_LeaveScope() at program termination. + If Fortify generated no output, the log file will not be altered. + FORTIFY_FIRST_ERROR_FUNCTION will be called upon generation of the +first Fortify message, so that the user can tell if a Fortify report has +been generated. Otherwise, Fortify would quietly write all this useful +stuff out to the log file, and no-one would know to look there! + You don't need to use Fortify_SetOutputFunc() at all if you're using +the automatic log file. + + + #define FORTIFY_LOCK() + #define FORTIFY_UNLOCK() + + In a multi-threaded environment, we need to arbitrate access to the +fortify memory list. This is what FORTIFY_LOCK() and FORTIFY_UNLOCK() are +used for. The calls to FORTIFY_LOCK() and FORTIFY_UNLOCK() must nest. If +no two threads/tasks/processes will be using the same Fortify at the same +time, then FORTIFY_LOCK() and FORTIFY_UNLOCK() can safely be #defined away +to nothing. + + + #define FORTIFY_TRACK_DEALLOCATED_MEMORY + #define FORTIFY_DEALLOCATED_MEMORY_LIMIT 1048576 + #define FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY + #define FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY + + If FORTIFY_TRACK_DEALLOCATED_MEMORY is enabled, Fortify won't actually +free memory when you deallocate it. Freed memory goes onto the +"deallocated" list, where Fortify will keep an eye on it. If you write to +the memory after it's been freed, Fortify will be able to tell. If you +attempt to free memory twice, Fortify will be able to tell you for certain, +rather than just suspect. + Deallocated memory tracking is only active if you've entered at least +one level of Fortify scope. This is so that Fortify will be able to +actually free the deallocated memory when the scope is left. + + Fortify: Write to deallocated block (0x75c4502,126,test.c.21) + detected at test.c.40 + Memory block was deallocated by "realloc()" at test.c.34 + Memory integrity was last verified at test.c.35 + Address Offset Data (a9) + 0x75c4502 0 58a9a9a9 a9a9a9a9 a9a9a9a9 a9a9a9a9 "X" + ...110 bytes skipped... + + FORTIFY_DEALLOCATED_MEMORY_LIMIT, if defined, is the maximum amount of +memory that Fortify should keep on it's deallocated list. If you +deallocate some memory, and the total amount of deallocated memory being +tracked is greater than this number, then Fortify will throw away (actually +free) the oldest chunks of memory, until it's under this threshold. You +don't have to worry about running out of memory if this value is set high, +Fortify will discard deallocated memory in preference to letting an +allocation fail. The main reason for this option is to restrict the amount +of time spent checking freed memory for corruption. + FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY will cause Fortify to +issue a warning whenever it has to free some deallocated memory when it +didn't really want to. If an allocation was going to fail, or if it was +over the FORTIFY_DEALLOCATED_MEMORY_LIMIT, and it had to free some memory, +then a warning would be issued. Freeing deallocated memory when leaving a +Fortify scope does not cause a warning. + FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY is useful when +you are trying to track down a particular write to some deallocated memory. +Fortify will list the details of all blocks being discarded, so that you +can tell if the block you are interested in is still being watched by +Fortify. + Note that Fortify will always perform one last check on the memory +before it discards it, so you can be sure that all memory being discarded +is corruption free. + + Fortify: Warning - Discarding deallocated memory at test.c.46 + (0x743e8b2,126,test.c.22,test.c.35) + (0x743f0b2,456,test.c.32,test.c.46) + + + + #define FORTIFY_DELETE_STACK_SIZE 256 + + Due to the way that delete works, Fortify has to maintain it's own +stack of delete source-code information. For simplicity, this has been +implemented in a static array. The size of this stack reflects the number +of levels of recursion that Fortify will be able to track during deletes +(ie deletes being called from destructors called from a delete call). If +this recursion limit is exceeded, Fortify will simply output the deepest +sourcecode level known. I think 256 levels should be ample. + + + + + typedef void (*Fortify_NewHandlerFunc)(void); + #define FORTIFY_NEW_HANDLER_FUNC Fortify_NewHandlerFunc + + By default, Fortify will use the standard new handler function +prototype. However, some non-standard compiler implementations (such as +Microsoft Visual C++ 2.0) use non-standard prototypes for their new +handlers. FORTIFY_NEW_HANDLER_FUNC is the type that Fortify uses for it's +new handler function pointer. Note that Fortify won't pass any parameters +to the new handler, so changing this is probably quite useless at this +point in time. + + + + RUN TIME CONTROL + ---------------- + Fortify can also be controlled at run time with a few special +functions, which compile away to nothing if FORTIFY isn't defined. + + Fortify_Disable() - Fortify can be disabled at run-time. Previously +this would only work if no memory was allocated at the time +Fortify_Disable() was called, but in C++ environments, this is almost never +possible. After Fortify_Disable() has been called, all new allocations +will not be Fortified. Deallocations will be treated slightly differently. +Fortify will check to see if it allocated the memory, and if so, free it. +If not, it will pass it on to free(). Fortify_Disable() is useful to turn +off Fortify if the overhead or bugs being induced by Fortify are getting in +the way. To correctly remove Fortify for software release, you must +compile it out. + Note that once you've called Fortify_Disable(), there is no way to turn +it back on again. + + Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output) - Sets the function +used to output all error and diagnostic messages by Fortify. The output +function takes a single (const char *) argument, and must be able to handle +newlines. The default output function is a printf() to stdout, unless you +are using FORTIFY_AUTOMATIC_LOG_FILE, where the default is to output to the +log file. The function returns the old pointer. + + + Fortify_SetAllocateFailRate(int Percent) - Fortify will make an +allocation attempt "fail" this "Percent" of the time, even if the memory IS +available. Useful to "stress-test" an application. Returns the old value. +The fail rate defaults to 0. + + + Fortify_SetAllocationLimit(unsigned long AllocationLimit) - Impose a +limit on the amount of memory the application can allocate. This limit may +be changed at any time. If you set this to 0, memory allocations are +guaranteed to fail. The default is 0xffffffff, which is essentially no +limit. Returns the old value. Fortify_GetCurrentAllocation() can be used +to find out how much memory is currently allocated (see STATISTICAL +FUNCTIONS, below). + + + DIAGNOSTIC FUNCTIONS + -------------------- + Fortify also provides some additional diagnostic functions which can be +used to track down memory corruption and memory leaks. If Fortify is +disabled, these functions do nothing. If calling these functions directly +from a debugger, remember to add the "const char *file" and "unsigned long +line" parameters to each of the calls (these are normally added by the +preprocessor macros). + + + Fortify_CheckPointer(void *uptr) - Returns true if the uptr points to a +valid block of allocated memory. The memory must be on Fortify's list, and +it's sentinels must be in tact. If anything is wrong, an error message is +issued (if Fortify is disabled, this function always returns true). + + + Fortify_LabelPointer(void *uptr, const char *label) - Labels the memory +block with a string provided by the user. This function takes a copy of +the passed in string. The pointer MUST be one returned by a Fortify +allocation function. + This function can be very useful to track a memory block that was +allocated on the same line as a bunch of other memory blocks. Whenever +Fortify outputs the information about this memory block, the label string +will also be output. + + + Fortify_CheckAllMemory() - Checks the sentinels of all memory that +Fortify knows about. This includes allocated memory, and if +FORTIFY_TRACK_DEALLOCATED_MEMORY is turned on, any deallocated memory that +Fortify is still tracking. Returns the number of blocks that failed (if +Fortify is disabled, this function always returns 0). + + + Fortify_ListAllMemory() - Outputs the entire list of currently +allocated memory. For each block is output it's Address, Size, and the +SourceFile and Line that allocated it. If there is no memory on the list, +this function outputs nothing. It returns the number of blocks on the list +(if Fortify has been disabled, this function always returns 0). + + + Fortify_DumpAllMemory() - Outputs a hex dump of all currently allocated +memory. + + + Fortify_EnterScope() - enters a level of fortify scope. Returns the +new scope level. + + + Fortify_LeaveScope() - leaves a level of fortify scope, it also prints +a dump of all unfreed memory allocated within the scope being left. This +can be very useful in tracking down memory leaks in a part of a program. +If you place a EnterScope/LeaveScope pair around a set of functions that +should have no memory allocated when it's done, Fortify will let you know +if this isn't the case. Fortify will also discard any deallocated memory +from the scope being left, if it is tracking deallocated memory. + + + + STATISTICAL FUNCTIONS + --------------------- + Fortify gathers some basic statistics about your programs memory usage. +It will keep track of the total number of memory allocation operations +performed, and also the maximum amount of memory allocated at any one time. + + Fortify_OutputStatistics() - display the current statistics. + + Fortify: Statistics at test.c.70 + Maximum memory allocated at one time: 13633 bytes in 7 blocks + There have been 9 allocations and 4 deallocations + There was a total of 14343 bytes allocated + The average allocation was 1593 bytes + + + Fortify_GetCurrentAllocation() - returns the total amount of memory +currently allocated by the application (if Fortify is disabled, this +function will always return 0). + + + + + PROBLEMS WITH THE new AND delete MACROS + --------------------------------------- + Due to limitations of the C preprocessor, getting caller source-code +information to new and delete isn't as easy as it is for malloc() and +free(). The macro for "new" which adds this information onto the new call +causes syntax errors if you try to declare a custom new operator. The +actual Fortifying works fine, it's just the macro expansion which causes +problems. + If this happens, the easiest thing to do is #ifdef out the custom +new/delete operators if FORTIFY is defined. Alternatively, you can not +#include "fortify.h" for the offending file (and any files that use these +custom operators, but remember that they won't have source-code +information). + + eg. + #ifndef FORTIFY /* can't easily use custom new's with FORTIFY enabled */ + void *X::operator new(size_t size) { return malloc(size); } + #endif + + Due to a limitation with delete (it is illegal to declare a version of +delete that takes different parameters), Fortify has limited information +about where delete is being called from, and so the the line and source +information will often say "delete.0". If a delete is occurring from +within another delete, Fortify is now able to maintain a 'delete stack', +and will output this stack after the main body of the Fortify hit. + + + + + WHEN TO USE FORTIFY + ------------------- + You should never be without Fortify when you're developing software. +It will detect your bugs _as_you_write_them_, which makes them a lot easier +to find. + Leave Fortify enabled until the final test and release of your +software. You probably won't want some of the slower options, such as +CHECK_ALL_MEMORY_ON_DEALLOCATE. With the exception of those options, +Fortify doesn't have a great deal of overhead. If posing a great problem, +this overhead can be reduced by cutting down on the size of the +fortifications, and turning off the pre/post fills, but each thing you turn +off gives Fortify less information to work with in tracking your bugs. + (And besides, the slower your program runs while you're developing it, +the more efficient your algorithms are likely to be!). + + + Dynamic Link Libraries + ---------------------- + Dynamic Linking of the standard libraries will not work with Fortify. +The standard library DLL has it's own copy of memory management functions, +so any memory allocated by the DLL will be unknown to Fortify, and you will +get erroneous error messages. + + If you are writing your own DLL's, or using a 3rd party one, it may be +possible to write a callback module to get the DLL's to call the Fortify +routines in the parent application. If anyone does write a module to do +this, please send it to me so I can include it in the next version. + + If the DLL doesn't allocate any memory, or your application doesn't +call free/delete/realloc on any memory allocated by the DLL, then Fortify +should work fine. + + Another alternative to the DLL problem is to use normal link libraries +while you are developing with Fortify, and then rebuild them as DLL's for +release. This of course may not be appropriate in all situations. + + + + REVISION HISTORY + ---------------- + +Changes from V2.1 to V2.2 + Added FORTIFY_WARN_ON_ZERO_MALLOC, and FORTIFY_FAIL_ON_ZERO_MALLOC, as +some compilers do not allow a malloc(0) to succeed. + Fixed a couple of incorrect #ifdef's. + Improved some of the documentation. + Fortify_LabelPointer() misbehaved when Fortify had been run-time +disabled. + +Changes from V2.0 to V2.1 + Fixed Fortify_LabelPointer(), which was broken. + + +Changes from V2.0B to V2.0 + + Moved the checksum to the start of the Header, to avoid alignment +problems (thanks to Ron Flory and Tim Taylor for finding this) + Fixed a miscalcultation with FORTIFY_ALIGNED_BEFORE_SIZE (thanks to Tim +Taylor) + Implemented the delete stack - Fortify is now aware of deletes inside +deletes, and outputs the full delete stack with all delete related hits. + Implemented the new and improved Fortify_Disable() - works even if +memory has already been allocated. + Fixed a few problems with FORTIFY_TRACK_DEALLOCATED_MEMORY being turned +off. + Fixed a couple of incorrect sprintfs. + Added Fortify_LabelPointer (thanks to Steve Toal for this idea) + FORTIFY_WARN_ON_ZERO_ALLOCATE removed as "new char[0]" is allowed, as +is malloc(0). + free(0) is now accepted as harmless. + + + +Changes from V1.0 to V2.0 B + + Added FORTIFY_ALIGNMENT, so that machines requiring special alignment +wouldn't break. + Added deallocated memory tracking, and loads of diagnostics made +possible by this. + Added statistics. + Merged Fortify and ZFortify. + Each memory block now knows which allocator function was used to +allocate it, and uses this information to ensure the correct function is +being used to free it. + Added support for strdup() + Added support for versions of sprintf that don't support "%p". + Fortify will now supply default values for configuration parameters +missing from "ufortify.h" + A few minor portability bug fixes + Fortify will now automatically configure itself for GCC and Borland C++ +4.5. + All error messages and warnings tidied up so they are easier to +understand. + The new operators will now call the new handler if the allocation fails +(if there is a new handler installed, of course). + + +Changes from SafeMem to Fortify V1.0 + + Completely rewrote it. + Fortifies ANSI memory allocation functions rather than Amiga OS ones. + Supports C++'s new and delete + + + + + TO DO + ----- + Make Fortify automatically configure itself for major compilers. + + Add support for non-standard new-handlers, such as Microsoft Visual C++ +2.0, via FORTIFY_NEW_HANDLER_FUNC. + + Implement a stack for scope source-code info and scoped statistics. + + Better documentation. + + FAQ - installation problems, linking problems, "spurious output", +understanding the results, how to track down hits. + + Better test programs (with examples of obscure bugs). + + Memory allocation list could be implemented as a "skip list", so that +searching would be quicker. "Paranoid" mode would be quite fast. + + CHECK_ALL_MEMORY_ON_ could take a percentage of the time to do it - it +could be set to 50%, so that memory is only checked 1/2 of the time, or we +only check 1/2 of the memory, or something like that, so that the checking +still goes on, but less frequently, so there is less overhead. Probably +overkill. + + Write a DLL callback module so that user DLL's can share memory with +their parent applicatoins. + + Add a "pass-count" to source-code information. + diff --git a/minorGems/util/development/fortify/Makefile b/minorGems/util/development/fortify/Makefile new file mode 100644 index 0000000..ba3d705 --- /dev/null +++ b/minorGems/util/development/fortify/Makefile @@ -0,0 +1,73 @@ +# +# Modification History +# +# 2002-March-29 Jason Rohrer +# Added include path to make minorGems reachable. +# Added pthread linking and debug compile flag. +# + + +# +# GCC makefile for Fortify's test applications +# Disable -DFORTIFY to compile without Fortify +# +# To build both of the testers, it should simply be +# a matter of +# +# make all +# +# + +.SUFFIXES: .cxx .cpp + +CC = g++ +CCFLAGS = -g -Wall -DFORTIFY -Wformat -pedantic -I../../../.. +LN = g++ +LNFLAGS = -lpthread #-lm #-liostream + +.c.o: + @echo ---- $*.c ---- + @$(CC) -c $(CCFLAGS) $*.c + +.cpp.o: + @echo ---- $*.cpp ---- + @$(CC) -c $(CCFLAGS) $*.cpp + +#.cpp.o: +# @echo ---- $*.cpp ---- +# @$(CC) -c $(CCFLAGS) $*.cpp + +all: test test2 test3 + + + + + +OBJS= test.o fortify.o +test: $(OBJS) + @echo Linking test + @$(LN) -o test $(OBJS) $(CCFLAGS) $(LNFLAGS) + +OBJS2 = test2.o fortify.o +test2: $(OBJS2) + @echo Linking test2 + @$(LN) -o test2 $(OBJS2) $(CCFLAGS) $(LNFLAGS) + +OBJS3 = test3.o fortify.o +test3: $(OBJS3) + @echo Linking test3 + @$(LN) -o test3 $(OBJS3) $(CCFLAGS) $(LNFLAGS) + + +clean: + rm -f test test2 test3 $(OBJS) $(OBJS2) $(OBJS3) + + + +fortify.o: fortify.cpp fortify.h ufortify.h + +test.o: test.c fortify.h ufortify.h + +test2.o: test2.cpp fortify.h ufortify.h + +test3.o: test3.cpp fortify.h ufortify.h diff --git a/minorGems/util/development/fortify/fortify.cpp b/minorGems/util/development/fortify/fortify.cpp new file mode 100644 index 0000000..633fca3 --- /dev/null +++ b/minorGems/util/development/fortify/fortify.cpp @@ -0,0 +1,2339 @@ +/* + * Modification History + * + * 2002-March-29 Jason Rohrer + * Fixed so that delete [] can be used on strdup strings. + * Changed to violate memory upon CheckPointer failing (so we can get + * a stack trace). + * Changed to use mutex locks for thread safety. + * Changed to use pthread mutex directly to avoid + * stack overflow (since MutexLock uses the new operator). + */ + + /* fortify.cxx - A fortified memory allocation shell - V2.2 */ + +/* + * This software is not public domain. All material in + * this archive is (C) Copyright 1995 Simon P. Bullen. The + * software is freely distributable, with the condition that + * no more than a nominal fee is charged for media. + * Everything in this distribution must be kept together, in + * original, unmodified form. + * The software may be modified for your own personal use, + * but modified files may not be distributed. + * The material is provided "as is" without warranty of + * any kind. The author accepts no responsibility for damage + * caused by this software. + * This software may not be used in any way by Microsoft + * Corporation or its subsidiaries, or current employees of + * Microsoft Corporation or its subsidiaries. + * This software may not be used for the construction, + * development, production, or testing of weapon systems of + * any kind. + * This software may not be used for the construction, + * development, production, or use of plants/installations + * which include the processing of radioactive/fissionable + * material. + */ + +/* + * If you use this software at all, I'd love to hear from + * you. All questions, criticisms, suggestions, praise and + * postcards are most welcome. + * + * email: sbullen@cybergraphic.com.au + * + * snail: Simon P. Bullen + * PO BOX 12138 + * A'Beckett St. + * Melbourne 3000 + * Australia + */ + +#ifdef FORTIFY + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* the user's options */ +#include "ufortify.h" + + + +/* Prototypes and such */ +#define __FORTIFY_C__ +#include "fortify.h" + + + +#include + +// this gives us a mutex that allows the same +// thread to lock it recursively. Fortify needs this kind of mutex. +// Who knows what this syntax means? (found in pthread.h) +pthread_mutex_t mutex = {0, 0, 0, PTHREAD_MUTEX_RECURSIVE_NP, {0, 0}}; + +void Fortify_Lock() { + pthread_mutex_lock( &mutex ); + } + +void Fortify_Unlock() { + pthread_mutex_unlock( &mutex ); + } + + + +/* + * Round x up to the nearest multiple of n. + */ +#define ROUND_UP(x, n) ((((x) + (n)-1)/(n))*(n)) + +/* + * struct Header - this structure is used + * internally by Fortify to manage it's + * own private lists of memory. + */ +struct Header +{ + unsigned short Checksum; /* For the integrity of our goodies */ + const char *File; /* The sourcefile of the allocator */ + unsigned long Line; /* The sourceline of the allocator */ +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + const char *FreedFile; /* The sourcefile of the deallocator */ + unsigned long FreedLine; /* The sourceline of the deallocator */ + unsigned char Deallocator; /* The deallocator used */ +#endif + size_t Size; /* The size of the malloc'd block */ + struct Header *Prev; /* Previous link */ + struct Header *Next; /* Next link */ + char *Label; /* User's Label (may be null) */ + unsigned char Scope; /* Scope level of the caller */ + unsigned char Allocator; /* malloc/realloc/new/etc */ +}; + +#define FORTIFY_HEADER_SIZE ROUND_UP(sizeof(struct Header), sizeof(unsigned short)) + + + +/* + * FORTIFY_ALIGNED_BEFORE_SIZE is FORTIFY_BEFORE_SIZE rounded up to the + * next multiple of FORTIFY_ALIGNMENT. This is so that we can guarantee + * the alignment of user memory for such systems where this is important + * (eg storing doubles on a SPARC) + */ +#define FORTIFY_ALIGNED_BEFORE_SIZE ( \ + ROUND_UP(FORTIFY_HEADER_SIZE + FORTIFY_BEFORE_SIZE, FORTIFY_ALIGNMENT) \ + - FORTIFY_HEADER_SIZE) + +/* + * FORTIFY_OVERHEAD is the total overhead added by Fortify to each + * memory block. + */ +#define FORTIFY_OVERHEAD ( FORTIFY_HEADER_SIZE \ + + FORTIFY_ALIGNED_BEFORE_SIZE \ + + FORTIFY_AFTER_SIZE) + + +/* + * + * Static Function Prototypes + * + */ +static int st_CheckBlock(struct Header *h, const char *file, unsigned long line); +static int st_CheckFortification (unsigned char *ptr, unsigned char value, size_t size); +static void st_SetFortification (unsigned char *ptr, unsigned char value, size_t size); +static void st_OutputFortification(unsigned char *ptr, unsigned char value, size_t size); +static void st_HexDump(unsigned char *ptr, size_t offset, size_t size, int title); +static int st_IsHeaderValid(struct Header *h); +static void st_MakeHeaderValid(struct Header *h); +static unsigned short st_ChecksumHeader(struct Header *h); +static int st_IsOnAllocatedList(struct Header *h); +static void st_OutputHeader(struct Header *h); +static void st_OutputMemory(struct Header *h); +static void st_OutputLastVerifiedPoint(void); +static void st_DefaultOutput(const char *String); +static const char *st_MemoryBlockString(struct Header *h); +static void st_OutputDeleteTrace(); + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY +#ifdef FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY +#ifdef FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY + static const char *st_DeallocatedMemoryBlockString(struct Header *h); +#endif /* FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ +#endif /* FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ + static int st_IsOnDeallocatedList(struct Header *h); + static int st_PurgeDeallocatedBlocks(unsigned long Bytes, const char *file, unsigned long line); + static int st_PurgeDeallocatedScope(unsigned char Scope, const char *file, unsigned long line); + static int st_CheckDeallocatedBlock(struct Header *h, const char *file, unsigned long line); + static void st_FreeDeallocatedBlock(struct Header *h, const char *file, unsigned long line); +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + +/* + * + * Static variables + * + */ +static struct Header *st_AllocatedHead = 0; +static int st_AllocateFailRate = 0; +static char st_Buffer[256]; +static Fortify_OutputFuncPtr st_Output = st_DefaultOutput; +static const char *st_LastVerifiedFile = "unknown"; +static unsigned long st_LastVerifiedLine = 0; +static unsigned char st_Scope = 0; +static unsigned char st_Disabled = 0; + +#ifdef __cplusplus + int gbl_FortifyMagic = 0; + static const char *st_DeleteFile[FORTIFY_DELETE_STACK_SIZE]; + static unsigned long st_DeleteLine[FORTIFY_DELETE_STACK_SIZE]; + static unsigned long st_DeleteStackTop; +#endif /* __cplusplus */ + +/* statistics */ +static unsigned long st_MaxBlocks = 0; +static unsigned long st_MaxAllocation = 0; +static unsigned long st_CurBlocks = 0; +static unsigned long st_CurAllocation = 0; +static unsigned long st_Allocations = 0; +static unsigned long st_Frees = 0; +static unsigned long st_TotalAllocation = 0; +static unsigned long st_AllocationLimit = 0xffffffff; + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + static struct Header *st_DeallocatedHead = 0; + static struct Header *st_DeallocatedTail = 0; + static unsigned long st_TotalDeallocated = 0; +#endif + + +/* allocators */ +static const char *st_AllocatorName[] = +{ + "malloc()", + "calloc()", + "realloc()", + "strdup()", + "new", + "new[]" +}; + +/* deallocators */ +static const char *st_DeallocatorName[] = +{ + "nobody", + "free()", + "realloc()", + "delete", + "delete[]" +}; + +static const unsigned char st_ValidDeallocator[] = +{ + (1< 0) + { + if(rand() % 100 < st_AllocateFailRate) + { +#ifdef FORTIFY_WARN_ON_FALSE_FAIL + sprintf(st_Buffer, + "\nFortify: A \"%s\" of %lu bytes \"false failed\" at %s.%lu\n", + st_AllocatorName[allocator], (unsigned long)size, file, line); + st_Output(st_Buffer); +#endif + return(0); + } + } + + /* Check to see if this allocation will + * push us over the artificial limit + */ + if(st_CurAllocation + size > st_AllocationLimit) + { +#ifdef FORTIFY_WARN_ON_FALSE_FAIL + sprintf(st_Buffer, + "\nFortify: A \"%s\" of %lu bytes \"false failed\" at %s.%lu\n", + st_AllocatorName[allocator], (unsigned long)size, file, line); + st_Output(st_Buffer); +#endif + return(0); + } + +#ifdef FORTIFY_WARN_ON_ZERO_MALLOC + if(size == 0 && (allocator == Fortify_Allocator_malloc || + allocator == Fortify_Allocator_calloc || + allocator == Fortify_Allocator_realloc )) + { + sprintf(st_Buffer, + "\nFortify: A \"%s\" of 0 bytes attempted at %s.%lu\n", + st_AllocatorName[allocator], file, line); + st_Output(st_Buffer); + } +#endif /* FORTIFY_WARN_ON_ZERO_MALLOC */ + +#ifdef FORTIFY_FAIL_ON_ZERO_MALLOC + if(size == 0 && (allocator == Fortify_Allocator_malloc || + allocator == Fortify_Allocator_calloc || + allocator == Fortify_Allocator_realloc )) + { +#ifdef FORTIFY_WARN_ON_ALLOCATE_FAIL + sprintf(st_Buffer, "\nFortify: A \"%s\" of %lu bytes failed at %s.%lu\n", + st_AllocatorName[allocator], (unsigned long)size, file, line); + st_Output(st_Buffer); +#endif /* FORTIFY_WARN_ON_ALLOCATE_FAIL */ + return 0; + } +#endif /* FORTIFY_FAIL_ON_ZERO_MALLOC */ + +#ifdef FORTIFY_WARN_ON_SIZE_T_OVERFLOW + /* + * Ensure the size of the memory block + * plus the overhead isn't bigger than + * size_t (that'd be a drag) + */ + { + size_t private_size = FORTIFY_HEADER_SIZE + + FORTIFY_ALIGNED_BEFORE_SIZE + size + FORTIFY_AFTER_SIZE; + + if(private_size < size) + { + sprintf(st_Buffer, + "\nFortify: A \"%s\" of %lu bytes has overflowed size_t at %s.%lu\n", + st_AllocatorName[allocator], (unsigned long)size, file, line); + st_Output(st_Buffer); + return(0); + } + } +#endif + + another_try = 1; + do + { + /* + * malloc the memory, including the space + * for the header and fortification buffers + */ + ptr = (unsigned char *)malloc( FORTIFY_HEADER_SIZE + + FORTIFY_ALIGNED_BEFORE_SIZE + + size + + FORTIFY_AFTER_SIZE ); + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + /* + * If we're tracking deallocated memory, then + * we can free some of it, rather than let + * this malloc fail + */ + if(!ptr) + { + another_try = st_PurgeDeallocatedBlocks(size, file, line); + } +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + } + while(!ptr && another_try); + + if(!ptr) + { +#ifdef FORTIFY_WARN_ON_ALLOCATE_FAIL + sprintf(st_Buffer, "\nFortify: A \"%s\" of %lu bytes failed at %s.%lu\n", + st_AllocatorName[allocator], (unsigned long)size, file, line); + st_Output(st_Buffer); +#endif + return(0); + } + + /* + * Begin Critical Region + */ + FORTIFY_LOCK(); + + + /* + * Make the head's prev pointer point to us + * ('cos we're about to become the head) + */ + if(st_AllocatedHead) + { + st_CheckBlock(st_AllocatedHead, file, line); + /* what should we do if this fails? (apart from panic) */ + + st_AllocatedHead->Prev = (struct Header *)ptr; + st_MakeHeaderValid(st_AllocatedHead); + } + + /* + * Initialize and validate the header + */ + h = (struct Header *)ptr; + h->Size = size; + h->File = file; + h->Line = line; + h->Next = st_AllocatedHead; + h->Prev = 0; + h->Scope = st_Scope; + h->Allocator = allocator; + h->Label = 0; +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + h->FreedFile = 0; + h->FreedLine = 0; + h->Deallocator = Fortify_Deallocator_nobody; +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + st_MakeHeaderValid(h); + st_AllocatedHead = h; + + /* + * Initialize the fortifications + */ + st_SetFortification(ptr + FORTIFY_HEADER_SIZE, + FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE); + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); + +#ifdef FORTIFY_FILL_ON_ALLOCATE + /* + * Fill the actual user memory + */ + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + FORTIFY_FILL_ON_ALLOCATE_VALUE, size); +#endif + + /* + * End Critical Region + */ + FORTIFY_UNLOCK(); + + + /* + * update the statistics + */ + st_TotalAllocation += size; + st_Allocations++; + st_CurBlocks++; + st_CurAllocation += size; + if(st_CurBlocks > st_MaxBlocks) + st_MaxBlocks = st_CurBlocks; + if(st_CurAllocation > st_MaxAllocation) + st_MaxAllocation = st_CurAllocation; + + /* + * We return the address of the user's memory, not the start of the block, + * which points to our magic cookies + */ + return(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE); +} + + + +/* + * Fortify_Deallocate() - Free a block of memory allocated with Fortify_Allocate() + */ +void FORTIFY_STORAGE +Fortify_Deallocate(void *uptr, unsigned char deallocator, const char *file, unsigned long line) +{ + unsigned char *ptr = (unsigned char *)uptr + - FORTIFY_HEADER_SIZE + - FORTIFY_ALIGNED_BEFORE_SIZE; + struct Header *h = (struct Header *)ptr; + +#ifdef FORTIFY_CHECK_ALL_MEMORY_ON_DEALLOCATE + Fortify_CheckAllMemory(file, line); +#endif + + /* + * If Fortify has been disabled, then it's easy + * (well, almost) + */ + if(st_Disabled) + { + /* there is a possibility that this memory + * block was allocated when Fortify was + * enabled, so we must check the Allocated + * list before we free it. + */ + if(!st_IsOnAllocatedList(h)) + { + free(uptr); + return; + } + else + { + /* the block was allocated by Fortify, so we + * gotta free it differently. + */ + /* + * Begin critical region + */ + FORTIFY_LOCK(); + + /* + * Remove the block from the list + */ + if(h->Prev) + h->Prev->Next = h->Next; + else + st_AllocatedHead = h->Next; + + if(h->Next) + h->Next->Prev = h->Prev; + + /* + * End Critical Region + */ + FORTIFY_UNLOCK(); + + /* + * actually free the memory + */ + free(ptr); + return; + } + } + + +#ifdef FORTIFY_PARANOID_DEALLOCATE + if(!st_IsOnAllocatedList(h)) + { +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + if(st_IsOnDeallocatedList(h)) + { + sprintf(st_Buffer, "\nFortify: \"%s\" twice of %s detected at %s.%lu\n", + st_DeallocatorName[deallocator], + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + st_OutputDeleteTrace(); + return; + } +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "\nFortify: Possible \"%s\" twice of (0x%08lx) was detected at %s.%lu\n", +#else + sprintf(st_Buffer, "\nFortify: Possible \"%s\" twice of (%p) was detected at %s.%lu\n", +#endif + st_DeallocatorName[deallocator], + uptr, file, line); + st_Output(st_Buffer); + st_OutputDeleteTrace(); + return; + } +#endif /* FORTIFY_PARANOID_DELETE */ + + /* + * Make sure the block is okay before we free it. + * If it's not okay, don't free it - it might not + * be a real memory block. Or worse still, someone + * might still be writing to it + */ + if(!st_CheckBlock(h, file, line)) + { + st_OutputDeleteTrace(); + return; + } + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + /* + * Make sure the block hasn't been freed already + * (we can get to here if FORTIFY_PARANOID_DELETE + * is off, but FORTIFY_TRACK_DEALLOCATED_MEMORY + * is on). + */ + if(h->Deallocator != Fortify_Deallocator_nobody) + { + sprintf(st_Buffer, "\nFortify: \"%s\" twice of %s detected at %s.%lu\n", + st_DeallocatorName[deallocator], + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + st_OutputDeleteTrace(); + return; + } +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + /* + * Make sure the block is being freed with a valid + * deallocator. If not, complain. (but free it anyway) + */ + if((st_ValidDeallocator[h->Allocator] & (1<Allocator]); + st_Output(st_Buffer); + st_OutputDeleteTrace(); + } + + /* + * Begin critical region + */ + FORTIFY_LOCK(); + + /* + * Remove the block from the list + */ + if(h->Prev) + { + if(!st_CheckBlock(h->Prev, file, line)) + { + FORTIFY_UNLOCK(); + st_OutputDeleteTrace(); + return; + } + + h->Prev->Next = h->Next; + st_MakeHeaderValid(h->Prev); + } + else + st_AllocatedHead = h->Next; + + if(h->Next) + { + if(!st_CheckBlock(h->Next, file, line)) + { + FORTIFY_UNLOCK(); + st_OutputDeleteTrace(); + return; + } + + h->Next->Prev = h->Prev; + st_MakeHeaderValid(h->Next); + } + + /* + * End Critical Region + */ + FORTIFY_UNLOCK(); + + /* + * update the statistics + */ + st_Frees++; + st_CurBlocks--; + st_CurAllocation -= h->Size; + + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + if(st_Scope > 0) + { + /* + * Don't _actually_ free the memory block, just yet. + * Place it onto the deallocated list, instead, so + * we can check later to see if it's been written to. + */ + #ifdef FORTIFY_FILL_ON_DEALLOCATE + /* + * Nuke out all user memory that is about to be freed + */ + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + FORTIFY_FILL_ON_DEALLOCATE_VALUE, + h->Size); + #endif /* FORTIFY_FILL_ON_DEALLOCATE */ + + /* + * Begin critical region + */ + FORTIFY_LOCK(); + + /* + * Place the block on the deallocated list + */ + if(st_DeallocatedHead) + { + st_DeallocatedHead->Prev = (struct Header *)ptr; + st_MakeHeaderValid(st_DeallocatedHead); + } + + h = (struct Header *)ptr; + h->FreedFile = file; + h->FreedLine = line; + h->Deallocator = deallocator; + h->Next = st_DeallocatedHead; + h->Prev = 0; + st_MakeHeaderValid(h); + st_DeallocatedHead = h; + + if(!st_DeallocatedTail) + { + st_DeallocatedTail = h; + } + + st_TotalDeallocated += h->Size; + + #ifdef FORTIFY_DEALLOCATED_MEMORY_LIMIT + /* + * If we've got too much on the deallocated list; free some + */ + if(st_TotalDeallocated > FORTIFY_DEALLOCATED_MEMORY_LIMIT) + { + st_PurgeDeallocatedBlocks(st_TotalDeallocated - FORTIFY_DEALLOCATED_MEMORY_LIMIT, file, line); + } + #endif + + /* + * End critical region + */ + FORTIFY_UNLOCK(); + } + else +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + { + /* + * Free the User Label + */ + if(h->Label) + { + free(h->Label); + } + +#ifdef FORTIFY_FILL_ON_DEALLOCATE + /* + * Nuke out all memory that is about to be freed, including the header + */ + st_SetFortification(ptr, FORTIFY_FILL_ON_DEALLOCATE_VALUE, + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size + FORTIFY_AFTER_SIZE); +#endif /* FORTIFY_FILL_ON_DEALLOCATE */ + + /* + * And do the actual free + */ + free(ptr); + } +} + + +/* + * Fortify_LabelPointer() - Labels the memory block + * with a string provided by the user. This function + * takes a copy of the passed in string. + * The pointer MUST be one returned by a Fortify + * allocation function. + */ +void +Fortify_LabelPointer(void *uptr, const char *label, const char *file, unsigned long line) +{ + if(!st_Disabled) + { + unsigned char *ptr = (unsigned char *)uptr + - FORTIFY_HEADER_SIZE - FORTIFY_ALIGNED_BEFORE_SIZE; + struct Header *h = (struct Header *)ptr; + + /* make sure the pointer is okay */ + Fortify_CheckPointer(uptr, file, line); + + /* free the previous label */ + if(h->Label) + { + free(h->Label); + } + + /* make sure the label is sensible */ + assert(label); + + /* copy it in */ + h->Label = (char*)malloc(strlen(label)+1); + strcpy(h->Label, label); + + /* update the checksum */ + st_MakeHeaderValid(h); + } +} + +/* + * Fortify_CheckPointer() - Returns true if the uptr + * points to a valid piece of Fortify_Allocated()'d + * memory. The memory must be on the allocated list, + * and it's fortifications must be intact. + * Always returns TRUE if Fortify is disabled. + */ +int FORTIFY_STORAGE +Fortify_CheckPointer(void *uptr, const char *file, unsigned long line) +{ + unsigned char *ptr = (unsigned char *)uptr + - FORTIFY_HEADER_SIZE - FORTIFY_ALIGNED_BEFORE_SIZE; + struct Header *h = (struct Header *)ptr; + int r; + + if(st_Disabled) + return 1; + + FORTIFY_LOCK(); + + if(!st_IsOnAllocatedList(h)) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "\nFortify: Invalid pointer (0x%08lx) detected at %s.%lu\n", +#else + sprintf(st_Buffer, "\nFortify: Invalid pointer (%p) detected at %s.%lu\n", +#endif + uptr, file, line); + st_Output(st_Buffer); + FORTIFY_UNLOCK(); + + // this should seg fault... + int *x = (int *)2; + *x = 0; + return(0); + } + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + if(st_IsOnDeallocatedList(h)) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "\nFortify: Deallocated pointer (0x%08lx) detected at %s.%lu\n", +#else + sprintf(st_Buffer, "\nFortify: Deallocated pointer (%p) detected at %s.%lu\n", +#endif + uptr, file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + FORTIFY_UNLOCK(); + return(0); + } +#endif + + r = st_CheckBlock(h, file, line); + FORTIFY_UNLOCK(); + return r; +} + +/* + * Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output) - + * Sets the function used to output all error and + * diagnostic messages. The output function takes + * a single const unsigned char * argument, and must be + * able to handle newlines. This function returns the + * old output function. + */ +Fortify_OutputFuncPtr FORTIFY_STORAGE +Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output) +{ + Fortify_OutputFuncPtr Old = st_Output; + + st_Output = Output; + + return(Old); +} + +/* + * Fortify_SetAllocateFailRate(int Percent) - + * Fortify_Allocate() will "fail" this Percent of + * the time, even if the memory is available. + * Useful to "stress-test" an application. + * Returns the old value. + * The fail rate defaults to 0 (a good default I think). + */ +int FORTIFY_STORAGE +Fortify_SetAllocateFailRate(int Percent) +{ + int Old = st_AllocateFailRate; + + st_AllocateFailRate = Percent; + + return(Old); +} + + +/* + * Fortify_CheckAllMemory() - Checks the fortifications + * of all memory on the allocated list. And, if + * FORTIFY_DEALLOCATED_MEMORY is enabled, all the + * known deallocated memory as well. + * Returns the number of blocks that failed. + * Always returns 0 if Fortify is disabled. + */ +unsigned long FORTIFY_STORAGE +Fortify_CheckAllMemory(const char *file, unsigned long line) +{ + struct Header *curr = st_AllocatedHead; + unsigned long count = 0; + + if(st_Disabled) + return 0; + + FORTIFY_LOCK(); + + /* + * Check the allocated memory + */ + while(curr) + { + if(!st_CheckBlock(curr, file, line)) + count++; + + curr = curr->Next; + } + + /* + * Check the deallocated memory while you're at it + */ +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + curr = st_DeallocatedHead; + while(curr) + { + if(!st_CheckDeallocatedBlock(curr, file, line)) + count++; + + curr = curr->Next; + } +#endif + + /* + * If we know where we are, and everything is cool, + * remember that. It might be important. + */ + if(file && count == 0) + { + st_LastVerifiedFile = file; + st_LastVerifiedLine = line; + } + + FORTIFY_UNLOCK(); + return(count); +} + + +/* + * Fortify_EnterScope() - enters a new Fortify scope + * level. Returns the new scope level. + */ +unsigned char FORTIFY_STORAGE +Fortify_EnterScope(const char *file, unsigned long line) +{ + return(++st_Scope); +} + +/* Fortify_LeaveScope - leaves a Fortify scope level, + * also prints a memory dump of all non-freed memory + * that was allocated during the scope being exited. + * Does nothing and returns 0 if Fortify is disabled. + */ +unsigned char FORTIFY_STORAGE +Fortify_LeaveScope(const char *file, unsigned long line) +{ + struct Header *curr = st_AllocatedHead; + unsigned long size = 0, count = 0; + + if(st_Disabled) + return 0; + + FORTIFY_LOCK(); + + st_Scope--; + while(curr) + { + if(curr->Scope > st_Scope) + { + if(count == 0) + { + sprintf(st_Buffer, "\nFortify: Memory leak detected leaving scope at %s.%lu\n", file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, "%10s %8s %s\n", "Address", "Size", "Allocator"); + st_Output(st_Buffer); + } + + st_OutputHeader(curr); + count++; + size += curr->Size; + } + + curr = curr->Next; + } + + if(count) + { + sprintf(st_Buffer,"%10s %8lu bytes in %lu blocks with %lu bytes overhead\n", + "total", size, count, count * FORTIFY_OVERHEAD); + st_Output(st_Buffer); + } + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + /* + * Quietly free all the deallocated memory + * that was allocated in this scope that + * we are still tracking + */ + st_PurgeDeallocatedScope( st_Scope, file, line ); +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + FORTIFY_UNLOCK(); + return(st_Scope); +} + +/* + * Fortify_ListAllMemory() - Outputs the entire + * list of currently allocated memory. For each block + * is output it's Address, Size, and the SourceFile and + * Line that allocated it. + * + * If there is no memory on the list, this function + * outputs nothing. + * + * It returns the number of blocks on the list, unless + * Fortify has been disabled, in which case it always + * returns 0. + */ +unsigned long FORTIFY_STORAGE +Fortify_ListAllMemory(const char *file, unsigned long line) +{ + struct Header *curr = st_AllocatedHead; + unsigned long size = 0, count = 0; + + if(st_Disabled) + return 0; + + Fortify_CheckAllMemory(file, line); + + FORTIFY_LOCK(); + + if(curr) + { + sprintf(st_Buffer, "\nFortify: Memory List at %s.%lu\n", file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, "%10s %8s %s\n", "Address", "Size", "Allocator"); + st_Output(st_Buffer); + + while(curr) + { + st_OutputHeader(curr); + count++; + size += curr->Size; + curr = curr->Next; + } + + sprintf(st_Buffer, "%10s %8lu bytes in %lu blocks and %lu bytes overhead\n", + "total", size, count, count * FORTIFY_OVERHEAD); + st_Output(st_Buffer); + } + + FORTIFY_UNLOCK(); + return(count); +} + +/* + * Fortify_DumpAllMemory() - Outputs the entire list of + * currently allocated memory. For each allocated block + * is output it's Address, Size, the SourceFile and Line + * that allocated it, a hex dump of the contents of the + * memory and an ascii dump of printable characters. + * + * If there is no memory on the list, this function outputs nothing. + */ +unsigned long FORTIFY_STORAGE +Fortify_DumpAllMemory(const char *file, unsigned long line) +{ + struct Header *curr = st_AllocatedHead; + unsigned long count = 0; + + if(st_Disabled) + return 0; + + Fortify_CheckAllMemory(file, line); + + FORTIFY_LOCK(); + + while(curr) + { + sprintf(st_Buffer, "\nFortify: Hex Dump of %s at %s.%lu\n", + st_MemoryBlockString(curr), file, line); + st_Output(st_Buffer); + st_OutputMemory(curr); + st_Output("\n"); + count++; + + curr = curr->Next; + } + + FORTIFY_UNLOCK(); + return(count); +} + +/* Fortify_OutputStatistics() - displays statistics + * about the maximum amount of memory that was + * allocated at any one time. + */ +void FORTIFY_STORAGE +Fortify_OutputStatistics(const char *file, unsigned long line) +{ + if(st_Disabled) + return; + + sprintf(st_Buffer, "\nFortify: Statistics at %s.%lu\n", file, line); + st_Output(st_Buffer); + + sprintf(st_Buffer, " Memory currently allocated: %lu bytes in %lu blocks\n", + st_CurAllocation, st_CurBlocks); + st_Output(st_Buffer); + sprintf(st_Buffer, " Maximum memory allocated at one time: %lu bytes in %lu blocks\n", + st_MaxAllocation, st_MaxBlocks); + st_Output(st_Buffer); + sprintf(st_Buffer, " There have been %lu allocations and %lu deallocations\n", + st_Allocations, st_Frees); + st_Output(st_Buffer); + sprintf(st_Buffer, " There was a total of %lu bytes allocated\n", + st_TotalAllocation); + st_Output(st_Buffer); + + if(st_Allocations > 0) + { + sprintf(st_Buffer, " The average allocation was %lu bytes\n", + st_TotalAllocation / st_Allocations); + st_Output(st_Buffer); + } +} + +/* Fortify_GetCurrentAllocation() - returns the number of + * bytes currently allocated. + */ +unsigned long FORTIFY_STORAGE +Fortify_GetCurrentAllocation(const char *file, unsigned long line) +{ + if(st_Disabled) + return 0; + + return st_CurAllocation; +} + +/* Fortify_SetAllocationLimit() - set a limit on the total + * amount of memory allowed for this application. + */ +void FORTIFY_STORAGE +Fortify_SetAllocationLimit(unsigned long NewLimit, const char *file, unsigned long line) +{ + st_AllocationLimit = NewLimit; +} + +/* + * Fortify_Disable() - Run time method of disabling Fortify. + * Useful if you need to turn off Fortify without recompiling + * everything. Not as effective as compiling out, of course. + * The less memory allocated by Fortify when it is disabled + * the better. + * (Previous versions of Fortify did not allow it to be + * disabled if there was any memory allocated at the time, + * but since in C++ memory is often allocated before main + * is even entered, this was useless so Fortify is now + * able to cope). + */ +void FORTIFY_STORAGE +Fortify_Disable(const char *file, unsigned long line) +{ +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + /* free all deallocated memory we might be tracking */ + st_PurgeDeallocatedScope( 0, file, line ); +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + st_Disabled = 1; +} + + + +/* + * st_CheckBlock - Check a block's header and fortifications. + * Returns true if the block is happy. + */ +static int +st_CheckBlock(struct Header *h, const char *file, unsigned long line) +{ + unsigned char *ptr = (unsigned char *)h; + int result = 1; + + if(!st_IsHeaderValid(h)) + { + sprintf(st_Buffer, +#ifdef FORTIFY_NO_PERCENT_P + "\nFortify: Invalid pointer (0x%08lx) or corrupted header detected at %s.%lu\n", +#else + "\nFortify: Invalid pointer (%p) or corrupted header detected at %s.%lu\n", +#endif + ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, file, line); + st_Output(st_Buffer); + st_OutputLastVerifiedPoint(); + + // this should seg fault... + int *x = (int *)2; + *x = 0; + + return(0); + } + + if(!st_CheckFortification(ptr + FORTIFY_HEADER_SIZE, + FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE)) + { + sprintf(st_Buffer, "\nFortify: Underwrite detected before block %s at %s.%lu\n", + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + + st_OutputLastVerifiedPoint(); + st_OutputFortification(ptr + FORTIFY_HEADER_SIZE, + FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE); + result = 0; + +#ifdef FORTIFY_FILL_ON_CORRUPTION + st_SetFortification(ptr + FORTIFY_HEADER_SIZE, FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE); +#endif + } + + if(!st_CheckFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE)) + { + sprintf(st_Buffer, "\nFortify: Overwrite detected after block %s at %s.%lu\n", + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + + st_OutputLastVerifiedPoint(); + st_OutputFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); + result = 0; + +#ifdef FORTIFY_FILL_ON_CORRUPTION + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); +#endif + } + + return(result); +} + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + +/* + * st_CheckDeallocatedBlock - Check a deallocated block's header and fortifications. + * Returns true if the block is happy. + */ +static int +st_CheckDeallocatedBlock(struct Header *h, const char *file, unsigned long line) +{ + unsigned char *ptr = (unsigned char *)h; + int result = 1; + + if(!st_IsHeaderValid(h)) + { + sprintf(st_Buffer, +#ifdef FORTIFY_NO_PERCENT_P + "\nFortify: Invalid deallocated pointer (0x%08lx) or corrupted header detected at %s.%lu\n", +#else + "\nFortify: Invalid deallocated pointer (%p) or corrupted header detected at %s.%lu\n", +#endif + ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, file, line); + st_Output(st_Buffer); + st_OutputLastVerifiedPoint(); + return(0); + } + + if(!st_CheckFortification(ptr + FORTIFY_HEADER_SIZE, + FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE)) + { + sprintf(st_Buffer, "\nFortify: Underwrite detected before deallocated block %s at %s.%lu\n", + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + + st_OutputLastVerifiedPoint(); + st_OutputFortification(ptr + FORTIFY_HEADER_SIZE, + FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE); + +#ifdef FORTIFY_FILL_ON_CORRUPTION + st_SetFortification(ptr + FORTIFY_HEADER_SIZE, FORTIFY_BEFORE_VALUE, FORTIFY_ALIGNED_BEFORE_SIZE); +#endif + result = 0; + } + + if(!st_CheckFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE)) + { + sprintf(st_Buffer, "\nFortify: Overwrite detected after deallocated block %s at %s.%lu\n", + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + + st_OutputLastVerifiedPoint(); + st_OutputFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); + +#ifdef FORTIFY_FILL_ON_CORRUPTION + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size, + FORTIFY_AFTER_VALUE, FORTIFY_AFTER_SIZE); +#endif + result = 0; + } + +#ifdef FORTIFY_FILL_ON_DEALLOCATE + if(!st_CheckFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + FORTIFY_FILL_ON_DEALLOCATE_VALUE, h->Size)) + { + sprintf(st_Buffer, "\nFortify: Write to deallocated block %s detected at %s.%lu\n", + st_MemoryBlockString(h), file, line); + st_Output(st_Buffer); + + sprintf(st_Buffer, " Memory block was deallocated by \"%s\" at %s.%lu\n", + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + st_OutputLastVerifiedPoint(); + + st_OutputFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + FORTIFY_FILL_ON_DEALLOCATE_VALUE, h->Size); + +#ifdef FORTIFY_FILL_ON_CORRUPTION + st_SetFortification(ptr + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + FORTIFY_FILL_ON_DEALLOCATE_VALUE, h->Size); +#endif /* FORTIFY_FILL_ON_CORRUPTION */ + result = 0; + } +#endif /* FORTIFY_FILL_ON_DEALLOCATE */ + return result; + } + +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + +/* + * st_CheckFortification - Checks if the _size_ + * bytes from _ptr_ are all set to _value_ + * Returns true if all is happy. + */ +static int +st_CheckFortification(unsigned char *ptr, unsigned char value, size_t size) +{ + while(size--) + if(*ptr++ != value) + return(0); + + return(1); +} + +/* + * st_SetFortification - Set the _size_ bytes from _ptr_ to _value_. + */ +static void +st_SetFortification(unsigned char *ptr, unsigned char value, size_t size) +{ + memset(ptr, value, size); +} + +/* + * st_OutputFortification - Output the corrupted section of the fortification + */ +static void +st_OutputFortification(unsigned char *ptr, unsigned char value, size_t size) +{ + size_t offset, skipped, advance; + offset = 0; + + sprintf(st_Buffer, " Address Offset Data (%02x)", value); + st_Output(st_Buffer); + + while(offset < size) + { + /* + * Skip 3 or more 'correct' lines + */ + if((size - offset) < 3 * 16) + advance = size - offset; + else + advance = 3 * 16; + if(advance > 0 && st_CheckFortification(ptr+offset, value, advance)) + { + offset += advance; + skipped = advance; + + if(size - offset < 16) + advance = size - offset; + else + advance = 16; + + while(advance > 0 && st_CheckFortification(ptr+offset, value, advance)) + { + offset += advance; + skipped += advance; + if(size - offset < 16) + advance = size - offset; + else + advance = 16; + } + sprintf(st_Buffer, "\n ...%lu bytes skipped...", (unsigned long)skipped); + st_Output(st_Buffer); + continue; + } + else + { + if(size - offset < 16) + st_HexDump(ptr, offset, size-offset, 0); + else + st_HexDump(ptr, offset, 16, 0); + + offset += 16; + } + } + + st_Output("\n"); +} + +/* + * st_HexDump - output a nice hex dump of "size" bytes, starting at "ptr" + "offset" + */ +static void +st_HexDump(unsigned char *ptr, size_t offset, size_t size, int title) +{ + char ascii[17]; + int column; + int output; + + if(title) + st_Output(" Address Offset Data"); + + column = 0; + ptr += offset; + output = 0; + + while(output < size) + { + if(column == 0) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "\n0x%08lx %8lu ", ptr, (unsigned long)offset); +#else + sprintf(st_Buffer, "\n%10p %8lu ", ptr, (unsigned long)offset); +#endif + st_Output(st_Buffer); + } + + sprintf(st_Buffer, "%02x%s", *ptr, ((column % 4) == 3) ? " " : ""); + st_Output(st_Buffer); + + ascii[ column ] = isprint( *ptr ) ? (char)(*ptr) : (char)('.'); + ascii[ column + 1 ] = '\0'; + + ptr++; + offset++; + output++; + column++; + + if(column == 16) + { + st_Output( " \"" ); + st_Output( ascii ); + st_Output( "\"" ); + column = 0; + } + } + + if ( column != 0 ) + { + while ( column < 16 ) + { + if( column % 4 == 3 ) + st_Output( " " ); + else + st_Output( " " ); + + column++; + } + st_Output( " \"" ); + st_Output( ascii ); + st_Output( "\"" ); + } +} + +/* + * st_IsHeaderValid - Returns true if the + * supplied pointer does indeed point to a + * real Header + */ +static int +st_IsHeaderValid(struct Header *h) +{ + return(st_ChecksumHeader(h) == FORTIFY_CHECKSUM_VALUE); +} + +/* + * st_MakeHeaderValid - Updates the checksum + * to make the header valid + */ +static void +st_MakeHeaderValid(struct Header *h) +{ + h->Checksum = 0; + h->Checksum = (unsigned short)(FORTIFY_CHECKSUM_VALUE - st_ChecksumHeader(h)); +} + +/* + * st_ChecksumHeader - Calculate (and return) + * the checksum of the header. (Including the + * Checksum field itself. If all is well, the + * checksum returned by this function should + * be FORTIFY_CHECKSUM_VALUE + */ +static unsigned short +st_ChecksumHeader(struct Header *h) +{ + unsigned short c, checksum, *p; + + for(c = 0, checksum = 0, p = (unsigned short *)h; + c < FORTIFY_HEADER_SIZE/sizeof(unsigned short); c++) + { + checksum += *p++; + } + + return(checksum); +} + +/* + * st_IsOnAllocatedList - Examines the allocated + * list to see if the given header is on it. + */ +static int +st_IsOnAllocatedList(struct Header *h) +{ + struct Header *curr; + + curr = st_AllocatedHead; + while(curr) + { + if(curr == h) + return(1); + + curr = curr->Next; + } + + return(0); +} + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY +/* + * st_IsOnDeallocatedList - Examines the deallocated + * list to see if the given header is on it. + */ +static int +st_IsOnDeallocatedList(struct Header *h) +{ + struct Header *curr; + + curr = st_DeallocatedHead; + while(curr) + { + if(curr == h) + return(1); + + curr = curr->Next; + } + + return(0); +} + +/* + * st_PurgeDeallocatedBlocks - free at least "Bytes" + * worth of deallocated memory, starting at the + * oldest deallocated block. + * Returns true if any blocks were freed. + */ +static int +st_PurgeDeallocatedBlocks(unsigned long Bytes, const char *file, unsigned long line) +{ + unsigned long FreedBytes = 0; + unsigned long FreedBlocks = 0; + +#ifdef FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY + sprintf(st_Buffer, "\nFortify: Warning - Discarding deallocated memory at %s.%lu\n", + file, line); + st_Output(st_Buffer); +#endif /* FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ + + while(st_DeallocatedTail && FreedBytes < Bytes) + { + st_CheckDeallocatedBlock(st_DeallocatedTail, file, line); + FreedBytes += st_DeallocatedTail->Size; + FreedBlocks++; +#ifdef FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY +#ifdef FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY + sprintf(st_Buffer, " %s\n", + st_DeallocatedMemoryBlockString(st_DeallocatedTail)); + st_Output(st_Buffer); +#endif /* FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ +#endif /* FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ + st_FreeDeallocatedBlock(st_DeallocatedTail, file, line); + } + + return FreedBlocks != 0; +} + +/* + * st_PurgeDeallocatedScope - free all deallocated + * memory blocks that were allocated within "Scope" + */ +static int +st_PurgeDeallocatedScope(unsigned char Scope, const char *file, unsigned long line) +{ + struct Header *curr, *next; + unsigned long FreedBlocks = 0; + + curr = st_DeallocatedHead; + while(curr) + { + next = curr->Next; + if(curr->Scope >= Scope) + { + st_FreeDeallocatedBlock(curr, file, line); + FreedBlocks++; + } + + curr = next; + } + + return FreedBlocks != 0; +} + +/* + * st_FreeDeallocatedBlock - actually remove + * a deallocated block from the deallocated + * list, and actually free it's memory. + */ +static void +st_FreeDeallocatedBlock(struct Header *h, const char *file, unsigned long line) +{ + st_CheckDeallocatedBlock( h, file, line ); + + /* + * Begin Critical region + */ + FORTIFY_LOCK(); + + st_TotalDeallocated -= h->Size; + + if(st_DeallocatedHead == h) + { + st_DeallocatedHead = h->Next; + } + + if(st_DeallocatedTail == h) + { + st_DeallocatedTail = h->Prev; + } + + if(h->Prev) + { + st_CheckDeallocatedBlock(h->Prev, file, line); + h->Prev->Next = h->Next; + st_MakeHeaderValid(h->Prev); + } + + if(h->Next) + { + st_CheckDeallocatedBlock(h->Next, file, line); + h->Next->Prev = h->Prev; + st_MakeHeaderValid(h->Next); + } + + /* + * Free the label + */ + if(h->Label) + { + free(h->Label); + } + + /* + * Nuke out all memory that is about to be freed, including the header + */ + st_SetFortification((unsigned char*)h, FORTIFY_FILL_ON_DEALLOCATE_VALUE, + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE + h->Size + FORTIFY_AFTER_SIZE); + + /* + * And do the actual free + */ + free(h); + + /* + * End critical region + */ + FORTIFY_UNLOCK(); +} + +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + +/* + * st_OutputMemory - Hex and ascii dump the + * user memory of a block. + */ +static void +st_OutputMemory(struct Header *h) +{ + st_HexDump((unsigned char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + 0, h->Size, 1); +} + + +/* + * st_OutputHeader - Output the header + */ +static void +st_OutputHeader(struct Header *h) +{ + if(h->Label == NULL) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "0x%08lx %8lu %s.%lu\n", +#else + sprintf(st_Buffer, "%10p %8lu %s.%lu\n", +#endif + (unsigned char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, + h->File, h->Line); + } + else + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_Buffer, "0x%08lx %8lu %s.%lu %s\n", +#else + sprintf(st_Buffer, "%10p %8lu %s.%lu %s\n", +#endif + (unsigned char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, + h->File, h->Line, h->Label); + } + st_Output(st_Buffer); +} + +/* + * st_OutputLastVerifiedPoint - output the last + * known point where everything was hoopy. + */ +static void +st_OutputLastVerifiedPoint() +{ + sprintf(st_Buffer, " Memory integrity was last verified at %s.%lu\n", + st_LastVerifiedFile, + st_LastVerifiedLine); + st_Output(st_Buffer); +} + +/* + * st_MemoryBlockString - constructs a string that + * desribes a memory block. (pointer,size,allocator,label) + */ +static const char * +st_MemoryBlockString(struct Header *h) +{ + static char st_BlockString[512]; + + if(h->Label == 0) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_BlockString,"(0x%08lx,%lu,%s.%lu)", +#else + sprintf(st_BlockString,"(%p,%lu,%s.%lu)", +#endif + (char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, h->File, h->Line); + } + else + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_BlockString,"(0x%08lx,%lu,%s.%lu,%s)", +#else + sprintf(st_BlockString,"(%p,%lu,%s.%lu,%s)", +#endif + (char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, h->File, h->Line, h->Label); + } + + return st_BlockString; +} + +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY +#ifdef FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY +#ifdef FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY +/* + * st_DeallocatedMemoryBlockString - constructs + * a string that desribes a deallocated memory + * block. (pointer,size,allocator,deallocator) + */ + +static const char * +st_DeallocatedMemoryBlockString(struct Header *h) +{ + static char st_BlockString[256]; + + if(h->Label == 0) + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_BlockString,"(0x%08lx,%lu,%s.%lu,%s.%lu)", +#else + sprintf(st_BlockString,"(%p,%lu,%s.%lu,%s.%lu)", +#endif + (char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, h->File, h->Line, h->FreedFile, h->FreedLine); + } + else + { +#ifdef FORTIFY_NO_PERCENT_P + sprintf(st_BlockString,"(0x%08lx,%lu,%s.%lu,%s.%lu,%s)", +#else + sprintf(st_BlockString,"(%p,%lu,%s.%lu,%s.%lu,%s)", +#endif + (char*)h + FORTIFY_HEADER_SIZE + FORTIFY_ALIGNED_BEFORE_SIZE, + (unsigned long)h->Size, h->File, h->Line, h->FreedFile, h->FreedLine, h->Label); + } + + return st_BlockString; +} +#endif /* FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ +#endif /* FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ +#endif /* FORTIFY_TRACK_DEALLOCATED_MEMORY */ + + +/* + * st_DefaultOutput - the default output function + */ +static void +st_DefaultOutput(const char *String) +{ + fprintf(stdout, String); + fflush(stdout); +} + +/* + * Fortify_malloc - Fortify's replacement malloc() + */ +void *FORTIFY_STORAGE +Fortify_malloc(size_t size, const char *file, unsigned long line) +{ + return Fortify_Allocate(size, Fortify_Allocator_malloc, file, line); +} + +/* + * Fortify_realloc - Fortify's replacement realloc() + */ +void * +Fortify_realloc(void *uptr, size_t new_size, const char *file, unsigned long line) +{ + unsigned char *ptr = (unsigned char *)uptr - FORTIFY_HEADER_SIZE - FORTIFY_ALIGNED_BEFORE_SIZE; + struct Header *h = (struct Header *)ptr; + void *new_ptr; + + /* + * If Fortify is disabled, we gotta do this a little + * differently. + */ + if(!st_Disabled) + { + if(!uptr) + return(Fortify_Allocate(new_size, Fortify_Allocator_realloc, file, line)); + + if(!st_IsOnAllocatedList(h)) + { +#ifdef FORTIFY_TRACK_DEALLOCATED_MEMORY + if(st_IsOnDeallocatedList(h)) + { + sprintf(st_Buffer, "\nFortify: Deallocated memory block passed to \"%s\" at %s.%lu\n", + st_AllocatorName[Fortify_Allocator_realloc], file, line); + st_Output(st_Buffer); + sprintf(st_Buffer, " Memory block %s was deallocated by \"%s\" at %s.%lu\n", + st_MemoryBlockString(h), + st_DeallocatorName[h->Deallocator], h->FreedFile, h->FreedLine); + st_Output(st_Buffer); + return 0; + } +#endif + + sprintf(st_Buffer, +#ifdef FORTIFY_NO_PERCENT_P + "\nFortify: Invalid pointer (0x%08lx) passed to realloc at %s.%lu\n", +#else + "\nFortify: Invalid pointer (%p) passed to realloc at %s.%lu\n", +#endif + ptr, file, line); + st_Output(st_Buffer); + return 0; + } + + if(!st_CheckBlock(h, file, line)) + return 0; + + new_ptr = Fortify_Allocate(new_size, Fortify_Allocator_realloc, file, line); + if(!new_ptr) + { + return(0); + } + + if(h->Size < new_size) + memcpy(new_ptr, uptr, h->Size); + else + memcpy(new_ptr, uptr, new_size); + + Fortify_Deallocate(uptr, Fortify_Deallocator_realloc, file, line); + return(new_ptr); + } + else + { + /* + * If the old block was fortified, we can't use normal realloc. + */ + if(st_IsOnAllocatedList(h)) + { + new_ptr = Fortify_Allocate(new_size, Fortify_Allocator_realloc, file, line); + if(!new_ptr) + return(0); + + if(h->Size < new_size) + memcpy(new_ptr, uptr, h->Size); + else + memcpy(new_ptr, uptr, new_size); + + Fortify_Deallocate(uptr, Fortify_Deallocator_realloc, file, line); + return(new_ptr); + } + else /* easy */ + { + return realloc(uptr, new_size); + } + } +} + +/* + * Fortify_calloc - Fortify's replacement calloc + */ +void * +Fortify_calloc(size_t num, size_t size, const char *file, unsigned long line) +{ + if(!st_Disabled) + { + void *ptr = Fortify_Allocate(size * num, Fortify_Allocator_calloc, file, line); + if(ptr) + { + memset(ptr, 0, size*num); + } + return ptr; + } + else + { + return calloc(num, size); + } +} + +/* + * Fortify_free - Fortify's replacement free + */ +void +Fortify_free(void *uptr, const char *file, unsigned long line) +{ + /* it is defined to be safe to free(0) */ + if(uptr == 0) + return; + + Fortify_Deallocate(uptr, Fortify_Deallocator_free, file, line); +} + +/* + * Fortify_strdup - Fortify's replacement strdup. Since strdup isn't + * ANSI, it is only provided if FORTIFY_STRDUP is defined. + */ +#ifdef FORTIFY_STRDUP +char *FORTIFY_STORAGE +Fortify_strdup(const char *oldStr, const char *file, unsigned long line) +{ + if(!st_Disabled) + { + char *newStr = (char*)Fortify_Allocate(strlen(oldStr)+1, Fortify_Allocator_strdup, file, line); + if(newStr) + { + strcpy(newStr, oldStr); + } + + return newStr; + } + else + { + return strdup(oldStr); + } +} +#endif /* FORTIFY_STRDUP */ + +static void +st_OutputDeleteTrace() +{ +#ifdef __cplusplus + if(st_DeleteStackTop > 1) + { + sprintf(st_Buffer, "Delete Trace: %s.%lu\n", st_DeleteFile[st_DeleteStackTop-1], + st_DeleteLine[st_DeleteStackTop-1]); + st_Output(st_Buffer); + for(int c = st_DeleteStackTop-2; c >= 0; c--) + { + sprintf(st_Buffer, " %s.%lu\n", st_DeleteFile[c], + st_DeleteLine[c]); + st_Output(st_Buffer); + } + } +#endif +} + +#ifdef __cplusplus + +/* + * st_NewHandler() - there is no easy way to get + * the new handler function. And isn't it great + * how the new handler doesn't take a parameter + * giving the size of the request that failed. + * Thanks Bjarne! + */ +Fortify_NewHandlerFunc +st_NewHandler() +{ + /* get the current handler */ + Fortify_NewHandlerFunc handler = set_new_handler(0); + + /* and set it back (since we cant + * get it without changing it) + */ + set_new_handler(handler); + + return handler; +} + +/* + * operator new - Fortify's replacement new, + * without source-code information. + */ +void *FORTIFY_STORAGE +operator new(size_t size) +{ + void *p; + + while((p = Fortify_Allocate(size, Fortify_Allocator_new, + st_AllocatorName[Fortify_Allocator_new], 0)) == 0) + { + if(st_NewHandler()) + (*st_NewHandler())(); + else + return 0; + } + + return p; +} + +/* + * operator new - Fortify's replacement new, + * with source-code information + */ +void *FORTIFY_STORAGE +operator new(size_t size, const char *file, unsigned long line) +{ + void *p; + + while((p = Fortify_Allocate(size, Fortify_Allocator_new, file, line)) == 0) + { + if(st_NewHandler()) + (*st_NewHandler())(); + else + return 0; + } + + return p; +} + +#ifdef FORTIFY_PROVIDE_ARRAY_NEW + +/* + * operator new[], without source-code info + */ +void *FORTIFY_STORAGE +operator new[](size_t size) +{ + void *p; + + while((p = Fortify_Allocate(size, Fortify_Allocator_array_new, + st_AllocatorName[Fortify_Allocator_array_new], 0)) == 0) + { + if(st_NewHandler()) + (*st_NewHandler())(); + else + return 0; + } + + return p; +} + +/* + * operator new[], with source-code info + */ +void *FORTIFY_STORAGE +operator new[](size_t size, const char *file, unsigned long line) +{ + void *p; + + while((p = Fortify_Allocate(size, Fortify_Allocator_array_new, file, line)) == 0) + { + if(st_NewHandler()) + (*st_NewHandler())(); + else + return 0; + } + + return p; +} + +#endif /* FORTIFY_PROVIDE_ARRAY_NEW */ + +/* + * Fortify_PreDelete - C++ does not allow overloading + * of delete, so the delete macro calls Fortify_PreDelete + * with the source-code info, and then calls delete. + */ +void FORTIFY_STORAGE +Fortify_PreDelete(const char *file, unsigned long line) +{ + FORTIFY_LOCK(); + + /* + * Push the source code info for the delete onto the delete stack + * (if we have enough room, of course) + */ + if(st_DeleteStackTop < FORTIFY_DELETE_STACK_SIZE) + { + st_DeleteFile[st_DeleteStackTop] = file; + st_DeleteLine[st_DeleteStackTop] = line; + } + + st_DeleteStackTop++; +} + +/* + * Fortify_PostDelete() - Pop the delete source-code info + * off the source stack. + */ +void FORTIFY_STORAGE +Fortify_PostDelete() +{ + st_DeleteStackTop--; + + FORTIFY_UNLOCK(); +} + +/* + * operator delete - fortify's replacement delete + */ +void FORTIFY_STORAGE +operator delete(void *uptr) +{ + const char *file; + unsigned long line; + + /* + * It is defined to be harmless to delete 0 + */ + if(uptr == 0) + return; + + /* + * find the source-code info + */ + if(st_DeleteStackTop) + { + if(st_DeleteStackTop < FORTIFY_DELETE_STACK_SIZE) + { + file = st_DeleteFile[st_DeleteStackTop-1]; + line = st_DeleteLine[st_DeleteStackTop-1]; + } + else + { + file = st_DeleteFile[FORTIFY_DELETE_STACK_SIZE-1]; + line = st_DeleteLine[FORTIFY_DELETE_STACK_SIZE-1]; + } + } + else + { + file = st_DeallocatorName[Fortify_Deallocator_delete]; + line = 0; + } + + Fortify_Deallocate(uptr, Fortify_Deallocator_delete, file, line); +} + +#ifdef FORTIFY_PROVIDE_ARRAY_DELETE + +/* + * operator delete[] - fortify's replacement delete[] + */ +void FORTIFY_STORAGE +operator delete[](void *uptr) +{ + const char *file; + unsigned long line; + + /* + * It is defined to be harmless to delete 0 + */ + if(uptr == 0) + return; + + /* + * find the source-code info + */ + if(st_DeleteStackTop) + { + if(st_DeleteStackTop < FORTIFY_DELETE_STACK_SIZE) + { + file = st_DeleteFile[st_DeleteStackTop-1]; + line = st_DeleteLine[st_DeleteStackTop-1]; + } + else + { + file = st_DeleteFile[FORTIFY_DELETE_STACK_SIZE-1]; + line = st_DeleteLine[FORTIFY_DELETE_STACK_SIZE-1]; + } + } + else + { + file = st_DeallocatorName[Fortify_Deallocator_array_delete]; + line = 0; + } + + Fortify_Deallocate(uptr, Fortify_Deallocator_array_delete, file, line); +} + +#endif /* FORTIFY_PROVIDE_ARRAY_DELETE */ + +#ifdef FORTIFY_AUTOMATIC_LOG_FILE +/* Automatic log file stuff! + * + * AutoLogFile class. There can only ever be ONE of these + * instantiated! It is a static class, which means that + * it's constructor will be called at program initialization, + * and it's destructor will be called at program termination. + * We don't know if the other static class objects have been + * constructed/destructed yet, but this pretty much the best + * we can do with standard C++ language features. + */ +class Fortify_AutoLogFile +{ + static FILE *fp; + static int written_something; + static char *init_string, *term_string; + +public: + Fortify_AutoLogFile() + { + written_something = 0; + Fortify_SetOutputFunc(Fortify_AutoLogFile::Output); + Fortify_EnterScope(init_string, 0); + } + + static void Output(const char *s) + { + if(written_something == 0) + { + FORTIFY_FIRST_ERROR_FUNCTION; + fp = fopen(FORTIFY_LOG_FILENAME, "w"); + if(fp) + { + time_t t; + time(&t); + fprintf(fp, "Fortify log started at %s\n", ctime(&t)); + written_something = 1; + } + } + + if(fp) + { + fputs(s, fp); + fflush(fp); + } + } + + ~Fortify_AutoLogFile() + { + Fortify_LeaveScope(term_string, 0); + Fortify_CheckAllMemory(term_string, 0); + if(fp) + { + time_t t; + time(&t); + fprintf(fp, "\nFortify log closed at %s\n", ctime(&t)); + fclose(fp); + fp = 0; + } + } +}; + +FILE *Fortify_AutoLogFile::fp = 0; +int Fortify_AutoLogFile::written_something = 0; +char *Fortify_AutoLogFile::init_string = "Program Initialization"; +char *Fortify_AutoLogFile::term_string = "Program Termination"; + +static Fortify_AutoLogFile Abracadabra; + +#endif /* FORTIFY_AUTOMATIC_LOG_FILE */ + +#endif /* __cplusplus */ + +#endif /* FORTIFY */ diff --git a/minorGems/util/development/fortify/fortify.h b/minorGems/util/development/fortify/fortify.h new file mode 100644 index 0000000..3dabff1 --- /dev/null +++ b/minorGems/util/development/fortify/fortify.h @@ -0,0 +1,273 @@ +/* fortify.h - V2.2 - All C & C++ source files to be fortified should #include this file */ + +/* + * This software is not public domain. All material in + * this archive is (C) Copyright 1995 Simon P. Bullen. The + * software is freely distributable, with the condition that + * no more than a nominal fee is charged for media. + * Everything in this distribution must be kept together, in + * original, unmodified form. + * The software may be modified for your own personal use, + * but modified files may not be distributed. + * The material is provided "as is" without warranty of + * any kind. The author accepts no responsibility for damage + * caused by this software. + * This software may not be used in any way by Microsoft + * Corporation or its subsidiaries, or current employees of + * Microsoft Corporation or its subsidiaries. + * This software may not be used for the construction, + * development, production, or testing of weapon systems of + * any kind. + * This software may not be used for the construction, + * development, production, or use of plants/installations + * which include the processing of radioactive/fissionable + * material. + */ + +/* + * If you use this software at all, I'd love to hear from + * you. All questions, criticisms, suggestions, praise and + * postcards are most welcome. + * + * email: sbullen@cybergraphic.com.au + * + * snail: Simon P. Bullen + * PO BOX 12138 + * A'Beckett St. + * Melbourne 3000 + * Australia + */ + +#ifndef __FORTIFY_H__ +#define __FORTIFY_H__ + +#include +#include + +/* the user's options */ +#include "ufortify.h" + +/* Ensure the configuration parameters have sensible defaults */ +#ifndef FORTIFY_STORAGE + #define FORTIFY_STORAGE +#endif + +#ifndef FORTIFY_ALIGNMENT + #define FORTIFY_ALIGNMENT sizeof(double) +#endif + +#ifndef FORTIFY_BEFORE_SIZE + #define FORTIFY_BEFORE_SIZE 32 +#endif +#ifndef FORTIFY_BEFORE_VALUE + #define FORTIFY_BEFORE_VALUE 0xA3 +#endif + +#ifndef FORTIFY_AFTER_SIZE + #define FORTIFY_AFTER_SIZE 32 +#endif + +#ifndef FORTIFY_AFTER_VALUE + #define FORTIFY_AFTER_VALUE 0xA5 +#endif + +#ifndef FORTIFY_FILL_ON_ALLOCATE_VALUE + #define FORTIFY_FILL_ON_ALLOCATE_VALUE 0xA7 +#endif + +#ifndef FORTIFY_FILL_ON_DEALLOCATE_VALUE + #define FORTIFY_FILL_ON_DEALLOCATE_VALUE 0xA9 +#endif + +#ifndef FORTIFY_LOCK + #define FORTIFY_LOCK() +#endif + +#ifndef FORTIFY_UNLOCK + #define FORTIFY_UNLOCK() +#endif + +#ifndef FORTIFY_CHECKSUM_VALUE + #define FORTIFY_CHECKSUM_VALUE 0x0AD0 +#endif + +#ifndef FORTIFY_DELETE_STACK_SIZE + #define FORTIFY_DELETE_STACK_SIZE 256 +#endif + +#ifndef FORTIFY_NEW_HANDLER_FUNC + typedef void (*Fortify_NewHandlerFunc)(void); + #define FORTIFY_NEW_HANDLER_FUNC Fortify_NewHandlerFunc +#endif + +/* + * Code to detect and configure for various compilers lives here. + */ + +#ifdef __GNUG__ + /* GCC configuration */ + #define FORTIFY_PROVIDE_ARRAY_NEW + #define FORTIFY_PROVIDE_ARRAY_DELETE +#endif + +#ifdef __BC45__ + /* Borland C++ 4.5 configuration */ + #define FORTIFY_PROVIDE_ARRAY_NEW + #define FORTIFY_PROVIDE_ARRAY_DELETE + #define FORTIFY_FAIL_ON_ZERO_MALLOC +#endif + +#ifdef __SASC + /* SAS configuration */ + #define FORTIFY_FAIL_ON_ZERO_MALLOC +#endif + +/* Allocators */ +#define Fortify_Allocator_malloc 0 /* ANSI C */ +#define Fortify_Allocator_calloc 1 /* ANSI C */ +#define Fortify_Allocator_realloc 2 /* ANSI C */ +#define Fortify_Allocator_strdup 3 /* C */ +#define Fortify_Allocator_new 4 /* ANSI C++ */ +#define Fortify_Allocator_array_new 5 /* Some C++ */ + +/* Deallocators */ +#define Fortify_Deallocator_nobody 0 +#define Fortify_Deallocator_free 1 /* ANSI C */ +#define Fortify_Deallocator_realloc 2 /* ANSI C */ +#define Fortify_Deallocator_delete 3 /* ANSI C++ */ +#define Fortify_Deallocator_array_delete 4 /* Some C++ */ + +/* Public Fortify Types */ +typedef void (*Fortify_OutputFuncPtr)(const char *); + +#ifdef __cplusplus +extern "C" { +#endif + +/* Core Fortify Functions */ +void *Fortify_Allocate (size_t size, unsigned char allocator, const char *file, unsigned long line); +void Fortify_Deallocate(void *uptr, unsigned char deallocator, const char *file, unsigned long line); +unsigned long Fortify_CheckAllMemory(const char *file, unsigned long line); +unsigned long Fortify_ListAllMemory (const char *file, unsigned long line); +unsigned long Fortify_DumpAllMemory (const char *file, unsigned long line); +int Fortify_CheckPointer(void *uptr, const char *file, unsigned long line); +void Fortify_LabelPointer(void *uptr, const char *label, const char *file, unsigned long line); +unsigned char Fortify_EnterScope(const char *file, unsigned long line); +unsigned char Fortify_LeaveScope(const char *file, unsigned long line); +void Fortify_OutputStatistics(const char *file, unsigned long line); +unsigned long Fortify_GetCurrentAllocation(const char *file, unsigned long line); +void Fortify_SetAllocationLimit(unsigned long Limit, const char *file, unsigned long line); +int Fortify_SetFailRate(int Percent); +Fortify_OutputFuncPtr Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output); +void Fortify_Disable(const char *file, unsigned long line); + +/* Fortify versions of the ANSI C memory allocation functions */ +void *Fortify_malloc(size_t size, const char *file, unsigned long line); +void *Fortify_realloc(void *ptr, size_t new_size, const char *file, unsigned long line); +void *Fortify_calloc(size_t num, size_t size, const char *file, unsigned long line); +void Fortify_free(void *uptr, const char *file, unsigned long line); + +/* Fortify versions of some non-ANSI C memory allocation functions */ +#ifdef FORTIFY_STRDUP + char *Fortify_strdup(const char *oldStr, const char *file, unsigned long line); +#endif + +#ifdef __cplusplus +/* Magic global variable */ +extern int gbl_FortifyMagic; +#endif + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +#include + + /* Fortify versions of new and delete */ + void *operator new(size_t size); + void *operator new(size_t size, const char *file, unsigned long line); + void operator delete(void *pointer); + void Fortify_PreDelete(const char *file, unsigned long line); + void Fortify_PostDelete(); + + /* Some compilers use a different new operator for newing arrays. + * This includes GNU G++ (2.6.0) and Borland C++ (4.02) + */ + #ifdef FORTIFY_PROVIDE_ARRAY_NEW + void *operator new[](size_t size); + void *operator new[](size_t size, const char *file, unsigned long line); + #endif + + /* Some compilers provide a different delete operator for deleting arrays. + * This incldues GNU G++ (2.6.0) + */ + #ifdef FORTIFY_PROVIDE_ARRAY_DELETE + void operator delete[](void *pointer); + #endif + +#endif /* __cplusplus */ + +#ifndef __FORTIFY_C__ /* Only define the macros if we're NOT in fortify.c */ + +/* Add file and line information to the fortify calls */ +#ifdef FORTIFY + /* Core Fortify Functions */ + #define Fortify_CheckAllMemory() Fortify_CheckAllMemory(__FILE__, __LINE__) + #define Fortify_ListAllMemory() Fortify_ListAllMemory (__FILE__, __LINE__) + #define Fortify_DumpAllMemory() Fortify_DumpAllMemory (__FILE__, __LINE__) + #define Fortify_CheckPointer(ptr) Fortify_CheckPointer(ptr, __FILE__, __LINE__) + #define Fortify_LabelPointer(ptr,str) Fortify_LabelPointer(ptr, str, __FILE__, __LINE__) + #define Fortify_EnterScope() Fortify_EnterScope(__FILE__, __LINE__) + #define Fortify_LeaveScope() Fortify_LeaveScope(__FILE__, __LINE__) + #define Fortify_OutputStatistics() Fortify_OutputStatistics(__FILE__, __LINE__) + #define Fortify_GetCurrentAllocation() Fortify_GetCurrentAllocation(__FILE__, __LINE__) + #define Fortify_SetAllocationLimit(x) Fortify_SetAllocationLimit(x, __FILE__, __LINE__) + #define Fortify_Disable() Fortify_Disable(__FILE__, __LINE__) + + /* Fortify versions of the ANSI C memory allocation functions */ + #define malloc(size) Fortify_malloc(size, __FILE__, __LINE__) + #define realloc(ptr,new_size) Fortify_realloc(ptr, new_size, __FILE__, __LINE__) + #define calloc(num,size) Fortify_calloc(num, size, __FILE__, __LINE__) + #define free(ptr) Fortify_free(ptr, __FILE__, __LINE__) + + /* Fortify versions of some non-ANSI C memory allocation functions */ + #ifdef FORTIFY_STRDUP + #define strdup(ptr) Fortify_strdup(ptr, __FILE__, __LINE__) + #endif + + /* Fortify versions of new and delete */ + #ifdef __cplusplus + #define Fortify_New new(__FILE__, __LINE__) + #define Fortify_Delete for(gbl_FortifyMagic = 1, \ + Fortify_PreDelete(__FILE__, __LINE__); \ + gbl_FortifyMagic; Fortify_PostDelete()) \ + gbl_FortifyMagic = 0, delete + #define new Fortify_New + #define delete Fortify_Delete + #endif /* __cplusplus */ + +#else /* Define the special fortify functions away to nothing */ + + #define Fortify_CheckAllMemory() 0 + #define Fortify_ListAllMemory() 0 + #define Fortify_DumpAllMemory() 0 + #define Fortify_CheckPointer(ptr) 1 + #define Fortify_LabelPointer(ptr,str) + #define Fortify_SetOutputFunc() 0 + #define Fortify_SetMallocFailRate(p) 0 + #define Fortify_EnterScope() 0 + #define Fortify_LeaveScope() 0 + #define Fortify_OutputStatistics() 0 + #define Fortify_GetCurrentAllocation() 0 + #define Fortify_SetAllocationLimit(x) 0 + #define Fortify_Disable() 0 + + #ifdef __cplusplus + #define Fortify_New new + #define Fortify_Delete delete + #endif /* __cplusplus */ + +#endif /* FORTIFY */ +#endif /* __FORTIFY_C__ */ +#endif /* __FORTIFY_H__ */ diff --git a/minorGems/util/development/fortify/test.c b/minorGems/util/development/fortify/test.c new file mode 100644 index 0000000..a6e9c43 --- /dev/null +++ b/minorGems/util/development/fortify/test.c @@ -0,0 +1,61 @@ +#include +#include +#include "fortify.h" + +/* + * NOTE: The Fortify routines will compile away to nothing + * if FORTIFY isn't defined in the makefile. + * + * DO NOT insert #define FORTIFY here. It Will Not Work. + * All files (including fortify.cxx) need FORTIFY to be + * defined. The correct place for this is the makefile. + */ + + +int +main(int argc, char **argv) +{ + char *foo, *bar; + + Fortify_EnterScope(); + + /* note that we never free this memory */ + foo = malloc(123); + + Fortify_LabelPointer(foo, "we never free this memory!"); + + /* note that we read this memory after it's been freed */ + foo = malloc(124); + *foo = 'X'; + free(foo); + printf("Should be X: '%c'\n", *foo); + + /* note we've already freed this memory */ + free(foo); + + /* note we write to memory that's been realloc'd, using the old pointer */ + foo = malloc(125); + bar = realloc(foo, 126); + *foo = 'X'; + free(bar); + + /* note we write before the block */ + foo = malloc(127); + Fortify_LabelPointer(foo, "we write before this block!"); + *(foo-1) = 'Z'; + free(foo); + + /* note we write after the block */ + bar = "I'm going to eat you little fishie!"; + foo = malloc(strlen(bar)); + strcpy(foo, bar); + free(foo); + + /* we never allocated this memory */ + free(bar); + + Fortify_LeaveScope(); + Fortify_OutputStatistics(); + + return 42; +} diff --git a/minorGems/util/development/fortify/test2.cpp b/minorGems/util/development/fortify/test2.cpp new file mode 100644 index 0000000..24311e6 --- /dev/null +++ b/minorGems/util/development/fortify/test2.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "fortify.h" + +/* + * NOTE: The Fortify routines will compile away to nothing + * if FORTIFY isn't defined in the makefile. + * + * DO NOT insert #define FORTIFY here. It Will Not Work. + * All files (including fortify.cxx) need FORTIFY to be + * defined. The correct place for this is the makefile. + */ + +class A +{ +public: + ~A() { delete (char*)123; } +}; + +int +main(int argc, char **argv) +{ + char *foo; + + Fortify_EnterScope(); + + /* zero size test */ + foo =(char*) malloc(0); + printf("malloc(0) %s\n", foo ? "succeeded" : "failed"); + + /* zero size test */ + foo = new char[0]; + printf("new char[0] %s\n", foo ? "succeeded" : "failed"); + + + foo = new char; + + /* note we use the incorrect deallocator */ + /* note this will only be detected if FORTIFY_PROVIDE_ARRAY_NEW + * and FORTIFY_PROVIDE_ARRAY_DELETE are both turned on + * (and your compiler supports them) */ + delete[] foo; + *foo = 'Z'; + + foo = new char; + Fortify_LabelPointer(foo, "we use the wrong deallocator on this one"); + + /* note we use the incorrect dealocator */ + free(foo); + + /* the destructor of this class does an illegal delete - + * demonstrates the delete-stack + */ + delete new A; + + Fortify_LeaveScope(); + + return 1; +} + + + + + + diff --git a/minorGems/util/development/fortify/test3.cpp b/minorGems/util/development/fortify/test3.cpp new file mode 100644 index 0000000..22ed52e --- /dev/null +++ b/minorGems/util/development/fortify/test3.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "fortify.h" + + +int main() { + //Fortify_EnterScope(); + + int *a = new int[ 10 ]; + + int i; + for( i=0; i<10; i++ ) { + + a[i] = i; + + } + + for( i=0; i<10; i++ ) { + + printf( "a[i] = %d\n", a[i] ); + + } + + + delete [] a; + + for( i=0; i<10; i++ ) { + + printf( "a[i] = %d\n", a[i] ); + + } + //Fortify_CheckAllMemory(); + + for( i=0; i<10; i++ ) { + + a[i] = i; + + } + for( i=0; i<10; i++ ) { + + printf( "a[i] = %d\n", a[i] ); + + } + + //Fortify_CheckAllMemory(); + + int *b = new int[5]; + + delete [] b; + + delete [] a; + + //Fortify_LeaveScope(); + + return 0; + } diff --git a/minorGems/util/development/fortify/ufortify.h b/minorGems/util/development/fortify/ufortify.h new file mode 100644 index 0000000..b5b6faa --- /dev/null +++ b/minorGems/util/development/fortify/ufortify.h @@ -0,0 +1,79 @@ +/* + * Modification History + * + * 2002-March-29 Jason Rohrer + * Added prototypes for locking functions. + */ + +/* + * FILE: + * ufortify.h + * + * DESCRIPTION: + * User options for fortify. Changes to this file require fortify.c to be + * recompiled, but nothing else. + */ + +#define FORTIFY_STORAGE /* storage for public functions */ + +#define FORTIFY_ALIGNMENT sizeof(double) /* Byte alignment of all memory blocks */ + +#define FORTIFY_BEFORE_SIZE 32 /* Bytes to allocate before block */ +#define FORTIFY_BEFORE_VALUE 0xA3 /* Fill value before block */ + +#define FORTIFY_AFTER_SIZE 32 /* Bytes to allocate after block */ +#define FORTIFY_AFTER_VALUE 0xA5 /* Fill value after block */ + +#define FORTIFY_FILL_ON_ALLOCATE /* Nuke out malloc'd memory */ +#define FORTIFY_FILL_ON_ALLOCATE_VALUE 0xA7 /* Value to initialize with */ + +#define FORTIFY_FILL_ON_DEALLOCATE /* free'd memory is cleared */ +#define FORTIFY_FILL_ON_DEALLOCATE_VALUE 0xA9 /* Value to de-initialize with */ + +#define FORTIFY_FILL_ON_CORRUPTION /* Nuke out corrupted memory */ + +#define FORTIFY_CHECK_ALL_MEMORY_ON_ALLOCATE +#define FORTIFY_CHECK_ALL_MEMORY_ON_DEALLOCATE + +#define FORTIFY_PARANOID_DEALLOCATE + +#define FORTIFY_WARN_ON_ZERO_MALLOC /* A debug is issued on a malloc(0) */ +/* #define FORTIFY_FAIL_ON_ZERO_MALLOC */ /* A malloc(0) will fail */ + +#define FORTIFY_WARN_ON_ALLOCATE_FAIL /* A debug is issued on a failed alloc */ +#define FORTIFY_WARN_ON_FALSE_FAIL /* See Fortify_SetAllocateFailRate */ +#define FORTIFY_WARN_ON_SIZE_T_OVERFLOW /* Watch for breaking the 64K limit in */ + /* some braindead architectures... */ + +#define FORTIFY_TRACK_DEALLOCATED_MEMORY +#define FORTIFY_DEALLOCATED_MEMORY_LIMIT 1048576 /* Maximum amount of deallocated bytes to keep */ +/* #define FORTIFY_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ +/* #define FORTIFY_VERBOSE_WARN_WHEN_DISCARDING_DEALLOCATED_MEMORY */ + +/* #define FORTIFY_NO_PERCENT_P */ /* sprintf() doesn't support %p */ +#define FORTIFY_STRDUP /* if you use non-ANSI strdup() */ + +// prototypes +void Fortify_Lock(); +void Fortify_Unlock(); + + +#define FORTIFY_LOCK() Fortify_Lock() +#define FORTIFY_UNLOCK() Fortify_Unlock() + + +#define FORTIFY_DELETE_STACK_SIZE 256 + +#ifdef __cplusplus /* C++ only options go here */ + +/* #define FORTIFY_PROVIDE_ARRAY_NEW */ +/* #define FORTIFY_PROVIDE_ARRAY_DELETE */ + #define FORTIFY_PROVIDE_ARRAY_NEW + #define FORTIFY_PROVIDE_ARRAY_DELETE + +/* #define FORTIFY_AUTOMATIC_LOG_FILE */ + #define FORTIFY_LOG_FILENAME "fortify.log" + #include + #define FORTIFY_FIRST_ERROR_FUNCTION cout << "\a\a\aFortify Hit Generated!\n" + +#endif /* __cplusplus */ diff --git a/minorGems/util/development/leakTracer/LeakCheck b/minorGems/util/development/leakTracer/LeakCheck new file mode 100755 index 0000000..563de58 --- /dev/null +++ b/minorGems/util/development/leakTracer/LeakCheck @@ -0,0 +1,22 @@ +#!/bin/sh +if [ $# -lt 1 ] ; then + echo "Usage: $0 " + exit 1 +fi + +# this looks in the same directory, this +# LeakCheck script resides; modify to your +# needs: +SHLIB=`dirname $0`/LeakTracer.so +if [ ! -x $SHLIB ] ; then + echo "$SHLIB not found" + exit 1 +fi + +if [ -z "$LEAKTRACE_FILE" ] ; then + rm -f leak.out +else + rm -f "$LEAKTRACE_FILE" +fi +export LD_PRELOAD=$SHLIB +exec $@ diff --git a/minorGems/util/development/leakTracer/LeakCheckAnalyze b/minorGems/util/development/leakTracer/LeakCheckAnalyze new file mode 100755 index 0000000..a80c9b7 --- /dev/null +++ b/minorGems/util/development/leakTracer/LeakCheckAnalyze @@ -0,0 +1,13 @@ +#!/bin/sh + +CHECKER=`dirname $0`/LeakCheck" $@" +ANALYZER=`dirname $0`/leak-analyze" $1 leak.out" + +echo "Checking with: $CHECKER" +echo "" +$CHECKER + +echo "" +echo "Analyzing with: $ANALYZER" +echo "" +$ANALYZER 2>&1 | more diff --git a/minorGems/util/development/leakTracer/LeakTracer.cc b/minorGems/util/development/leakTracer/LeakTracer.cc new file mode 100644 index 0000000..ab26b19 --- /dev/null +++ b/minorGems/util/development/leakTracer/LeakTracer.cc @@ -0,0 +1,578 @@ +/* + * Modification History + * + * 2002-March-31 Jason Rohrer + * Added support for strdup. + * Fixed bug in strdup. + * + * 2003-October-3 Jason Rohrer + * Added printout of leak contents in report. + * + * 2004-January-16 Jason Rohrer + * Switched to use minorGems platform-independed mutexes. + * Changed to use simpler fopen call to open report. + */ + + +/* + * Homepage: + * + * Authors: + * Erwin S. Andreasen + * Henner Zeller + * + * This program is Public Domain + */ + +#ifdef THREAD_SAVE +#define _THREAD_SAVE +//#include +#include "minorGems/system/MutexLock.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * underlying allocation, de-allocation used within + * this tool + */ +#define LT_MALLOC malloc +#define LT_FREE free +#define LT_REALLOC realloc + +/* + * prime number for the address lookup hash table. + * if you have _really_ many memory allocations, use a + * higher value, like 343051 for instance. + */ +#define SOME_PRIME 35323 +#define ADDR_HASH(addr) ((unsigned long) addr % SOME_PRIME) + +/** + * Filedescriptor to write to. This should not be a low number, + * because these often have special meanings (stdin, out, err) + * and may be closed by the program (daemons) + * So choose an arbitrary higher FileDescriptor .. e.g. 42 + */ +#define FILEDESC 42 + +/** + * allocate a bit more memory in order to check if there is a memory + * overwrite. Either 0 or more than sizeof(unsigned int). Note, you can + * only detect memory over_write_, not _reading_ beyond the boundaries. Better + * use electric fence for these kind of bugs + * + */ +typedef unsigned long magic_t; +#define MAGIC ((magic_t) 0xAABBCCDDLu) + +/** + * this may be more than sizeof(magic_t); if you want more, then + * sepecify it like #define SAVESIZE (sizeof(magic_t) + 12) + */ +#define SAVESIZE (sizeof(magic_t) + 0) + +/** + * on 'new', initialize the memory with this value. + * if not defined - uninitialized. This is very helpful because + * it detects if you initialize your classes correctly .. if not, + * this helps you faster to get the segmentation fault you're + * implicitly asking for :-). + * + * Set this to some value which is likely to produce a + * segmentation fault on your platform. + */ +#define SAVEVALUE 0xAA + +/** + * on 'delete', clean memory with this value. + * if not defined - no memory clean. + * + * Set this to some value which is likely to produce a + * segmentation fault on your platform. + */ +#define MEMCLEAN 0xEE + +/** + * Initial Number of memory allocations in our list. + * Doubles for each re-allocation. + */ +#define INITIALSIZE 32768 + +static class LeakTracer { + struct Leak { + const void *addr; + size_t size; + const void *allocAddr; + bool type; + int nextBucket; + }; + + int newCount; // how many memory blocks do we have + int leaksCount; // amount of entries in the leaks array + int firstFreeSpot; // Where is the first free spot in the leaks array? + int currentAllocated; // currentAllocatedMemory + int maxAllocated; // maximum Allocated + unsigned long totalAllocations; // total number of allocations. stats. + unsigned int abortOn; // resons to abort program (see abortReason_t) + + /** + * Have we been initialized yet? We depend on this being + * false before constructor has been called! + */ + bool initialized; + bool destroyed; // Has our destructor been called? + + + FILE *report; // filedescriptor to write to + + /** + * pre-allocated array of leak info structs. + */ + Leak *leaks; + + /** + * fast hash to lookup the spot where an allocation is + * stored in case of an delete. map + */ + int *leakHash; // fast lookup + +#ifdef THREAD_SAVE + MutexLock mutex; +#endif + + enum abortReason_t { + OVERWRITE_MEMORY = 0x01, + DELETE_NONEXISTENT = 0x02, + NEW_DELETE_MISMATCH = 0x04 + }; + +public: + LeakTracer() { + initialize(); + } + + void initialize() { + // Unfortunately we might be called before our constructor has actualy fired + if (initialized) + return; + + // fprintf(stderr, "LeakTracer::initialize()\n"); + initialized = true; + newCount = 0; + leaksCount = 0; + firstFreeSpot = 1; // index '0' is special + currentAllocated = 0; + maxAllocated = 0; + totalAllocations = 0; + abortOn = OVERWRITE_MEMORY; // only _severe_ reason + report = 0; + leaks = 0; + leakHash = 0; + + char uniqFilename[256]; + const char *filename = getenv("LEAKTRACE_FILE") ? : "leak.out"; + struct stat dummy; + if (stat(filename, &dummy) == 0) { + sprintf(uniqFilename, "%s.%d", filename, getpid()); + fprintf(stderr, + "LeakTracer: file exists; using %s instead\n", + uniqFilename); + } + else { + sprintf(uniqFilename, "%s", filename); + } + + // not sure why this "open" code is here + // (it doesn't open the file properly in MinGW on win32) + /* + int reportfd = open(uniqFilename, + O_WRONLY|O_CREAT|O_TRUNC,S_IREAD|S_IWRITE); + if (reportfd < 0) { + fprintf(stderr, "LeakTracer: cannot open %s: %m\n", + filename); + report = stderr; + } + else { + int dupfd = dup2(reportfd, FILEDESC); + close(reportfd); + report = fdopen(dupfd, "w"); + if (report == NULL) { + report = stderr; + } + } + */ + + // simpler version using only fopen + report = fopen( uniqFilename, "w" ); + if( report == NULL ) { + fprintf( stderr, "LeakTracer: cannot open %s\n", + uniqFilename ); + report = stderr; + } + + + time_t t = time(NULL); + fprintf (report, "# starting %s", ctime(&t)); + + leakHash = (int*) LT_MALLOC(SOME_PRIME * sizeof(int)); + memset ((void*) leakHash, 0x00, SOME_PRIME * sizeof(int)); + +#ifdef MAGIC + fprintf (report, "# memory overrun protection of %d Bytes " + "with magic 0x%4lX\n", + SAVESIZE, MAGIC); +#endif + +#ifdef SAVEVALUE + fprintf (report, "# initializing new memory with 0x%2X\n", + SAVEVALUE); +#endif + +#ifdef MEMCLEAN + fprintf (report, "# sweeping deleted memory with 0x%2X\n", + MEMCLEAN); +#endif + if (getenv("LT_ABORTREASON")) { + abortOn = atoi(getenv("LT_ABORTREASON")); + } + +#define PRINTREASON(x) if (abortOn & x) fprintf(report, "%s ", #x); + fprintf (report, "# aborts on "); + PRINTREASON( OVERWRITE_MEMORY ); + PRINTREASON( DELETE_NONEXISTENT ); + PRINTREASON( NEW_DELETE_MISMATCH ); + fprintf (report, "\n"); +#undef PRINTREASON + +#ifdef THREAD_SAVE + fprintf (report, "# thread save\n"); + /* + * create default, non-recursive ('fast') mutex + * to lock our datastructure where we keep track of + * the user's new/deletes + */ + /*if (pthread_mutex_init(&mutex, NULL) < 0) { + fprintf(report, "# couldn't init mutex ..\n"); + fclose(report); + _exit(1); + }*/ +#else + fprintf(report, "# not thread save; if you use threads, recompile with -DTHREAD_SAVE\n"); +#endif + fflush(report); + } + + /* + * the workhorses: + */ + void *registerAlloc(size_t size, bool type); + void registerFree (void *p, bool type); + + /** + * write a hexdump of the given area. + */ + void hexdump(const unsigned char* area, int size); + + /** + * Terminate current running progam. + */ + void progAbort(abortReason_t reason) { + if (abortOn & reason) { + fprintf(report, "# abort; DUMP of current state\n"); + writeLeakReport(); + fclose(report); + abort(); + } + else + fflush(report); + } + + /** + * write a Report over leaks, e.g. still pending deletes + */ + void writeLeakReport(); + + ~LeakTracer() { + // fprintf(stderr, "LeakTracer::destroy()\n"); + time_t t = time(NULL); + fprintf (report, "# finished %s", ctime(&t)); + writeLeakReport(); + fclose(report); + free(leaks); +#ifdef THREAD_SAVE + //pthread_mutex_destroy(&mutex); +#endif + destroyed = true; + } +} leakTracer; + +void* LeakTracer::registerAlloc (size_t size, bool type) { + initialize(); + + // fprintf(stderr, "LeakTracer::registerAlloc()\n"); + + if (destroyed) { + fprintf(stderr, "Oops, registerAlloc called after destruction of LeakTracer (size=%d)\n", size); + return LT_MALLOC(size); + } + + + void *p = LT_MALLOC(size + SAVESIZE); + // Need to call the new-handler + if (!p) { + fprintf(report, "LeakTracer malloc %m\n"); + _exit (1); + } + +#ifdef SAVEVALUE + /* initialize with some defined pattern */ + memset(p, SAVEVALUE, size + SAVESIZE); +#endif + +#ifdef MAGIC + /* + * the magic value is a special pattern which does not need + * to be uniform. + */ + if (SAVESIZE >= sizeof(magic_t)) { + magic_t *mag; + mag = (magic_t*)((char*)p + size); + *mag = MAGIC; + } +#endif + +#ifdef THREAD_SAVE + //pthread_mutex_lock(&mutex); + mutex.lock(); +#endif + + ++newCount; + ++totalAllocations; + currentAllocated += size; + if (currentAllocated > maxAllocated) + maxAllocated = currentAllocated; + + for (;;) { + for (int i = firstFreeSpot; i < leaksCount; i++) + if (leaks[i].addr == NULL) { + leaks[i].addr = p; + leaks[i].size = size; + leaks[i].type = type; + leaks[i].allocAddr=__builtin_return_address(1); + firstFreeSpot = i+1; + // allow to lookup our index fast. + int *hashPos = &leakHash[ ADDR_HASH(p) ]; + leaks[i].nextBucket = *hashPos; + *hashPos = i; +#ifdef THREAD_SAVE + //pthread_mutex_unlock(&mutex); + mutex.unlock(); +#endif + return p; + } + + // Allocate a bigger array + // Note that leaksCount starts out at 0. + int new_leaksCount = (leaksCount == 0) ? INITIALSIZE + : leaksCount * 2; + leaks = (Leak*)LT_REALLOC(leaks, + sizeof(Leak) * new_leaksCount); + if (!leaks) { + fprintf(report, "# LeakTracer realloc failed: %m\n"); + _exit(1); + } + else { + fprintf(report, "# internal buffer now %d\n", + new_leaksCount); + fflush(report); + } + memset(leaks+leaksCount, 0x00, + sizeof(Leak) * (new_leaksCount-leaksCount)); + leaksCount = new_leaksCount; + } +} + +void LeakTracer::hexdump(const unsigned char* area, int size) { + fprintf(report, "# "); + for (int j=0; j < size ; ++j) { + fprintf (report, "%02x ", *(area+j)); + if (j % 16 == 15) { + fprintf(report, " "); + for (int k=-15; k < 0 ; k++) { + char c = (char) *(area + j + k); + fprintf (report, "%c", isprint(c) ? c : '.'); + } + fprintf(report, "\n# "); + } + } + fprintf(report, "\n"); +} + +void LeakTracer::registerFree (void *p, bool type) { + initialize(); + + if (p == NULL) + return; + + if (destroyed) { + fprintf(stderr, "Oops, allocation destruction of LeakTracer (p=%p)\n", p); + return; + } + +#ifdef THREAD_SAVE + //pthread_mutex_lock(&mutex); + mutex.lock(); +#endif + int *lastPointer = &leakHash[ ADDR_HASH(p) ]; + int i = *lastPointer; + + while (i != 0 && leaks[i].addr != p) { + lastPointer = &leaks[i].nextBucket; + i = *lastPointer; + } + + if (leaks[i].addr == p) { + *lastPointer = leaks[i].nextBucket; // detach. + newCount--; + leaks[i].addr = NULL; + currentAllocated -= leaks[i].size; + if (i < firstFreeSpot) + firstFreeSpot = i; + + if (leaks[i].type != type) { + fprintf(report, + "S %10p %10p # new%s but delete%s " + "; size %d\n", + leaks[i].allocAddr, + __builtin_return_address(1), + ((!type) ? "[]" : " normal"), + ((type) ? "[]" : " normal"), + leaks[i].size); + + progAbort( NEW_DELETE_MISMATCH ); + } +#ifdef MAGIC + if ((SAVESIZE >= sizeof(magic_t)) && + *((magic_t*)((char*)p + leaks[i].size)) != MAGIC) { + fprintf(report, "O %10p %10p " + "# memory overwritten beyond allocated" + " %d bytes\n", + leaks[i].allocAddr, + __builtin_return_address(1), + leaks[i].size); + fprintf(report, "# %d byte beyond area:\n", + SAVESIZE); + hexdump((unsigned char*)p+leaks[i].size, + SAVESIZE); + progAbort( OVERWRITE_MEMORY ); + } +#endif + +#ifdef THREAD_SAVE +# ifdef MEMCLEAN + int allocationSize = leaks[i].size; +# endif + //pthread_mutex_unlock(&mutex); + mutex.unlock(); +#else +#define allocationSize leaks[i].size +#endif + +#ifdef MEMCLEAN + // set it to some garbage value. + memset((unsigned char*)p, MEMCLEAN, allocationSize + SAVESIZE); +#endif + LT_FREE(p); + return; + } + +#ifdef THREAD_SAVE + //pthread_mutex_unlock(&mutex); + mutex.unlock(); +#endif + fprintf(report, "D %10p # delete non alloc or twice pointer %10p\n", + __builtin_return_address(1), p); + progAbort( DELETE_NONEXISTENT ); +} + + +void LeakTracer::writeLeakReport() { + initialize(); + + if (newCount > 0) { + fprintf(report, "# LeakReport\n"); + fprintf(report, "# %10s | %9s # Pointer Addr\n", + "from new @", "size"); + } + for (int i = 0; i < leaksCount; i++) + if (leaks[i].addr != NULL) { + // This ought to be 64-bit safe? + char *memContents = (char *)LT_MALLOC( leaks[i].size + 1 ); + memcpy( (void *)memContents, (void *)( leaks[i].addr ), + leaks[i].size ); + memContents[ leaks[i].size ] = '\0'; + fprintf(report, "L %10p %9ld # %p \"%s\"\n", + leaks[i].allocAddr, + (long) leaks[i].size, + leaks[i].addr, + memContents ); + LT_FREE( memContents ); + } + fprintf(report, "# total allocation requests: %6ld ; max. mem used" + " %d kBytes\n", totalAllocations, maxAllocated / 1024); + fprintf(report, "# leak %6d Bytes\t:-%c\n", currentAllocated, + (currentAllocated == 0) ? ')' : '('); + if (currentAllocated > 50 * 1024) { + fprintf(report, "# .. that is %d kByte!! A lot ..\n", + currentAllocated / 1024); + } +} + +/** -- The actual new/delete operators -- **/ + +void* operator new(size_t size) { + return leakTracer.registerAlloc(size,false); +} + + +void* operator new[] (size_t size) { + return leakTracer.registerAlloc(size,true); +} + + +void operator delete (void *p) { + leakTracer.registerFree(p,false); +} + + +void operator delete[] (void *p) { + leakTracer.registerFree(p,true); +} + + +// added by Jason Rohrer +char *strdup( const char *inString ) { + char *outString = + (char*)leakTracer.registerAlloc( strlen( inString ) + 1, true ); + strcpy( outString, inString ); + return outString; + } + + +/* Emacs: + * Local variables: + * c-basic-offset: 8 + * End: + * vi:set tabstop=8 shiftwidth=8 nowrap: + */ diff --git a/minorGems/util/development/leakTracer/Makefile b/minorGems/util/development/leakTracer/Makefile new file mode 100644 index 0000000..c618ebc --- /dev/null +++ b/minorGems/util/development/leakTracer/Makefile @@ -0,0 +1,64 @@ +# +# Modification History +# +# 2004-January-16 Jason Rohrer +# Switched to use minorGems platform-independed mutexes. +# + + +CC = g++ + +# Source files +SRC := LeakTracer.cc + +ROOT_PATH = ../../../.. + +# Switch comment to select a platform +# PLATFORM_MUTEX = $(ROOT_PATH)/minorGems/system/win32/MutexLockWin32.cpp +PLATFORM_MUTEX = $(ROOT_PATH)/minorGems/system/linux/MutexLockLinux.cpp -pthread + +# Comment both of these out to disable thread safetly +C_THREAD=-DTHREAD_SAVE -D_REENTRANT -D_THREAD_SAFE +O_THREAD = $(PLATFORM_MUTEX) + + +# Common flags +C_FLAGS = -g -pipe -Wall -W -I$(ROOT_PATH) $(C_THREAD) +O_FLAGS = $(C_FLAGS) $(O_THREAD) + +# Object files +OBJ_DIR = . +OBJ := $(patsubst %.cc,$(OBJ_DIR)/%.o,$(SRC)) +SHOBJ := $(patsubst %.o,$(OBJ_DIR)/%.so,$(OBJ)) + +.PHONY: all clean tidy distrib test + +all: $(OBJ) $(SHOBJ) + +clean: tidy + rm -f $(OBJ) leak.out + +tidy: + rm -f *~ *orig *bak *rej + +tags: $(SRC) $(INCL) + ctags $(SRC) $(INCL) + +distrib: clean all README.html + (cd .. && tar cvfz /root/drylock/LeakTracer/LeakTracer.tar.gz --exclude LeakTracer/CVS --exclude LeakTracer/old --exclude LeakTracer/test LeakTracer/) + +$(OBJ_DIR)/%.o: %.cc + $(CC) -fPIC -c $(C_FLAGS) $< -o $@ + +$(OBJ_DIR)/%.so : $(OBJ_DIR)/%.o + $(CC) $(O_FLAGS) -shared -o $@ $< + +README.html: README + /root/ed/mcl/util/htmlize.pl README + +test: + $(CC) $(C_FLAGS) test.cc -o test + ./test + ./LeakCheck ./test + ./leak-analyze ./test +# ./compare-test test.template test.result diff --git a/minorGems/util/development/leakTracer/README b/minorGems/util/development/leakTracer/README new file mode 100644 index 0000000..7bbbc19 --- /dev/null +++ b/minorGems/util/development/leakTracer/README @@ -0,0 +1,230 @@ +Introduction +------------ + +LeakTracer is a small tool I wrote when checking a C++ program for memory +leaks. I couldn't get dmalloc to display what I wanted, and I just saw the +__builtin_return_address gcc-extension mentioned. + +To use LeakTracer, run your program using the provided LeakCheck script. It +uses the LD_PRELOAD feature to "overlay" some functions on top of your +functions (no recompile needed). If your platform does not support LD_PRELOAD, +you can add the LeakTracer.o object file to the objects in your Makefile and +run your application. + +LeakTracer uses gdb to print out the exact line where the memory was allocated +and not freed - this of course means you have to free all dynamically +allocated data. LeakTracer also overrides the global operator new and operator +delete - this will give problems if you override them as well. + +LeakTracer traces only new/new[] and delete calls - it does not look at +malloc/free/realloc. + +Here is some example output: + +Gathered 8 (8 unique) points of data. +(gdb) +Allocations: 1 / Size: 36 +0x80608e6 is in NullArcableInstance::NullArcableInstance(void) (Machine.cc:40). +39 public: +40 NullArcableInstance() : ArcableInstance(new NullArcable) {} + +Allocations: 1 / Size: 8 +0x8055b02 is in init_types(void) (Type.cc:119). +118 void init_types() { +119 Type::Integer = new IntegerType; + +Allocations: 1 / Size: 132 (new[]) +0x805f4ab is in Hashtable::Hashtable(unsigned int) (ea/h/Hashtable.h:15). +14 Hashtable (uint _size = 32) : size(_size), count(0) { +15 table = new List [size]; + +[...] + +Requirements +------------ + +You need Perl5 and gdb installed to run the leak-analyzer. You need gcc -- I +currently use 2.95 but have used it with previous older versions without +problems. + + +You also need to run this on an architecture which supports +__builtin_return_address arguments that are greater than 0 - there may be +some problems on MIPS there. + +So far this code has been tested under Linux 2.2, x86 system, Solaris and +HP-UX. + + +Installation +------------ + +Just type make. There is no install target; you should put LeakTracer +some place you can remember. + +Since version 2.0, it is possible to preload the LeakTracer object on +architectures that support LD_PRELOAD (this is at least Linux and probably +others -- please report success/failure). This means it is much easier to use +the program: you do not need to relink your program with LeakTracer.o. + +In case your platform does not support LD_PRELOAD, you can use LeakTracer in +the old pre 2.0 way: add LeakTracer.o to your object files -- at the very end +of them (also after -llibrary lines). + +In any case your application must also be compiled with debugging enabled +(i.e. -g). + +Running with LeakTracer +----------------------- + +If you are using the shared object, run the LeakCheck script. This script +should stay in the directory where you install LeakCheck -- it will search for +LeakTracer.so file there and load it. E.g.: + +~/src/LeakTracer/LeakCheck yourApplication + +(if you put LeakTracer in ~/src/LeakTracer/) + +Run your application as normal, performing tasks that you want to be traced +for memory leaks. While the application runs, LeakTracer will write data about +memory allocation to the file "leak.out" in the current directory. You can +override the location of that file by setting the LEAKTRACE_FILE environment +variable. + +If you cannot use LD_PRELOAD, just run your application as normal after +relinking it. It will also produce a "leak.out" file when it finishes. + + +Detectable errors +----------------- + +LeakTracer is capable to detect the following problems with your program + + 1) memory which is allocated but not freed + 2) (limited support for) overwritten memory at the end of the allocated + block ( reason = 1 ) + 3) memory which is tried to be deleted but which is not allocated + (either because of a garbage pointer or twice deletion) + (reason = 2) + 4) memory which is allocated with new[] but deleted with simple delete + and vice versa (reason = 4) + +For the last three problems, LeakTracer can abort() your program if you +tell it so; the resulting core-dump allows to debug the problem. By default, +only the overwrite memory condition results in an abort of the program +because it is inherently critical. The two other conditions are not critical. +You can influence what LeakTracer does with the environment variable + LT_ABORTREASON +which you can set to some numeric value which is the result of the +sum of the reasons you find in the parentesis in the enumeration above. +To abort on any reason, for example, you would set LT_ABORTREASON to 7. + + +Analyzing output +---------------- + +You should then run leak-analyze, since looking at the raw leak.out file will +not help you much. To run leak-analyze, you need Perl as well as gdb +installed (any version of gdb will do). For example: + +leak-analyze myprog leak.out + +You don't have to specify the leak.out filename if you just use the default +one. leak-analyze will run gdb on the file, sending it a number of commands +that will show the source lines with the memory leaks. + +leak-analyze should show you something like this: + +Gathered 2 (2 unique) points of data. + +#-- Alloc: Different allocation schemes +alloc here :0x80485b7 is in main (test.cc:6). +5 +6 int *wrong = new int[10]; +..free here :0x80485d9 is in main (test.cc:11). +11 delete wrong; + +#-- Leak: Allocations: 1 / Size: 168 +0x8048593 is in main (test.cc:3). +2 int main() { +3 int *array = new int [42] ; + +#-- Leak: Allocations: 1 / Size: 4 +0x80485a5 is in main (test.cc:4). +3 int *array = new int [42] ; +4 int *foo = new int; + + +This means that total of two allocations happened, in two different places. + +First a delete error is shown: you allocated some memory using new[] but you +freed it using delete. leak-analyze will show where you allocated the memory and where you freed it. + +Afterwards each allocation is shown in turn. There was 1 allocation from this +line of code (test.cc:3), and it was 168 bytes in size. Note that of the two +lines of code shown, it's the bottom one that created the allocation. + +That's all there is to it - now you should find those memory leaks, fix them +and rerun Leak tracer. + +Shared libraries and objects +---------------------------- + +If you want to analyze the leaks in shared libraries in your file, it may be +necessary to make leak-analyze run your program and thus load the shared +libraries before searching for addresses. + +To do that, run leak-analyze with the program name, leak name AND another +argument which is where to set the breakpoint, e.g.: + +leak-analyze myprog leak.out main + +This will make leak-analyze tell gdb to set a breakpoint on "main" and then +run the program. After the analysis is complete, the program will be killed. + +If you want to load some shared libraries, you can set a breakpoint on a +different location, e.g. main.cc:42 if you know that once line 42 is reached, +all shared objects have been loaded. + +If your program needs some command line arguments, supply them after "main". + + +Licensing +--------- + +LeakTracer is public domain (i.e. do with it whatever you feel like). + +Credits +------- + +Initial version of LeakTracer was written by Erwin Andreasen. Henner Zeller +(foobar@to.com) contributed a rewrite of the code which +introduced dynamic loading of LeakTracer and more. + + +Revision history +---------------- + +February 21, 1999 v1.0 - only tested internally +February 23, 1999 v1.1 - added operator new[] / delete[] +February 23, 1999 v1.2 - Oops, forgot to free() the memory.. +February 26, 1999 v1.3 - allow delete 0 +March 27, 1999 v1.4 - Allow %p format without leading 0x for non-GNU + libc. Option to leak-analyze to run the program. +July 21, 1999 v1.5 - Fix for the above suggested by Alan Gonzalez +August 21, 2000 v1.6 - use a destructor instead of + __attribute__(destructor) +November 19, 2000 v2.0 - Rewrite by Henner Zeller introduces LD_PRELOAD + and much more +February 27, 2001 v2.1 - Further update by Henner: optional thread safety, + choose what should make LeakTracer abort(), better + tracing of delete on non-new'ed pointers +March 2, 2001 v2.2 - Another updated by Henner: hash table to increase + performance with many allocations +June 13, 2001 v2.3 - Made LT more resistant to being called before init + and after destruction + +Authors: Erwin Andreasen + Henner Zeller +Homepage: http://www.andreasen.org/LeakTracer/ + diff --git a/minorGems/util/development/leakTracer/README.html b/minorGems/util/development/leakTracer/README.html new file mode 100644 index 0000000..0c37be9 --- /dev/null +++ b/minorGems/util/development/leakTracer/README.html @@ -0,0 +1,210 @@ +

Table of contents

+
+

Introduction

+LeakTracer is a small tool I wrote when checking a C++ program for memory
+leaks. I couldn't get dmalloc to display what I wanted, and I just saw the
+__builtin_return_address gcc-extension mentioned.
+
+To use LeakTracer, run your program using the provided LeakCheck script. It
+uses the LD_PRELOAD feature to "overlay" some functions on top of your
+functions (no recompile needed). If your platform does not support LD_PRELOAD,
+you can add the LeakTracer.o object file to the objects in your Makefile and
+run your application. 
+
+LeakTracer uses gdb to print out the exact line where the memory was allocated
+and not freed - this of course means you have to free all dynamically
+allocated data. LeakTracer also overrides the global operator new and operator
+delete - this will give problems if you override them as well.
+
+LeakTracer traces only new/new[] and delete calls - it does not look at
+malloc/free/realloc.
+
+Here is some example output:
+
+Gathered 8 (8 unique) points of data.
+(gdb)
+Allocations: 1 / Size: 36
+0x80608e6 is in NullArcableInstance::NullArcableInstance(void) (Machine.cc:40).
+39      public:
+40          NullArcableInstance() : ArcableInstance(new NullArcable) {}
+
+Allocations: 1 / Size: 8
+0x8055b02 is in init_types(void) (Type.cc:119).
+118     void init_types() {
+119         Type::Integer = new IntegerType;
+
+Allocations: 1 / Size: 132 (new[])
+0x805f4ab is in Hashtable::Hashtable(unsigned int) (ea/h/Hashtable.h:15).
+14          Hashtable (uint _size = 32) : size(_size), count(0) {
+15              table = new List [size];
+
+[...]
+
+

Requirements

+You need Perl5 and gdb installed to run the leak-analyzer. You need gcc -- I
+currently use 2.95 but have used it with previous older versions without
+problems.
+You also need to run this on an architecture which supports
+__builtin_return_address arguments that are greater than 0 - there may be
+some problems on MIPS there. 
+
+So far this code has been tested under Linux 2.2, x86 system, Solaris and
+HP-UX.
+

Installation

+Just type make. There is no install target; you should put LeakTracer
+some place you can remember.
+
+Since version 2.0, it is possible to preload the LeakTracer object on
+architectures that support LD_PRELOAD (this is at least Linux and probably
+others -- please report success/failure). This means it is much easier to use
+the program: you do not need to relink your program with LeakTracer.o.
+
+In case your platform does not support LD_PRELOAD, you can use LeakTracer in
+the old pre 2.0 way: add LeakTracer.o to your object files -- at the very end
+of them (also after -llibrary lines).
+
+In any case your application must also be compiled with debugging enabled
+(i.e. -g).
+
+

Running with LeakTracer

+If you are using the shared object, run the LeakCheck script. This script
+should stay in the directory where you install LeakCheck -- it will search for
+LeakTracer.so file there and load it. E.g.:
+
+~/src/LeakTracer/LeakCheck yourApplication
+
+(if you put LeakTracer in ~/src/LeakTracer/)
+
+Run your application as normal, performing tasks that you want to be traced
+for memory leaks. While the application runs, LeakTracer will write data about
+memory allocation to the file "leak.out" in the current directory. You can
+override the location of that file by setting the LEAKTRACE_FILE environment
+variable.
+
+If you cannot use LD_PRELOAD, just run your application as normal after
+relinking it. It will also produce a "leak.out" file when it finishes.
+

Detectable errors

+LeakTracer is capable to detect the following problems with your program
+
+  1) memory which is allocated but not freed
+  2) (limited support for) overwritten memory at the end of the allocated
+     block  ( reason = 1 )
+  3) memory which is tried to be deleted but which is not allocated
+     (either because of a garbage pointer or twice deletion)
+     (reason = 2)
+  4) memory which is allocated with new[] but deleted with simple delete
+     and vice versa (reason = 4)
+
+For the last three problems, LeakTracer can abort() your program if you
+tell it so; the resulting core-dump allows to debug the problem. By default,
+only the overwrite memory condition results in an abort of the program
+because it is inherently critical. The two other conditions are not critical.
+You can influence what LeakTracer does with the environment variable
+   LT_ABORTREASON
+which you can set to some numeric value which is the result of the
+sum of the reasons you find in the parentesis in the enumeration above.
+To abort on any reason, for example, you would set LT_ABORTREASON to 7.
+

Analyzing output

+You should then run leak-analyze, since looking at the raw leak.out file will
+not help you much. To run leak-analyze, you need Perl as well as gdb
+installed (any version of gdb will do). For example:
+
+leak-analyze myprog leak.out
+
+You don't have to specify the leak.out filename if you just use the default
+one. leak-analyze will run gdb on the file, sending it a number of commands
+that will show the source lines with the memory leaks.
+
+leak-analyze should show you something like this:
+
+Gathered 2 (2 unique) points of data.
+
+#-- Alloc: Different allocation schemes
+alloc here :0x80485b7 is in main (test.cc:6).
+5
+6               int *wrong = new int[10];
+..free here :0x80485d9 is in main (test.cc:11).
+11              delete wrong;
+
+#-- Leak: Allocations: 1 / Size: 168 
+0x8048593 is in main (test.cc:3).
+2       int main() {
+3               int *array = new int [42] ;
+
+#-- Leak: Allocations: 1 / Size: 4 
+0x80485a5 is in main (test.cc:4).
+3               int *array = new int [42] ;
+4               int *foo = new int;
+This means that total of two allocations happened, in two different places.
+
+First a delete error is shown: you allocated some memory using new[] but you
+freed it using delete. leak-analyze will show where you allocated the memory and where you freed it.
+
+Afterwards each allocation is shown in turn. There was 1 allocation from this
+line of code (test.cc:3), and it was 168 bytes in size. Note that of the two
+lines of code shown, it's the bottom one that created the allocation.
+
+That's all there is to it - now you should find those memory leaks, fix them
+and rerun Leak tracer.
+
+

Shared libraries and objects

+If you want to analyze the leaks in shared libraries in your file, it may be
+necessary to make leak-analyze run your program and thus load the shared
+libraries before searching for addresses.
+
+To do that, run leak-analyze with the program name, leak name AND another
+argument which is where to set the breakpoint, e.g.:
+
+leak-analyze myprog leak.out main
+
+This will make leak-analyze tell gdb to set a breakpoint on "main" and then
+run the program. After the analysis is complete, the program will be killed.
+
+If you want to load some shared libraries, you can set a breakpoint on a
+different location, e.g. main.cc:42 if you know that once line 42 is reached,
+all shared objects have been loaded.
+
+If your program needs some command line arguments, supply them after "main".
+

Licensing

+LeakTracer is public domain (i.e. do with it whatever you feel like).
+
+

Credits

+Initial version of LeakTracer was written by Erwin Andreasen. Henner Zeller
+(foobar@to.com) contributed a rewrite of the code which
+introduced dynamic loading of LeakTracer and more.
+

Revision history

+February 21, 1999       v1.0 - only tested internally
+February 23, 1999       v1.1 - added operator new[] / delete[]
+February 23, 1999           v1.2 - Oops, forgot to free() the memory..
+February 26, 1999       v1.3 - allow delete 0
+March 27, 1999          v1.4 - Allow %p format without leading 0x for non-GNU 
+                                       libc. Option to leak-analyze to run the program.
+July 21, 1999               v1.5 - Fix for the above suggested by Alan Gonzalez
+August 21, 2000         v1.6 - use a destructor instead of 
+                                       __attribute__(destructor)
+November 19, 2000               v2.0 - Rewrite by Henner Zeller introduces LD_PRELOAD
+                                       and much more
+February 27, 2001               v2.1 - Further update by Henner: optional thread safety,
+                                       choose what should make LeakTracer abort(), better
+                                       tracing of delete on non-new'ed pointers
+March 2, 2001                   v2.2 - Another updated by Henner: hash table to increase
+                                       performance with many allocations
+June 13, 2001                   v2.3 - Made LT more resistant to being called before init
+                                       and after destruction
+
+Authors:    Erwin Andreasen 
+        Henner Zeller 
+Homepage:   http://www.andreasen.org/LeakTracer/
+
+
diff --git a/minorGems/util/development/leakTracer/VERSION b/minorGems/util/development/leakTracer/VERSION new file mode 100644 index 0000000..bb576db --- /dev/null +++ b/minorGems/util/development/leakTracer/VERSION @@ -0,0 +1 @@ +2.3 diff --git a/minorGems/util/development/leakTracer/leak-analyze b/minorGems/util/development/leakTracer/leak-analyze new file mode 100755 index 0000000..0c90742 --- /dev/null +++ b/minorGems/util/development/leakTracer/leak-analyze @@ -0,0 +1,116 @@ +#!/usr/bin/perl + +# +# Modification History +# +# 2004-January-17 Jason Rohrer +# Fixed regexps to match both A-F and a-f for hex address strings. +# + + +# Erwin S. Andreasen +# Henner Zeller +# +# Homepage: http://www.andreasen.org/LeakTracer/ +# This program is Public Domain +use IO::Handle; + +die "You must supply at least one argument.\n" unless $#ARGV >= 0; + +$ExeFile = shift @ARGV; +$LeaksFile = $#ARGV >= 0 ? shift @ARGV : "leak.out"; +open (LEAKS, $LeaksFile) or die "Could not open leaks data file $LeaksFile: $!"; + +if ($#ARGV >= 0) { + $BreakOn = shift @ARGV; + # Rest in @ARGV are program arguments +} + +$n = $u = 0; +while () { + chop; + next if (m/^\s*#/); + # 1 2 3 4 5 6 7 + #if (/^\s*L\s+(0x)?([0-9a-fA-F]+)\s+(0x)?([0-9a-fA-F]+)\s+(0x)?([0-9a-fA-F]+)\s+(\d+)/) { + # Allocations, which have not been freed or deallocations which have not + # been allocated. + # 1 2 3 + if (/^\s*L\s+(0x)?([0-9a-fA-F]+)\s+(\d+)/) { + $addr="$2"; # ",$4,$6"; + $u++ if not exists $Type{$addr}; + $Count{$addr}++; + $Size{$addr} += $3; # $7; + $Type{$addr} = "Leak"; + $n++; + } + elsif (/^\s*D\s+(0x)?([0-9a-fA-F]+)/) { + $addr="$2"; # ",$4,$6"; + $u++ if not exists $Type{$addr}; + $Count{$addr}++; + $Type{$addr} = "delete on not allocated memory"; + $n++; + } + # allocations/deallocations with other errornous conditions + # 1 2 3 4 5 + elsif (/^\s*([SO])\s+(0x)?([0-9a-fA-F]+)\s+(0x)?([0-9a-fA-F]+)/) { + $addrs = "$3,$5,$1"; + $AllocDealloc{$addrs} = ("$1" =~ m/S/) + ? "Different allocation schemes" + : "This Memory was overwritten"; + } +} + +print STDERR "Gathered $n ($u unique) points of data.\n"; + +close (LEAKS); + + +# Instead of using -batch, we just run things as usual. with -batch, +# we quit on the first error, which we don't want. +open (PIPE, "|gdb -q $ExeFile") or die "Cannot start gdb"; +#open (PIPE, "|cat"); + +# Change set listsize 2 to something else to show more lines +print PIPE "set prompt\nset complaints 1000\nset height 0\n"; + +# Optionally, run the program +if (defined($BreakOn)) { + print PIPE "break $BreakOn\n"; + print PIPE "run ", join(" ", @ARGV), " \n"; +} + + +print PIPE "set listsize 2\n"; +foreach (sort keys %AllocDealloc) { + print PIPE "echo \\n#-- Alloc: $AllocDealloc{$_}\\nalloc here :\n"; + @addrs = split(/,/,$_); + print PIPE "l *0x" . (shift @addrs) . "\necho ..free here :\n"; + print PIPE "set listsize 1\n"; + print PIPE "l *0x" . (shift @addrs) . "\n"; +} + +foreach (sort keys %Type) { + print PIPE "echo \\n#-- $Type{$_}: counted $Count{$_}x"; + if ($Size{$_} > 0) { + print PIPE " / total Size: $Size{$_}"; + } + print PIPE "\\n\n"; + @addrs = split(/,/,$_); + print PIPE "set listsize 2\n"; + print PIPE "l *0x" . (shift @addrs) . "\n"; + #print PIPE "echo ..called from :\n"; + #print PIPE "set listsize 1\n"; + # gdb bails out, if it cannot find an address. + #print PIPE "l *0x" . (shift @addrs) . "\necho ..called from :\n"; + #print PIPE "l *0x" . (shift @addrs) . "\n"; +} + +if (defined($BreakOn)) { + print PIPE "kill\n"; +} + +print PIPE "quit\n"; +PIPE->flush(); +wait(); + +close (PIPE); diff --git a/minorGems/util/development/leakTracer/test.cc b/minorGems/util/development/leakTracer/test.cc new file mode 100644 index 0000000..6d64892 --- /dev/null +++ b/minorGems/util/development/leakTracer/test.cc @@ -0,0 +1,27 @@ +/* + * Modification History + * + * 2002-March-31 Jason Rohrer + * Added test of strdup support. + */ + + +#include + +// Small leaky test program + +void foo() { + int *x = new int; +} + +int main() { + char *str = strdup( "Test String" ); + + int *z = new int[10]; + foo(); + foo(); + delete z; + delete z; // delete value twice + + +} diff --git a/minorGems/util/development/memory/MemoryTrack.cpp b/minorGems/util/development/memory/MemoryTrack.cpp new file mode 100644 index 0000000..e390cee --- /dev/null +++ b/minorGems/util/development/memory/MemoryTrack.cpp @@ -0,0 +1,277 @@ +/* + * Modification History + * + * 2002-October-17 Jason Rohrer + * Created. + * + * 2002-October-18 Jason Rohrer + * Changed to use custom list instead of SimpleVector because SimpleVector + * uses debugMemory. + * Added static initialization counting class. + * Changed to use struct and malloc for AllocationList to avoid + * circular new and delete calls. + * + * 2002-October-19 Jason Rohrer + * Fixed a bug in adding to the alloc list. + * Improved printing behavior. + * Added support for clearing memory on allocation and deallocation. + * Fixed to ignore deallocation of our own static lock. + * Fixed bug in allocation count. + * Added message for NULL pointer deallocation. + * Put locks in place around print statements, which are not atomic on win32. + * Changed to use hex notation when printing pointers. + * + * 2002-October-19 Jason Rohrer + * Added ifdef for DEBUG_MEMORY. + * + * 2002-October-20 Jason Rohrer + * Removed file and line arguments from deallocation calls. + */ + + + +#ifdef DEBUG_MEMORY + + +#include "minorGems/util/development/memory/MemoryTrack.h" + +#include +#include +#include + + + +int MemoryTrackStaticInitCounter::mCount = 0; + +MutexLock *MemoryTrack::mLock; + +AllocationList *MemoryTrack::mListHead; + +char MemoryTrack::mTracking = false; + +int MemoryTrack::mTotalAllocationSize = 0; +int MemoryTrack::mTotalDeallocationSize = 0; +int MemoryTrack::mNumberOfAllocations = 0; + + + +void MemoryTrack::addAllocation( void *inPointer, + unsigned int inAllocationSize, + int inAllocationType, + const char *inFileName, + int inLineNumber ) { + + mLock->lock(); + + if( !mTracking ) { + printf( "Tracking off on allocation (0x%x) [%d bytes] %s:%d.\n", + (unsigned int)inPointer, + inAllocationSize, + inFileName, inLineNumber ); + + mLock->unlock(); + return; + } + + + // insert after head of list + AllocationList *element = + (AllocationList *)malloc( sizeof( AllocationList ) ); + element->mPrevious = (void *)mListHead; + element->mNext = mListHead->mNext; + + mListHead->mNext = (void *)element; + + + AllocationList *nextElement = (AllocationList *)( element->mNext ); + if( nextElement != NULL ) { + nextElement->mPrevious = (void *)element; + } + + element->mPointer = inPointer; + element->mAllocationSize = inAllocationSize; + element->mAllocationType = inAllocationType; + element->mFileName = inFileName; + element->mLineNumber = inLineNumber; + + + mTotalAllocationSize += inAllocationSize; + + mNumberOfAllocations ++; + + // wipe this block of memory + clearMemory( inPointer, inAllocationSize ); + + mLock->unlock(); + } + + + +int MemoryTrack::addDeallocation( void *inPointer, + int inDeallocationType ) { + + mLock->lock(); + + if( inPointer == NULL ) { + printf( "NULL pointer (0x%x) deallocated\n", + (unsigned int)inPointer ); + } + + + if( inPointer == (void *)mLock ) { + // we're seeing the deallocation of our own static lock as + // the system exits + // ignore it + mLock->unlock(); + return 0; + } + + if( !mTracking ) { + printf( "Tracking off on deallocation (0x%x)\n", + (unsigned int)inPointer ); + mLock->unlock(); + return 0; + } + + + + AllocationList *element = (AllocationList *)( mListHead->mNext ); + + while( element != NULL ) { + + void *pointer = element->mPointer; + + if( pointer == inPointer ) { + + unsigned int allocationSize = element->mAllocationSize; + int allocationType = element->mAllocationType; + const char *allocFileName = element->mFileName; + int allocLineNumber = element->mLineNumber; + + // remove from list, whether or not types match + AllocationList *previousElement = + (AllocationList *)( element->mPrevious ); + AllocationList *nextElement = + (AllocationList *)( element->mNext ); + + // patch list + previousElement->mNext = (void *)( nextElement ); + + if( nextElement != NULL ) { + nextElement->mPrevious = (void *)( previousElement ); + } + + free( element ); + + mTotalDeallocationSize += allocationSize; + + + if( allocationType == inDeallocationType ) { + // found and types match + mLock->unlock(); + + // wipe this block of memory + clearMemory( inPointer, allocationSize ); + return 0; + } + else { + // allocation types don't match + + printf( "Attempt to deallocate (0x%x) [%d bytes] with wrong" + " delete form\n" + " %s:%d (location of original allocation)\n", + (unsigned int)inPointer, + allocationSize, + allocFileName, allocLineNumber ); + + mLock->unlock(); + + return 2; + } + + } + + element = (AllocationList *)( element->mNext ); + } + + // not found (delete of unallocated memory) + printf( "Attempt to deallocate (0x%x) unallocated memory\n", + (unsigned int)inPointer ); + + mLock->unlock(); + + return 1; + } + + + +void MemoryTrack::printLeaks() { + mLock->lock(); + + printf( "\n\n---- debugMemory report ----\n" ); + + printf( "Number of Allocations: %d\n", mNumberOfAllocations ); + printf( "Total allocations: %d bytes\n", mTotalAllocationSize ); + printf( "Total deallocations: %d bytes\n", mTotalDeallocationSize ); + + int leakEstimate = mTotalAllocationSize - mTotalDeallocationSize; + + + + AllocationList *element = (AllocationList *)( mListHead->mNext ); + + if( element == NULL ) { + printf( "No leaks detected.\n" ); + } + else { + printf( "Leaks detected:\n" ); + } + + int leakSum = 0; + while( element != NULL ) { + + printf( "Not deallocated (0x%x) [%d bytes]\n" + " %s:%d (location of original allocation)\n", + (unsigned int)( element->mPointer ), + element->mAllocationSize, + element->mFileName, + element->mLineNumber ); + + leakSum += element->mAllocationSize; + + element = (AllocationList *)( element->mNext ); + } + + + if( leakSum != leakEstimate ) { + printf( "Warning: Leak sum does not equal leak estimate.\n" ); + } + + + printf( "Leaked memory: %d bytes\n", leakSum ); + + printf( "---- END debugMemory report ----\n\n" ); + + + mLock->unlock(); + } + + + +void MemoryTrack::clearMemory( void *inPointer, unsigned int inSize ) { + + unsigned char *charArray = (unsigned char *)inPointer; + + for( unsigned int i=0; i +#include + + +#define SINGLE_ALLOCATION 0 +#define ARRAY_ALLOCATION 1 + + + +/** + * Linked list of memory allocations. + * + * @author Jason Rohrer + */ +typedef struct { + + void *mPrevious; + void *mNext; + + void *mPointer; + unsigned int mAllocationSize; + int mAllocationType; + const char *mFileName; + int mLineNumber; + + } AllocationList; + + + +/** + * Class that tracks memory allocations and deallocations. + * + * @author Jason Rohrer + */ +class MemoryTrack { + + + + public: + + + + /** + * Adds an allocation to this tracker and clears the allocated + * memory block. + * + * @param inPointer a pointer to the allocated memory. + * @param inAllocationType the type of allocation, + * either SINGLE_ALLOCATION or ARRAY_ALLOCATION. + * @param inAllocationSize the size of the allocation in bytes. + * @param inFileName the name of the source file in which the + * allocation took place. + * @param inLineNumber the line number in the source file + * on which the allocation took place. + */ + static void addAllocation( void *inPointer, + unsigned int inAllocationSize, + int inAllocationType, + const char *inFileName, + int inLineNumber ); + + + + /** + * Adds a deallocation to this tracker and clears the block + * to be deallocated. + * Must be called *before* the memory is deallocated + * + * @param inPointer a pointer to the memory being deallocated. + * @param inDeallocationType the type of deallocation, + * either SINGLE_ALLOCATION or ARRAY_ALLOCATION. + * @return 0 if the deallocation deallocates + * an allocated block of memory, or 1 if it + * deallocates a block of memory that is not currently allocated, + * and 2 if it is the wrong deallocation type for the specified + * block. + */ + static int addDeallocation( void *inPointer, int inDeallocationType ); + + + + + /** + * Prints a list of all memory leaks (allocations that have never + * been deallocated). + */ + static void printLeaks(); + + + + // these are public so initializer can get to them + + static MutexLock *mLock; + + // dummy place holder for list head + static AllocationList *mListHead; + + // true if we're tracking + static char mTracking; + + static int mTotalAllocationSize; + + static int mTotalDeallocationSize; + + static int mNumberOfAllocations; + + protected: + + + + /** + * Clears memory so that reading from it will not produce + * anything useful. Good for checking for reads to memory that + * has been deallocated. + * + * @param inPointer pointer to the memory to clear. + * @Param inSize the number of bytes to clear starting at inPointer. + */ + static void clearMemory( void *inPointer, unsigned int inSize ); + + + + }; + + + +/** + * Class that initializes MemoryTrack's static members. + * + * *All* files that use MemoryTrack will instantiate a static + * instance of this class (see static instance below). + * + * This class counts how many static instantiations have happened so + * far, making sure to init/destroy MemoryTrack's static members only once. + * + * Adapted from: + * http://www.hlrs.de/organization/par/services/tools/docu/kcc/ + * tutorials/static_initialization.html + */ +class MemoryTrackStaticInitCounter { + + + public: + + + + MemoryTrackStaticInitCounter() { + if( mCount == 0 ) { + // allocate static members + MemoryTrack::mLock = new MutexLock(); + + MemoryTrack::mListHead = + (AllocationList *) + malloc( sizeof( AllocationList ) ); + MemoryTrack::mListHead->mPrevious = NULL; + MemoryTrack::mListHead->mNext = NULL; + + MemoryTrack::mTotalAllocationSize = 0; + MemoryTrack::mTotalDeallocationSize = 0; + MemoryTrack::mNumberOfAllocations = 0; + + MemoryTrack::mTracking = true; + } + mCount++; + } + + + + ~MemoryTrackStaticInitCounter() { + mCount--; + if( mCount == 0 ) { + // print leaks... we should only get here after + // all static members of classes that use MemoryTrack + // have been destroyed. + MemoryTrack::printLeaks(); + + MemoryTrack::mTracking = false; + // deallocate static members + free( MemoryTrack::mListHead ); + delete MemoryTrack::mLock; + } + } + + + private: + // only allocate/deallocate when mCount == 0 + static int mCount; + + }; + + + +// This will be included in *every* file that includes MemoryTrack.h +static MemoryTrackStaticInitCounter memoryTrackInitializer; + + + +#endif + + diff --git a/minorGems/util/development/memory/compileTestDebugMemory b/minorGems/util/development/memory/compileTestDebugMemory new file mode 100755 index 0000000..9e2f7dd --- /dev/null +++ b/minorGems/util/development/memory/compileTestDebugMemory @@ -0,0 +1 @@ +g++ -Wall -g -DDEBUG_MEMORY -o testDebugMemory -I../../../.. debugMemory.cpp MemoryTrack.cpp testDebugMemory.cpp ../../../../minorGems/system/linux/MutexLockLinux.cpp diff --git a/minorGems/util/development/memory/debugMemory.cpp b/minorGems/util/development/memory/debugMemory.cpp new file mode 100644 index 0000000..6db7b9d --- /dev/null +++ b/minorGems/util/development/memory/debugMemory.cpp @@ -0,0 +1,95 @@ +/* + * Modification History + * + * 2002-October-17 Jason Rohrer + * Created. + * + * 2002-October-18 Jason Rohrer + * Added static initialization counting class for MemoryTrack. + * + * 2002-October-19 Jason Rohrer + * Added more detail to error message. + * Improved printing behavior. + * Moved include of debugMemory.h to work better with IDE compilers. + * Fixed to deal with differences between malloc and new[] on some platforms. + * + * 2002-October-20 Jason Rohrer + * Removed delete macro trick that was causing crashes in tinyxml. + * Removed function that was no longer being used. + */ + + + +#include "minorGems/util/development/memory/debugMemory.h" + + +#ifdef DEBUG_MEMORY + +#include "minorGems/util/development/memory/MemoryTrack.h" + +#include "stdlib.h" +#include "stdio.h" + + + +void *debugMemoryNew( unsigned int inSize, + const char *inFileName, int inLine ) { + + + void *allocatedPointer = (void *)malloc( inSize ); + + MemoryTrack::addAllocation( allocatedPointer, inSize, + SINGLE_ALLOCATION, + inFileName, inLine ); + + return allocatedPointer; + } + + + +void *debugMemoryNewArray( unsigned int inSize, + const char *inFileName, int inLine ) { + + unsigned int mallocSize = inSize; + if( inSize == 0 ) { + // always allocate at least one byte to circumvent differences + // between malloc and new[] on some platforms + // (new int[0] returns a pointer to an array of length 0, while + // malloc( 0 ) can return NULL on some platforms) + mallocSize = 1; + } + + void *allocatedPointer = (void *)malloc( mallocSize ); + + MemoryTrack::addAllocation( allocatedPointer, inSize, + ARRAY_ALLOCATION, + inFileName, inLine ); + + return allocatedPointer; + } + + + +void debugMemoryDelete( void *inPointer ) { + MemoryTrack::addDeallocation( inPointer, SINGLE_ALLOCATION ); + free( inPointer ); + } + + + +void debugMemoryDeleteArray( void *inPointer ) { + MemoryTrack::addDeallocation( inPointer, ARRAY_ALLOCATION ); + free( inPointer ); + } + + + +#endif + + + + + + + + diff --git a/minorGems/util/development/memory/debugMemory.h b/minorGems/util/development/memory/debugMemory.h new file mode 100644 index 0000000..41c6d77 --- /dev/null +++ b/minorGems/util/development/memory/debugMemory.h @@ -0,0 +1,127 @@ +/* + * Modification History + * + * 2002-October-17 Jason Rohrer + * Created. + * + * 2002-October-18 Jason Rohrer + * Added static initialization counting class for MemoryTrack. + * + * 2002-October-20 Jason Rohrer + * Removed delete macro trick that was causing crashes in tinyxml. + */ + + +#ifndef DEBUG_MEMORY_INCLUDED +#define DEBUG_MEMORY_INCLUDED + + + + +#ifdef DEBUG_MEMORY + +#include "minorGems/util/development/memory/MemoryTrack.h" + + + +// internal function prototypes +void *debugMemoryNew( unsigned int inSize, + const char *inFileName, int inLine ); + +void *debugMemoryNewArray( unsigned int inSize, + const char *inFileName, int inLine ); + +void debugMemoryDelete( void *inPointer ); + +void debugMemoryDeleteArray( void *inPointer ); + + + +// overrided primitive operators... must be inline? + + + +/** + * Overrides the new operator to track memory allocations. + * + * @param inSize the size of the allocation. + * @param inFileName the name of the source file where the allocation + * occurred. + * @param inLine the line in the source file where the allocation occurred. + */ +inline void *operator new( unsigned int inSize, + const char *inFileName, int inLine ) { + return debugMemoryNew( inSize, inFileName, inLine ); + } + + + +/** + * Overrides the new [] operator to track memory allocations. + * + * @param inSize the size of the allocation. + * @param inFileName the name of the source file where the allocation + * occurred. + * @param inLine the line in the source file where the allocation occurred. + */ +inline void * operator new [] ( unsigned int inSize, + const char *inFileName, int inLine ) { + + return debugMemoryNewArray( inSize, inFileName, inLine ); + } + + + + +/** + * Overrides the delete operator to track memory allocations. + * + * @param inPointer a pointer to the memory to deallocate. + */ +inline void operator delete( void *inPointer ) { + debugMemoryDelete( inPointer ); + } + + + +/** + * Overrides the delete [] operator to track memory allocations. + * + * @param inPointer a pointer to the memory to deallocate. + */ +inline void operator delete [] ( void *inPointer ) { + debugMemoryDeleteArray( inPointer ); + } + + + +#endif + + + + + +// macro trickery to pass file name and line number into new + +#ifdef DEBUG_MEMORY +#define DEBUG_NEW new( __FILE__, __LINE__ ) +#else +#define DEBUG_NEW new +#endif + + + +#define new DEBUG_NEW + + + +#endif + + + + + + + + + diff --git a/minorGems/util/development/memory/testDebugMemory.cpp b/minorGems/util/development/memory/testDebugMemory.cpp new file mode 100644 index 0000000..53738d5 --- /dev/null +++ b/minorGems/util/development/memory/testDebugMemory.cpp @@ -0,0 +1,85 @@ +/* + * Modification History + * + * 2002-October-17 Jason Rohrer + * Created. + * + * 2002-October-18 Jason Rohrer + * Added static initialization counting class for MemoryTrack. + * + * 2002-October-19 Jason Rohrer + * Removed call to debugMemoryPrintLeaksAndStopTracking. + * Made test cases more interesting. + * Added test cases for deleting NULL pointers. + */ + + +#include "minorGems/util/development/memory/debugMemory.h" + + +#include + + +class TestClass { + public: + TestClass() { + mX = new int[5]; + } + + ~TestClass() { + delete [] mX; + } + + int *mX; + }; + + + +int main() { + + int *x = new int[5]; + + printf( "array contents before initializing elements:\n" + "%d, %d, %d, %d, %d\n\n", + x[0], x[1], x[2], x[3], x[4] ); + + x[0] = 1; + x[1] = 2; + x[2] = 3; + x[3] = 4; + x[4] = 5; + + printf( "array contents before deleting:\n" + "%d, %d, %d, %d, %d\n\n", + x[0], x[1], x[2], x[3], x[4] ); + + delete [] x; + + printf( "array contents after deleting:\n" + "%d, %d, %d, %d, %d\n\n", + x[0], x[1], x[2], x[3], x[4] ); + + + int *y = new int[4]; + y[0] = 1; + + TestClass *t = new TestClass(); + + delete t; + + int *z = new int[7]; + delete z; + + //delete t; + + int *badPointer = NULL; + delete badPointer; + + int *badPointer2 = NULL; + delete [] badPointer2; + + return 0; + } + + + diff --git a/minorGems/util/log/AppLog.cpp b/minorGems/util/log/AppLog.cpp new file mode 100644 index 0000000..e76f33d --- /dev/null +++ b/minorGems/util/log/AppLog.cpp @@ -0,0 +1,156 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-March-30 Jason Rohrer + * Wrapped our dynamically allocated static member in a statically + * allocated class to avoid memory leaks at program termination. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + + +#include "AppLog.h" +#include "Log.h" + +#include + + + +// wrap our static member in a statically allocated class +LogPointerWrapper AppLog::mLogPointerWrapper( new PrintLog ); + + + +LogPointerWrapper::LogPointerWrapper( Log *inLog ) + : mLog( inLog ) { + + } + + + +LogPointerWrapper::~LogPointerWrapper() { + if( mLog != NULL ) { + delete mLog; + } + } + + + +void AppLog::criticalError( const char *inString ) { + mLogPointerWrapper.mLog->logString( + inString, Log::CRITICAL_ERROR_LEVEL ); + } + + + +void AppLog::criticalError( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::CRITICAL_ERROR_LEVEL ); + } + + + +void AppLog::error( const char *inString ) { + mLogPointerWrapper.mLog->logString( inString, Log::ERROR_LEVEL ); + } + + + +void AppLog::error( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::ERROR_LEVEL ); + } + + + +void AppLog::warning( const char *inString ) { + mLogPointerWrapper.mLog->logString( inString, Log::WARNING_LEVEL ); + } + + + +void AppLog::warning( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::WARNING_LEVEL ); + } + + + +void AppLog::info( const char *inString ) { + mLogPointerWrapper.mLog->logString( inString, Log::INFO_LEVEL ); + } + + + +void AppLog::info( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::INFO_LEVEL ); + } + + + +void AppLog::detail( const char *inString ) { + mLogPointerWrapper.mLog->logString( inString, Log::DETAIL_LEVEL ); + } + + + +void AppLog::detail( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::DETAIL_LEVEL ); + } + + + +void AppLog::trace( const char *inString ) { + mLogPointerWrapper.mLog->logString( inString, Log::TRACE_LEVEL ); + } + + + +void AppLog::trace( const char *inLoggerName, const char *inString ) { + mLogPointerWrapper.mLog->logString( + inLoggerName, inString, Log::TRACE_LEVEL ); + } + + + +void AppLog::setLog( Log *inLog ) { + int currentLoggingLevel = getLoggingLevel(); + + if( inLog != mLogPointerWrapper.mLog ) { + delete mLogPointerWrapper.mLog; + } + mLogPointerWrapper.mLog = inLog; + + setLoggingLevel( currentLoggingLevel ); + } + + + +Log *AppLog::getLog() { + return mLogPointerWrapper.mLog; + } + + + +void AppLog::setLoggingLevel( int inLevel ) { + mLogPointerWrapper.mLog->setLoggingLevel( inLevel ); + } + + + +int AppLog::getLoggingLevel() { + return mLogPointerWrapper.mLog->getLoggingLevel(); + } + + + + + + diff --git a/minorGems/util/log/AppLog.h b/minorGems/util/log/AppLog.h new file mode 100644 index 0000000..c16e273 --- /dev/null +++ b/minorGems/util/log/AppLog.h @@ -0,0 +1,167 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-March-30 Jason Rohrer + * Wrapped our dynamically allocated static member in a statically + * allocated class to avoid memory leaks at program termination. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef APP_LOG_INCLUDED +#define APP_LOG_INCLUDED + + +#include "Log.h" +#include "PrintLog.h" + + + +// internally used class +class LogPointerWrapper; + + + +/** + * A singleton wrapper for implementations of the Log interface. + * + * This class should be the interface for log-access for applications. + * + * @author Jason Rohrer + */ +class AppLog { + + + + public: + + + /** + * These log errors at various levels. + * + * All char* parameters must be \0-terminated and destroyed by caller. + */ + + static void criticalError( const char *inString ); + static void criticalError( const char *inLoggerName, + const char *inString ); + + static void error( const char *inString ); + static void error( const char *inLoggerName, const char *inString ); + + static void warning( const char *inString ); + static void warning( const char *inLoggerName, const char *inString ); + + static void info( const char *inString ); + static void info( const char *inLoggerName, const char *inString ); + + static void detail( const char *inString ); + static void detail( const char *inLoggerName, const char *inString ); + + static void trace( const char *inString ); + static void trace( const char *inLoggerName, const char *inString ); + + + + /** + * Sets the log to use. + * Note that this call destroys the current log. + * + * @param inLog the log to use. + * Will be destroyed by this class. + */ + static void setLog( Log *inLog ); + + + + /** + * Gets the log being used. + * + * @return the log being used. + * Will be destroyed by this class. + */ + static Log *getLog(); + + + + /** + * Sets the logging level of the current log. + * + * Messages with levels above the current level will not be logged. + * + * @param inLevel one of the defined logging levels. + */ + static void setLoggingLevel( int inLevel ); + + + + /** + * Gets the logging level of the current log. + * + * Messages with levels above the current level will not be logged. + * + * @return one of the defined logging levels. + */ + static int getLoggingLevel(); + + + + protected: + + // note that all static objects + // are destroyed at program termination + //static Log *mLog; + static LogPointerWrapper mLogPointerWrapper; + + }; + + + +/** + * Wrapper for static member pointer to ensure + * deletion of static member object at program termination. + */ +class LogPointerWrapper { + + + + public: + + + + /** + * Constructor allows specification of the object + * to wrap and destroy at program termination. + * + * @param inLog the log object to wrap. + * Will be destroyed at program termination if non-NULL. + */ + LogPointerWrapper( Log *inLog ); + + + + /** + * Destructor will get called at program termination + * if the object is statically allocated. + */ + ~LogPointerWrapper(); + + + + Log *mLog; + + + + }; + + + +#endif diff --git a/minorGems/util/log/FileLog.cpp b/minorGems/util/log/FileLog.cpp new file mode 100644 index 0000000..2ea02cb --- /dev/null +++ b/minorGems/util/log/FileLog.cpp @@ -0,0 +1,146 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-March-13 Jason Rohrer + * Added a flush after every write. + * + * 2002-April-8 Jason Rohrer + * Changed to be thread-safe. + * + * 2002-November-5 Jason Rohrer + * Added support for backing up logs and deleting old log data. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + +#include "FileLog.h" + +#include "PrintLog.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/io/file/File.h" + + +#include +#include + + + +const char *FileLog::mDefaultLogFileName = "default.log"; + + + +FileLog::FileLog( const char *inFileName, unsigned long inSecondsBetweenBackups ) + : mLogFile( NULL ), + mLogFileName( stringDuplicate( inFileName ) ), + mSecondsBetweenBackups( inSecondsBetweenBackups ), + mTimeOfLastBackup( time( NULL ) ) { + + + mLogFile = fopen( mLogFileName, "a" ); + + if( mLogFile == NULL ) { + printf( "Log file %s failed to open.\n", mLogFileName ); + printf( "Writing log to default file: %s\n", + mDefaultLogFileName ); + + // switch to default log file name + + delete [] mLogFileName; + mLogFileName = stringDuplicate( mDefaultLogFileName ); + + + mLogFile = fopen( mLogFileName, "a" ); + + if( mLogFile == NULL ) { + printf( "Default log file %s failed to open.\n", + mLogFileName ); + } + } + + } + + + +FileLog::~FileLog() { + if( mLogFile != NULL ) { + fclose( mLogFile ); + } + delete [] mLogFileName; + } + + + +void FileLog::logString( const char *inLoggerName, const char *inString, + int inLevel ) { + + if( mLogFile != NULL ) { + if( inLevel <= mLoggingLevel ) { + + + char *message = PrintLog::generateLogMessage( inLoggerName, + inString, + inLevel ); + + mLock->lock(); + fprintf( mLogFile, "%s\n", message ); + + fflush( mLogFile ); + + if( time( NULL ) - mTimeOfLastBackup > mSecondsBetweenBackups ) { + makeBackup(); + } + + mLock->unlock(); + + delete [] message; + } + } + } + + + +void FileLog::makeBackup() { + fclose( mLogFile ); + + File *currentLogFile = new File( NULL, mLogFileName ); + + char *backupFileName = new char[ strlen( mLogFileName ) + 10 ]; + sprintf( backupFileName, "%s.backup", mLogFileName ); + + + File *backupLogFile = new File( NULL, backupFileName ); + delete [] backupFileName; + + + // copy into backup log file, which will overwrite it + currentLogFile->copy( backupLogFile ); + + + delete currentLogFile; + delete backupLogFile; + + + // clear main log file and start writing to it again + mLogFile = fopen( mLogFileName, "w" ); + + if( mLogFile == NULL ) { + printf( "Log file %s failed to open.\n", mLogFileName ); + } + + mTimeOfLastBackup = time( NULL ); + } + + + + + + + + + diff --git a/minorGems/util/log/FileLog.h b/minorGems/util/log/FileLog.h new file mode 100644 index 0000000..73c06d3 --- /dev/null +++ b/minorGems/util/log/FileLog.h @@ -0,0 +1,95 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-November-5 Jason Rohrer + * Added support for backing up logs and deleting old log data. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + +#include "minorGems/common.h" + + + +#ifndef FILE_LOG_INCLUDED +#define FILE_LOG_INCLUDED + + + +#include "PrintLog.h" + +#include + + +/** + * A file-based implementation of the Log interface. + * + * @author Jason Rohrer + */ +class FileLog : public PrintLog { + + + + public: + + + + /** + * Constructs a file log. + * + * @param inFileName the name of the file to write log messages to. + * Must be destroyed by caller. + * @param inSecondsBetweenBackups the number of seconds to wait + * before making a backup of the current log file (deleting any + * old backups), clearing the current log file, and starting + * a fresh log in the current log file. Defaults to 3600 + * seconds (one hour). Backup logs are saved to inFileName.bakup + */ + FileLog( const char *inFileName, + unsigned long inSecondsBetweenBackups = 3600 ); + + + + virtual ~FileLog(); + + + + /** + * Makes a backup of the current log file, deletes old backups, + * and clears the current log file. + */ + void makeBackup(); + + + + // overrides PrintLog::logString + virtual void logString( const char *inLoggerName, const char *inString, + int inLevel ); + + + + + protected: + + FILE *mLogFile; + + char *mLogFileName; + + unsigned long mSecondsBetweenBackups; + + unsigned long mTimeOfLastBackup; + + + static const char *mDefaultLogFileName; + + + }; + + + +#endif diff --git a/minorGems/util/log/Log.cpp b/minorGems/util/log/Log.cpp new file mode 100644 index 0000000..ba9a710 --- /dev/null +++ b/minorGems/util/log/Log.cpp @@ -0,0 +1,32 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + */ + + + +#include "Log.h" + + + +const int Log::DEACTIVATE_LEVEL = 0; + +const int Log::CRITICAL_ERROR_LEVEL = 1; + +const int Log::ERROR_LEVEL = 2; + +const int Log::WARNING_LEVEL = 3; + +const int Log::INFO_LEVEL = 4; + +const int Log::DETAIL_LEVEL = 5; + +const int Log::TRACE_LEVEL = 6; + + + +Log::~Log() { + + } diff --git a/minorGems/util/log/Log.h b/minorGems/util/log/Log.h new file mode 100644 index 0000000..19a7fba --- /dev/null +++ b/minorGems/util/log/Log.h @@ -0,0 +1,126 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-March-29 Jason Rohrer + * Added Fortify inclusion. + * + * 2010-April-5 Jason Rohrer + * Printf-like functionality. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef LOG_INCLUDED +#define LOG_INCLUDED + + + +#ifdef FORTIFY +#include "minorGems/util/development/fortify/fortify.h" +#endif + + + +/** + * An interface for a class that can perform logging functions. + * + * @author Jason Rohrer + */ +class Log { + + + + public: + + // These levels were copied from the JLog framework + // by Todd Lauinger + + static const int DEACTIVATE_LEVEL; + + static const int CRITICAL_ERROR_LEVEL; + + static const int ERROR_LEVEL; + + static const int WARNING_LEVEL; + + static const int INFO_LEVEL; + + static const int DETAIL_LEVEL; + + static const int TRACE_LEVEL; + + + + // provided so that virtual deletes work properly + virtual ~Log(); + + + + /** + * Sets the logging level of the current log. + * + * Messages with levels above the current level will not be logged. + * + * @param inLevel one of the defined logging levels. + */ + virtual void setLoggingLevel( int inLevel ) = 0; + + + + /** + * Gets the logging level of the current log. + * + * Messages with levels above the current level will not be logged. + * + * @return one of the defined logging levels. + */ + virtual int getLoggingLevel() = 0; + + + + /** + * Logs a string in this log under the default logger name. + * + * @param inString the string to log as a \0-terminated string. + * Must be destroyed by caller. + * @param inLevel the level to log inString at. + */ + virtual void logString( const char *inString, int inLevel ) = 0; + + + + /** + * Logs a string in this log, specifying a logger name. + * + * @param inLoggerName the name of the logger + * as a \0-terminated string. + * Must be destroyed by caller. + * @param inString the string to log as a \0-terminated string. + * Must be destroyed by caller. + * @param inLevel the level to log inString at. + */ + virtual void logString( const char *inLoggerName, const char *inString, + int inLevel ) = 0; + + + virtual void logPrintf( int inLevel, const char* inFormatString, ... ) + = 0; + + virtual void logNPrintf( const char *inLoggerName, + int inLevel, const char* inFormatString, ... ) + = 0; + + + }; + + + +#endif diff --git a/minorGems/util/log/Makefile b/minorGems/util/log/Makefile new file mode 100644 index 0000000..390539e --- /dev/null +++ b/minorGems/util/log/Makefile @@ -0,0 +1,61 @@ +# +# Modification History +# +# 2002-February-25 Jason Rohrer +# Created. +# Changed to be more succinct. +# + + +GXX=g++ + +ROOT_PATH = ../../.. + +DEBUG_ON_FLAG = -g +DEBUG_OFF_FLAG = + +DEBUG_FLAG = ${DEBUG_OFF_FLAG} + +TIME_PLATFORM_PATH = unix +TIME_PLATFORM = Unix + + + +TIME_O = ${ROOT_PATH}/minorGems/system/${TIME_PLATFORM_PATH}/Time${TIME_PLATFORM}.o +TIME_H = ${ROOT_PATH}/minorGems/system/Time.h +TIME_CPP = ${ROOT_PATH}/minorGems/system/${TIME_PLATFORM_PATH}/Time${TIME_PLATFORM}.cpp + + + + +testLog: AppLog.o FileLog.o PrintLog.o Log.o testLog.o ${TIME_O} + ${GXX} ${DEBUG_FLAG} -o testLog AppLog.o FileLog.o PrintLog.o Log.o testLog.o ${TIME_O} + + + +clean: + rm -f *.o ${TIME_O} + + +testLog.o: testLog.cpp AppLog.h FileLog.h Log.h + ${GXX} ${DEBUG_FLAG} -o testLog.o -c testLog.cpp + +AppLog.o: AppLog.h AppLog.cpp Log.h PrintLog.h + ${GXX} ${DEBUG_FLAG} -o AppLog.o -c AppLog.cpp + +PrintLog.o: PrintLog.h PrintLog.cpp Log.h PrintLog.h ${TIME_H} + ${GXX} ${DEBUG_FLAG} -I${ROOT_PATH} -o PrintLog.o -c PrintLog.cpp + +FileLog.o: PrintLog.h FileLog.cpp FileLog.h Log.h + ${GXX} ${DEBUG_FLAG} -o FileLog.o -c FileLog.cpp + +Log.o: Log.h Log.cpp + ${GXX} ${DEBUG_FLAG} -o Log.o -c Log.cpp + +${TIME_O}: ${TIME_H} ${TIME_CPP} + ${GXX} ${DEBUG_FLAG} -I${ROOT_PATH} -o ${TIME_O} -c ${TIME_CPP} + + + + + diff --git a/minorGems/util/log/PrintLog.cpp b/minorGems/util/log/PrintLog.cpp new file mode 100644 index 0000000..fa7170e --- /dev/null +++ b/minorGems/util/log/PrintLog.cpp @@ -0,0 +1,209 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-March-11 Jason Rohrer + * Added a missing include. + * + * 2002-April-8 Jason Rohrer + * Fixed a casting, dereferencing Win32 compile bug. + * Changed to be thread-safe. + * Changed to use thread-safe printing function. + * + * 2002-April-8 Jason Rohrer + * Fixed a signed-unsigned mismatch. + * + * 2004-January-11 Jason Rohrer + * Added lock around asctime call. + * Increased scope of lock. + * + * 2004-January-29 Jason Rohrer + * Changed to use ctime instead of localtime and asctime. + * Improved locking scope. + * Changed to use autoSprintf. + * + * 2010-April-5 Jason Rohrer + * Printf-like functionality. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + +#include "PrintLog.h" + +#include "minorGems/system/Time.h" + +#include "minorGems/util/printUtils.h" +#include "minorGems/util/stringUtils.h" + + +#include +#include +#include +#include + + +const char *PrintLog::mDefaultLoggerName = "general"; + + + +PrintLog::PrintLog() + : mLoggingLevel( Log::TRACE_LEVEL ), + mLock( new MutexLock() ) { + + } + + + +PrintLog::~PrintLog() { + delete mLock; + } + + + +void PrintLog::setLoggingLevel( int inLevel ) { + mLock->lock(); + mLoggingLevel = inLevel; + mLock->unlock(); + } + + + +int PrintLog::getLoggingLevel() { + mLock->lock(); + int level = mLoggingLevel; + mLock->unlock(); + + return level; + } + + + +void PrintLog::logString( const char *inString, int inLevel ) { + logString( (char *)mDefaultLoggerName, inString, inLevel ); + } + + + +void PrintLog::logString( const char *inLoggerName, const char *inString, + int inLevel ) { + + + // not thread-safe to read mLoggingLevel here + // without synchronization. + // However, we want logging calls that are above + // our level to execute with nearly no overhead. + // mutex might be too much overhead.... + // Besides, not being thread-safe in this case might + // (worst case) result in a missed log entry or + // an extra log entry... but setting the logging level should be rare. + if( inLevel <= mLoggingLevel ) { + + + char *message = generateLogMessage( inLoggerName, + inString, + inLevel ); + + threadPrintF( "%s\n", message ); + + delete [] message; + + } + } + + + +void PrintLog::logPrintf( int inLevel, const char* inFormatString, ... ) { + + unsigned int bufferSize = 200; + + va_list argList; + va_start( argList, inFormatString ); + + char *buffer = new char[ bufferSize ]; + + int stringLength = + vsnprintf( buffer, bufferSize, inFormatString, argList ); + + va_end( argList ); + + if( stringLength == -1 || stringLength >= (int)bufferSize ) { + // too long! + delete [] buffer; + buffer = stringDuplicate( "Message too long" ); + } + + + logString( (char *)mDefaultLoggerName, buffer, inLevel ); + + delete [] buffer; + } + + + +void PrintLog::logNPrintf( const char *inLoggerName, + int inLevel, + const char* inFormatString, ... ) { + + unsigned int bufferSize = 200; + + va_list argList; + va_start( argList, inFormatString ); + + char *buffer = new char[ bufferSize ]; + + int stringLength = + vsnprintf( buffer, bufferSize, inFormatString, argList ); + + va_end( argList ); + + if( stringLength == -1 || stringLength >= (int)bufferSize ) { + // too long! + delete [] buffer; + buffer = stringDuplicate( "Message too long" ); + } + + + logString( inLoggerName, buffer, inLevel ); + + delete [] buffer; + } + + + +char *PrintLog::generateLogMessage( const char *inLoggerName, + const char *inString, + int inLevel ) { + + unsigned long seconds, milliseconds; + + Time::getCurrentTime( &seconds, &milliseconds ); + + + // lock around ctime call, since it returns a static buffer + mLock->lock(); + + char *dateString = stringDuplicate( ctime( (time_t *)( &seconds ) ) ); + + // done with static buffer, since we made a copy + mLock->unlock(); + + + // this date string ends with a newline... + // get rid of it + dateString[ strlen(dateString) - 1 ] = '\0'; + + char *messageBuffer = autoSprintf( "L%d | %s (%ld ms) | %s | %s", + inLevel, dateString, milliseconds, + inLoggerName, inString ); + + delete [] dateString; + + + return messageBuffer; + } + + diff --git a/minorGems/util/log/PrintLog.h b/minorGems/util/log/PrintLog.h new file mode 100644 index 0000000..68665bc --- /dev/null +++ b/minorGems/util/log/PrintLog.h @@ -0,0 +1,107 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + * + * 2002-April-8 Jason Rohrer + * Changed to be thread-safe. + * + * 2010-April-5 Jason Rohrer + * Printf-like functionality. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + +#include "minorGems/common.h" + + + +#ifndef PRINT_LOG_INCLUDED +#define PRINT_LOG_INCLUDED + + + +#include "Log.h" + +#include "minorGems/system/MutexLock.h" + + + +/** + * A console-based implementation of the Log interface. + * + * @author Jason Rohrer + */ +class PrintLog : public Log { + + + + public: + + + + /** + * Constructs a print log. + */ + PrintLog(); + + + + virtual ~PrintLog(); + + + + // implement the Log interface + virtual void setLoggingLevel( int inLevel ); + + virtual int getLoggingLevel(); + + virtual void logString( const char *inString, int inLevel ); + + virtual void logString( const char *inLoggerName, const char *inString, + int inLevel ); + + virtual void logPrintf( int inLevel, const char* inFormatString, ... ); + + virtual void logNPrintf( const char *inLoggerName, + int inLevel, const char* inFormatString, + ... ); + + + protected: + + int mLoggingLevel; + + static const char *mDefaultLoggerName; + + + MutexLock *mLock; + + + + /** + * Generates a string representation of a log message. + * + * @param inLoggerName the name of the logger + * as a \0-terminated string. + * Must be destroyed by caller. + * @param inString the string to log as a \0-terminated string. + * Must be destroyed by caller. + * @param inLevel the level to log inString at. + * + * @return the log message as a \0-terminated string. + * Must be destroyed by caller. + */ + char *generateLogMessage( const char *inLoggerName, + const char *inString, + int inLevel ); + + + + }; + + + +#endif diff --git a/minorGems/util/log/testLog.cpp b/minorGems/util/log/testLog.cpp new file mode 100644 index 0000000..e6ea1e7 --- /dev/null +++ b/minorGems/util/log/testLog.cpp @@ -0,0 +1,42 @@ +/* + * Modification History + * + * 2002-February-25 Jason Rohrer + * Created. + */ + + +#include "AppLog.h" +#include "FileLog.h" + +#include "Log.h" + + +int main() { + + AppLog::warning( "A test warning" ); + AppLog::warning( "Another test warning" ); + AppLog::warning( "mainApp", "And Another test warning" ); + + AppLog::setLoggingLevel( Log::ERROR_LEVEL ); + + AppLog::warning( "Yet Another test warning" ); + AppLog::error( "mainApp", "A test error" ); + + + AppLog::setLog( new FileLog( "test.log" ) ); + + // this should be skipped + AppLog::warning( "A second test warning" ); + // this should not be + AppLog::criticalError( "A critical error" ); + + + AppLog::setLog( new FileLog( "test2.log" ) ); + + // this should be skipped + AppLog::warning( "A third test warning" ); + + + return 0; + } diff --git a/minorGems/util/printUtils.cpp b/minorGems/util/printUtils.cpp new file mode 100644 index 0000000..547c630 --- /dev/null +++ b/minorGems/util/printUtils.cpp @@ -0,0 +1,42 @@ +/* + * Modification History + * + * 2002-April-8 Jason Rohrer + * Created. + * + * 2002-April-11 Jason Rohrer + * Added a missing return value. + */ + + + +#include "printUtils.h" +#include "minorGems/system/MutexLock.h" + + + +#include + +// for variable argument lists +#include + + + +MutexLock threadPrintFLock; + + + +int threadPrintF( const char* inFormatString, ... ) { + threadPrintFLock.lock(); + + va_list argList; + va_start( argList, inFormatString ); + + int returnVal = vprintf( inFormatString, argList ); + + threadPrintFLock.unlock(); + + return returnVal; + } + + diff --git a/minorGems/util/printUtils.h b/minorGems/util/printUtils.h new file mode 100644 index 0000000..161c915 --- /dev/null +++ b/minorGems/util/printUtils.h @@ -0,0 +1,31 @@ +/* + * Modification History + * + * 2002-April-8 Jason Rohrer + * Created. + */ + +#include "minorGems/common.h" + + + +#ifndef PRINT_UTILS_INCLUDED +#define PRINT_UTILS_INCLUDED + + + +/** + * A thread-safe version of printf. + * + * Note that printf is already thread-safe on certain platforms, + * but does not seem to be thread-safe on Win32. + * + * @param inFormatString the format string to use. + * @param ... a variable argument list, with the same usage + * pattern as printf. + */ +int threadPrintF( const char* inFormatString, ... ); + + + +#endif diff --git a/minorGems/util/random/CustomRandomSource.h b/minorGems/util/random/CustomRandomSource.h new file mode 100644 index 0000000..2ea1709 --- /dev/null +++ b/minorGems/util/random/CustomRandomSource.h @@ -0,0 +1,244 @@ +/* + * Modification History + * + * 2009-January-14 Jason Rohrer + * Created. + * + * 2009-February-5 Jason Rohrer + * Added support for restoring from saved state. + * + */ + + + +#ifndef CUSTOM_RANDOM_SOURCE_INCLUDED +#define CUSTOM_RANDOM_SOURCE_INCLUDED + +#include +#include +#include "RandomSource.h" + + +/** + * Implementation of RandomSource that does not depend on platform or library. + * + * Maintains its own internal state. + */ +class CustomRandomSource : public RandomSource { + + public: + + // seeds itself with current time + CustomRandomSource(); + + // specify the seed + CustomRandomSource( unsigned int inSeed ); + + // save for rewind later + void saveState(); + + void rewindState(); + + + // can be used to save state to disk + unsigned int getSavedState(); + + void restoreFromSavedState( unsigned int inSavedState ); + + + + void reseed( unsigned int inSeed ); + + + // implements these functions + float getRandomFloat(); // in interval [0,1.0] + double getRandomDouble(); // in interval [0,1.0] + unsigned int getRandomInt(); // in interval [0,MAX] + unsigned int getIntMax(); // returns MAX + + int getRandomBoundedInt( int inRangeStart, + int inRangeEnd ); + + double getRandomBoundedDouble( double inRangeStart, + double inRangeEnd ); + char getRandomBoolean(); + + + private: + double mInvMAXPlusOne; // 1 / ( MAX + 1 ) + + unsigned int mState; + + unsigned int mSavedState; + + + // returns next number and updates state + unsigned int genRand32(); + + }; + + + +inline CustomRandomSource::CustomRandomSource() { + MAX = 4294967295U; + mState = (unsigned)( time(NULL) ); + invMAX = (float)1.0 / ((float)MAX); + invDMAX = 1.0 / ((double)MAX); + mInvMAXPlusOne = 1.0 / ( ( (float)MAX ) + 1.0 ); + + saveState(); + } + + + +inline CustomRandomSource::CustomRandomSource( unsigned int inSeed ) { + MAX = 4294967295U; + mState = inSeed; + invMAX = (float)1.0 / ((float)MAX); + invDMAX = 1.0 / ((double)MAX); + mInvMAXPlusOne = 1.0 / ( ( (double)MAX ) + 1.0 ); + + saveState(); + } + + + +inline void CustomRandomSource::saveState() { + mSavedState = mState; + } + + + +inline void CustomRandomSource::rewindState() { + mState = mSavedState; + } + + + +inline unsigned int CustomRandomSource::getSavedState() { + return mSavedState; + } + + +inline void CustomRandomSource::restoreFromSavedState( + unsigned int inSavedState) { + + mState = inSavedState; + } + + + + +inline void CustomRandomSource::reseed( unsigned int inSeed ) { + mState = inSeed; + } + + + + +// from Cultivation/Passage's landscape.cpp +// faster as a set of macros +#define CustNum1( inSeed ) \ + ( ( inSeed * 0xFEA09B9DU ) + 1 ) + +#define CustNum2( inSeed ) \ + ( ( ( inSeed ^ CustNum1( inSeed ) ) * 0x9C129511U ) + 1 ) + +#define CustNum3( inSeed ) \ + ( ( inSeed * 0x2512CFB8U ) + 1 ) + +#define CustNum4( inSeed ) \ + ( ( ( inSeed ^ CustNum3( inSeed ) ) * 0xB89C8895U ) + 1 ) + +#define CustNum5( inSeed ) \ + ( ( inSeed * 0x6BF962C1U ) + 1 ) + +#define CustNum6( inSeed ) \ + ( ( ( inSeed ^ CustNum5( inSeed ) ) * 0x4BF962C1U ) + 1 ) + + + + +inline unsigned int CustomRandomSource::genRand32() { + mState = + CustNum2( mState ) ^ + (CustNum4( mState ) >> 11) ^ + (CustNum6( mState ) >> 22); + + return mState; + } + + + + + +inline float CustomRandomSource::getRandomFloat() { + + return (float)(genRand32()) * invMAX; + } + + + +inline double CustomRandomSource::getRandomDouble() { + + return (double)(genRand32()) * invDMAX; + } + + + +inline unsigned int CustomRandomSource::getRandomInt() { + + return genRand32(); + } + + + +inline unsigned int CustomRandomSource::getIntMax() { + + return MAX; + } + + + +inline int CustomRandomSource::getRandomBoundedInt( int inRangeStart, + int inRangeEnd ) { + + // float in range [0,1) + double randFloat = (double)( genRand32() ) * mInvMAXPlusOne; + + int onePastRange = inRangeEnd + 1; + + int magnitude = (int)( randFloat * ( onePastRange - inRangeStart ) ); + + return magnitude + inRangeStart; + } + + +inline double CustomRandomSource::getRandomBoundedDouble( double inRangeStart, + double inRangeEnd ) { + + // double in range [0,1] + double randDouble = getRandomDouble(); + + double magnitude = randDouble * ( inRangeEnd - inRangeStart ); + + return magnitude + inRangeStart; + } + + + +inline char CustomRandomSource::getRandomBoolean() { + + // float in range [0,1] + double randFloat = getRandomFloat(); + + if( randFloat < 0.5 ) { + return true; + } + else { + return false; + } + } + + +#endif diff --git a/minorGems/util/random/Noise.cpp b/minorGems/util/random/Noise.cpp new file mode 100644 index 0000000..833324b --- /dev/null +++ b/minorGems/util/random/Noise.cpp @@ -0,0 +1,312 @@ +// Jason Rohrer +// Noise.cpp + +/** +* +* Noise generation implementation +* +* +* Created 11-3-99 +* Mods: +* Jason Rohrer 12-20-2000 Changed genFractalNoise2d function to make +* it less blocky. +* +*/ + + +#include "Noise.h" + + +// fills 2d image with ARGB noise +void genRandNoise2d(unsigned long *buff, int buffHigh, int buffWide) { + + int *yOffset = new int[buffHigh]; + + // precalc y offsets + for( int y=0; y> 24 & 0xFF) + alpha; + int buffRed = (buffARGB >> 16 & 0xFF) + red; + int buffGreen = (buffARGB >> 8 & 0xFF) + green; + int buffBlue = (buffARGB & 0xFF) + blue; + + + if( buffAlpha < 0) buffAlpha = 0; + if( buffRed < 0) buffRed = 0; + if( buffGreen < 0) buffGreen = 0; + if( buffBlue < 0) buffBlue = 0; + + if( buffAlpha > 255) buffAlpha = 255; + if( buffRed > 255) buffRed = 255; + if( buffGreen > 255) buffGreen = 255; + if( buffBlue > 255) buffBlue = 255; + + + buff[ yOffset[buffY] + buffX] = buffBlue | buffGreen << 8 | buffRed << 16 | buffAlpha << 24; + + buffX++; + blockX++; + if( buffX >= buffWide ) blockX = blockWide; // if this block hangs outside buffer + + } + buffY++; + blockY++; + if( buffY >= buffHigh ) blockY = blockHigh; // if this block hangs outside buffer + + } + buffX = startX + blockWide; + } + buffY = startY + blockHigh; + } + } + + delete [] yOffset; + } + + + +void genFractalNoise2d( double *inBuffer, int inWidth, int inMaxFrequency, + double inFPower, char inInterpolate, RandomSource *inRandSource ) { + + RandomSource *r = inRandSource; + + int w = inWidth; + + int i, x, y, f; + + int numPoints = w * w; + + // first, fill surface with a uniform 0.5 value + for( i=0; igetRandomDouble() - 1 ) * weight; + } + + // now walk though 2d array and perform + // bilinear interpolation between blocks + for( y=0; y 1.0 ) { + inBuffer[y * w + x] = 1.0; + } + else if( inBuffer[y * w + x] < 0.0 ) { + inBuffer[y * w + x] = 0.0; + } + } + } + + delete [] blockValues; + } + } + + +void genFractalNoise( double *inBuffer, int inWidth, int inMaxFrequency, + double inFPower, char inInterpolate, RandomSource *inRandSource ) { + + RandomSource *r = inRandSource; + + int w = inWidth; + + int i, x, f; + + // first, fill array with uniform 0.5 values + for( i=0; igetRandomDouble() - 1 ) * weight; + } + + // now walk though array and perform linear interpolation between blocks + + for( x=0; x 1.0 ) { + inBuffer[x] = 1.0; + } + else if( inBuffer[x] < 0.0 ) { + inBuffer[x] = 0.0; + } + } + + delete [] blockValues; + } + + } diff --git a/minorGems/util/random/Noise.h b/minorGems/util/random/Noise.h new file mode 100644 index 0000000..acdde60 --- /dev/null +++ b/minorGems/util/random/Noise.h @@ -0,0 +1,89 @@ +// Jason Rohrer +// Noise.h + +/** +* +* Noise generation interface +* +* +* Created 11-3-99 +* Mods: +* Jason Rohrer 12-20-2000 Added a fractal noise function +* that fills a double array. +* +*/ + +#include "minorGems/common.h" + + + +#ifndef NOISE_INCLUDED +#define NOISE_INCLUDED + + +#include +#include + +#include "RandomSource.h" + + + + +// fills 2d image with RGBA noise +void genRandNoise2d(unsigned long *buff, int buffHigh, int buffWide); + + + +// returns a random floating point between 0 and 1 +inline float floatRand() { + float invRandMax = 1 / ((float)RAND_MAX); + return (float)(rand()) * invRandMax; + } + + +// fills 2d image with ARGB fractal noise +void genFractalNoise2d(unsigned long *buff, int buffHigh, int buffWide); + + +/** + * Fills a 2d array with 1/f fractal noise. + * + * @param inBuffer a pre-allocated buffer to fill. + * @param inWidth the width and height of the 2d array + * contained in the buffer. + * Must be a power of 2. + * @param inMaxFrequency the maximum frequency of noise modulation to + * include, in [2,inWidth]. Lower values produce more "blurry" or + * blocky noise. + * @param inFPower power to raise F to whene generating noise. + * Amplitude of modulation = 1 / (F^inFPower). + * @param inInterpolate set to true to perform interpolation of + * each frequency modulation. Setting to false produces a "blockier" + * noise, while setting to true makes the noise more cloud-like. + * @param inRandSource the source to use for random numbers. + */ +void genFractalNoise2d( double *inBuffer, int inWidth, int inMaxFrequency, + double inFPower, char inInterpolate, RandomSource *inRandSource ); + + +/** + * Fills a 1d array with 1/f fractal noise. + * + * @param inBuffer a pre-allocated buffer to fill. + * @param inWidth the width of the 2d array + * contained in the buffer. + * Must be a power of 2. + * @param inMaxFrequency the maximum frequency of noise modulation to + * include, in [2,inWidth]. Lower values produce more "blurry" or + * blocky noise. + * @param inFPower power to raise F to whene generating noise. + * Amplitude of modulation = 1 / (F^inFPower). + * @param inInterpolate set to true to perform interpolation of + * each frequency modulation. Setting to false produces a "blockier" + * noise, while setting to true makes the noise more cloud-like. + * @param inRandSource the source to use for random numbers. + */ +void genFractalNoise( double *inBuffer, int inWidth, int inMaxFrequency, + double inFPower, char inInterpolate, RandomSource *inRandSource ); + +#endif diff --git a/minorGems/util/random/RandomSource.h b/minorGems/util/random/RandomSource.h new file mode 100644 index 0000000..55dc512 --- /dev/null +++ b/minorGems/util/random/RandomSource.h @@ -0,0 +1,86 @@ +// Jason Rohrer +// RandomSource.h + +/** +* +* abstract interface for random number generation +* +* Can be implemented by: +* --stdlib rand() function calls +* --seed file full of random numbers +* +* Created 12-7-99 +* Mods: +* Jason Rohrer 9-28-2000 Added a getRandomBoundedInt() +* interface to faciliate retrieving +* an integer in a given range [a,b] +* where each integer in the range +* has the same probability of being +* returned. +* Jason Rohrer 12-16-2000 Added a getRandomDouble() interface. +* Jason Rohrer 11-21-2005 Added a virtual destructor. +* Jason Rohrer 07-09-2006 Added a getRandomBoundedDouble interface. +* Jason Rohrer 07-27-2006 Added a getRandomBoolean interface. +*/ + +#include "minorGems/common.h" + + + +#ifndef RANDOM_SOURCE_INCLUDED +#define RANDOM_SOURCE_INCLUDED + + +class RandomSource { + + public: + // pure virtual functions implemented by inheriting classes + + virtual float getRandomFloat() = 0; // in interval [0,1.0] + virtual double getRandomDouble() = 0; // in interval [0,1.0] + virtual unsigned int getRandomInt() = 0; // in interval [0,MAX] + virtual unsigned int getIntMax() = 0; // returns MAX + + /** + * Returns a random integer in [rangeStart,rangeEnd] + * where each integer in the range has an equal + * probability of occuring. + */ + virtual int getRandomBoundedInt( int inRangeStart, + int inRangeEnd ) = 0; + + + /** + * Returns a random double in [rangeStart,rangeEnd]. + */ + virtual double getRandomBoundedDouble( double inRangeStart, + double inRangeEnd ) = 0; + + + + /** + * Gets a random true/false value. + */ + virtual char getRandomBoolean() = 0; + + + + virtual ~RandomSource(); + + + protected: + unsigned int MAX; // maximum integer random number + float invMAX; // floating point inverse of MAX + double invDMAX; // double invers of MAX + }; + + + +inline RandomSource::~RandomSource() { + // does nothing + // exists to ensure that subclass destructors are called + } + + + +#endif diff --git a/minorGems/util/random/StdRandomSource.h b/minorGems/util/random/StdRandomSource.h new file mode 100644 index 0000000..5b242d1 --- /dev/null +++ b/minorGems/util/random/StdRandomSource.h @@ -0,0 +1,152 @@ +// Jason Rohrer +// StdRandomSource.h + +/** +* +* Implementation of random number generation that uses stdlib calls +* +* +* Created 12-7-99 +* Mods: +* Jason Rohrer 9-28-2000 Added a getRandomBoundedInt() +* implementation +* Jason Rohrer 12-7-2000 Overloaded constructor to support +* specifying a seed. +* Jason Rohrer 12-16-2000 Added a getRandomDouble() function. +* Jason Rohrer 12-17-2000 Fixed bug in initialization of invDMAX +* in default constructor. +* Jason Rohrer 9-13-2001 Fixed a bug in getRandomBoundedInt() +* as floats were being used, and they +* don't provide enough resolution. +* Jason Rohrer 10-11-2002 Fixed some type casting warnings. +* Jason Rohrer 07-09-2006 Added getRandomBoundedDouble. +* Jason Rohrer 07-27-2006 Added getRandomBoolean. +*/ + +#include "minorGems/common.h" + + + +#ifndef STD_RANDOM_SOURCE_INCLUDED +#define STD_RANDOM_SOURCE_INCLUDED + +#include +#include +#include "RandomSource.h" + +class StdRandomSource : public RandomSource { + + public: + + StdRandomSource(); // needed to seed stdlib generator + + // specify the seed for the stdlib generator + StdRandomSource( unsigned int inSeed ); + + + // implements these functions + float getRandomFloat(); // in interval [0,1.0] + double getRandomDouble(); // in interval [0,1.0] + unsigned int getRandomInt(); // in interval [0,MAX] + unsigned int getIntMax(); // returns MAX + + int getRandomBoundedInt( int inRangeStart, + int inRangeEnd ); + + double getRandomBoundedDouble( double inRangeStart, + double inRangeEnd ); + char getRandomBoolean(); + + + private: + double mInvMAXPlusOne; // 1 / ( MAX + 1 ) + }; + + + +inline StdRandomSource::StdRandomSource() { + MAX = RAND_MAX; + srand( (unsigned)time(NULL) ); + invMAX = (float)1.0 / ((float)MAX); + invDMAX = 1.0 / ((double)MAX); + mInvMAXPlusOne = 1.0 / ( ( (float)MAX ) + 1.0 ); + } + + + +inline StdRandomSource::StdRandomSource( unsigned int inSeed ) { + MAX = RAND_MAX; + srand( inSeed ); + invMAX = (float)1.0 / ((float)MAX); + invDMAX = 1.0 / ((double)MAX); + mInvMAXPlusOne = 1.0 / ( ( (double)MAX ) + 1.0 ); + } + + + +inline float StdRandomSource::getRandomFloat() { + + return (float)(rand()) * invMAX; + } + + + +inline double StdRandomSource::getRandomDouble() { + + return (double)(rand()) * invDMAX; + } + + + +inline unsigned int StdRandomSource::getRandomInt() { + + return rand(); + } + +inline unsigned int StdRandomSource::getIntMax() { + + return MAX; + } + +inline int StdRandomSource::getRandomBoundedInt( int inRangeStart, + int inRangeEnd ) { + + // float in range [0,1) + double randFloat = (double)( rand() ) * mInvMAXPlusOne; + + int onePastRange = inRangeEnd + 1; + + int magnitude = (int)( randFloat * ( onePastRange - inRangeStart ) ); + + return magnitude + inRangeStart; + } + + +inline double StdRandomSource::getRandomBoundedDouble( double inRangeStart, + double inRangeEnd ) { + + // double in range [0,1] + double randDouble = getRandomDouble(); + + double magnitude = randDouble * ( inRangeEnd - inRangeStart ); + + return magnitude + inRangeStart; + } + + + +inline char StdRandomSource::getRandomBoolean() { + + // float in range [0,1] + double randFloat = getRandomFloat(); + + if( randFloat < 0.5 ) { + return true; + } + else { + return false; + } + } + + +#endif diff --git a/minorGems/util/random/testRandom.cpp b/minorGems/util/random/testRandom.cpp new file mode 100644 index 0000000..bca6608 --- /dev/null +++ b/minorGems/util/random/testRandom.cpp @@ -0,0 +1,215 @@ +#include "RandomSource.h" + +#include + + +// perform tests on a random source + +// tests found here: +// NIST FIPS 140-1 U.S. Federal Standard +// http://csrc.nist.gov/publications/fips/fips140-1/fips1401.pdf + +void testRandom( RandomSource *inSource ) { + + int bitsPerStep = 1; + + + // 20000 bits + // must be a multiple of bitsPerStep + int numBits = 20000; + + int numSteps = numBits / bitsPerStep; + + unsigned int *drawnNumbers = new unsigned int[ numSteps ]; + + unsigned int *drawnBits = new unsigned int[ numBits ]; + + + unsigned int bitSum = 0; + + // 1 or 0 when we're in a run + unsigned int currentRunType = 2; + unsigned int currentRunLength = 0; + + + unsigned int longestRunLength = 0; + + #define maxRunLengthToTrack 100 + + unsigned int runLengthCounts[2][ maxRunLengthToTrack ]; + + int r; + for( r=0; rgetRandomBoundedInt( 0, 1 ); + //drawnNumbers[i] = inSource->getRandomBoolean(); + + + for( int b=0; b> b & 0x01 ); + bitSum += bit; + + drawnBits[bitIndex] = bit; + bitIndex++; + + + if( bit == currentRunType ) { + currentRunLength++; + + + if( currentRunLength > longestRunLength ) { + longestRunLength = currentRunLength; + } + } + else { + // end of run + + if( currentRunLength > 0 && + currentRunLength < maxRunLengthToTrack ) { + // count it + runLengthCounts[currentRunType][ currentRunLength ] ++; + } + + currentRunType = bit; + currentRunLength = 1; + } + } + } + + float mean = (float)bitSum / numBits; + + printf( "Mean = %f\n", mean ); + + + float varSum = 0; + + for( i=0; i 2326 && Z[t] < 2674 ) { + } + else { + failed = true; + //printf( "autocorrelation test failed for Z[%d] = %d\n", t, Z[t] ); + } + } + if( !failed ) { + printf( "Autocorrelation test passed.\n" ); + } + + + + + delete [] drawnNumbers; + delete [] drawnBits; + } + + + + +#include "CustomRandomSource.h" +#include "StdRandomSource.h" + +int main() { + + CustomRandomSource randSource( 11234258 ); + //StdRandomSource randSource( 11 ); + + testRandom( &randSource ); + + + return 0; + } + diff --git a/minorGems/util/stringUtils.cpp b/minorGems/util/stringUtils.cpp new file mode 100644 index 0000000..45663bd --- /dev/null +++ b/minorGems/util/stringUtils.cpp @@ -0,0 +1,450 @@ +/* + * Modification History + * + * 2003-May-10 Jason Rohrer + * Created. + * Added a tokenization function. + * + * 2003-June-14 Jason Rohrer + * Added a join function. + * + * 2003-June-22 Jason Rohrer + * Added an autoSprintf function. + * + * 2003-July-27 Jason Rohrer + * Fixed bugs in autoSprintf return values for certain cases. + * + * 2003-August-12 Jason Rohrer + * Added a concatonate function. + * + * 2003-September-7 Jason Rohrer + * Changed so that split returns last part, even if it is empty. + * + * 2004-January-15 Jason Rohrer + * Added work-around for MinGW vsnprintf bug. + * + * 2006-June-2 Jason Rohrer + * Added a stringStartsWith function. + * + * 2009-September-7 Jason Rohrer + * Fixed int types. + * Switched away from StringBufferOutputStream to new function in SimpleVector. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + + +#include "stringUtils.h" + +#include + + + +char *stringToLowerCase( const char *inString ) { + + unsigned int length = strlen( inString ); + + char *returnString = stringDuplicate( inString ); + + for( unsigned int i=0; i stringLength ) { + return false; + } + else { + for( unsigned int i=0; i *parts = new SimpleVector(); + + char *workingString = stringDuplicate( inString ); + char *workingStart = workingString; + + unsigned int separatorLength = strlen( inSeparator ); + + char *foundSeparator = strstr( workingString, inSeparator ); + + while( foundSeparator != NULL ) { + // terminate at separator + foundSeparator[0] = '\0'; + parts->push_back( stringDuplicate( workingString ) ); + + // skip separator + workingString = &( foundSeparator[ separatorLength ] ); + foundSeparator = strstr( workingString, inSeparator ); + } + + // add the remaining part, even if it is the empty string + parts->push_back( stringDuplicate( workingString ) ); + + + delete [] workingStart; + + *outNumParts = parts->size(); + char **returnArray = parts->getElementArray(); + + delete parts; + + return returnArray; + } + + + +char *join( char **inStrings, int inNumParts, const char *inGlue ) { + SimpleVector result; + + for( int i=0; i *inTargetVector, + SimpleVector *inSubstituteVector ) { + + int numTargets = inTargetVector->size(); + + char *newHaystack = stringDuplicate( inHaystack ); + + char tagFound; + + for( int i=0; igetElement( i ) ), + *( inSubstituteVector->getElement( i ) ), + &tagFound ); + delete [] newHaystack; + + newHaystack = newHaystackWithReplacements; + } + + return newHaystack; + } + + + +SimpleVector *tokenizeString( const char *inString ) { + + char *tempString = stringDuplicate( inString ); + + char *restOfString = tempString; + + SimpleVector *foundTokens = new SimpleVector(); + + SimpleVector *currentToken = new SimpleVector(); + + + while( restOfString[0] != '\0' ) { + // characters remain + + // skip whitespace + char nextChar = restOfString[0]; + while( nextChar == ' ' || nextChar == '\n' || + nextChar == '\r' || nextChar == '\t' ) { + + restOfString = &( restOfString[1] ); + nextChar = restOfString[0]; + } + + if( restOfString[0] != '\0' ) { + + // a token + + while( nextChar != ' ' && nextChar != '\n' && + nextChar != '\r' && nextChar != '\t' && + nextChar != '\0' ) { + + // still not whitespace + currentToken->push_back( nextChar ); + + restOfString = &( restOfString[1] ); + nextChar = restOfString[0]; + } + + // reached end of token + foundTokens->push_back( currentToken->getElementString() ); + currentToken->deleteAll(); + } + } + + delete [] tempString; + + delete currentToken; + + return foundTokens; + } + + + +char *autoSprintf( const char* inFormatString, ... ) { + + unsigned int bufferSize = 50; + + va_list argList; + va_start( argList, inFormatString ); + + char *buffer = new char[ bufferSize ]; + + int stringLength = + vsnprintf( buffer, bufferSize, inFormatString, argList ); + + va_end( argList ); + + + if( stringLength != -1 ) { + // follows C99 standard... + // stringLength is the length of the string that would have been + // written if the buffer was big enough + + // not room for string and terminating \0 in bufferSize bytes + if( (unsigned int)stringLength >= bufferSize ) { + + // need to reprint with a bigger buffer + delete [] buffer; + + bufferSize = (unsigned int)( stringLength + 1 ); + + va_list argList; + va_start( argList, inFormatString ); + + buffer = new char[ bufferSize ]; + + // can simply use vsprintf now + vsprintf( buffer, inFormatString, argList ); + + va_end( argList ); + + return buffer; + } + else { + // buffer was big enough + + // trim the buffer to fit the string + char *returnString = stringDuplicate( buffer ); + delete [] buffer; + + return returnString; + } + } + else { + // follows old ANSI standard + // -1 means the buffer was too small + + // Note that some buggy non-C99 vsnprintf implementations + // (notably MinGW) + // do not return -1 if stringLength equals bufferSize (in other words, + // if there is not enough room for the trailing \0). + + // Thus, we need to check for both + // (A) stringLength == -1 + // (B) stringLength == bufferSize + // below. + + // keep doubling buffer size until it's big enough + while( stringLength == -1 || + (unsigned int)stringLength == bufferSize ) { + + delete [] buffer; + + if( (unsigned int)stringLength == bufferSize ) { + // only occurs if vsnprintf implementation is buggy + + // might as well use the information, though + // (instead of doubling the buffer size again) + bufferSize = bufferSize + 1; + } + else { + // double buffer size again + bufferSize = 2 * bufferSize; + } + + va_list argList; + va_start( argList, inFormatString ); + + buffer = new char[ bufferSize ]; + + stringLength = + vsnprintf( buffer, bufferSize, inFormatString, argList ); + + va_end( argList ); + } + + // trim the buffer to fit the string + char *returnString = stringDuplicate( buffer ); + delete [] buffer; + + return returnString; + } + } + diff --git a/minorGems/util/stringUtils.h b/minorGems/util/stringUtils.h new file mode 100644 index 0000000..e26e6bf --- /dev/null +++ b/minorGems/util/stringUtils.h @@ -0,0 +1,321 @@ +/* + * Modification History + * + * 2002-April-6 Jason Rohrer + * Created. + * + * 2002-April-8 Jason Rohrer + * Fixed multiple inclusion bug + * + * 2002-May-7 Jason Rohrer + * Added functions for case-insensitive substring finding and case + * conversion. + * + * 2002-May-9 Jason Rohrer + * Fixed a bug when string not found. + * + * 2002-May-26 Jason Rohrer + * Added a function for string comparison ignoring cases. + * + * 2003-March-28 Jason Rohrer + * Added split function. + * + * 2003-May-1 Jason Rohrer + * Added replacement functions. + * + * 2003-May-4 Jason Rohrer + * Added list replacement function. + * + * 2003-May-10 Jason Rohrer + * Moved implementations into a .cpp file. This will break several + * projects. + * Added a tokenization function. + * + * 2003-June-14 Jason Rohrer + * Added a join function. + * + * 2003-June-22 Jason Rohrer + * Added an autoSprintf function. + * + * 2003-August-12 Jason Rohrer + * Added a concatonate function. + * + * 2003-September-7 Jason Rohrer + * Improved split comment. + * + * 2006-June-2 Jason Rohrer + * Added a stringStartsWith function. + * + * 2010-May-14 Jason Rohrer + * String parameters as const to fix warnings. + */ + + + +#include "minorGems/common.h" +#include "minorGems/util/SimpleVector.h" + + + +#ifndef STRING_UTILS_INCLUDED +#define STRING_UTILS_INCLUDED + + + +// ANSI does not support strdup, strcasestr, or strcasecmp +#include +#include +#include + + + + +/** + * Duplicates a string into a newly allocated string. + * + * @param inString the \0-terminated string to duplicate. + * Must be destroyed by caller if non-const. + * + * @return a \0-terminated duplicate of inString. + * Must be destroyed by caller. + */ +inline char *stringDuplicate( const char *inString ) { + + char *returnBuffer = new char[ strlen( inString ) + 1 ]; + + strcpy( returnBuffer, inString ); + + return returnBuffer; + } + + + +/** + * Converts a string to lower case. + * + * @param inString the \0-terminated string to convert. + * Must be destroyed by caller if non-const. + * + * @return a newly allocated \0-terminated string + * that is a lowercase version of inString. + * Must be destroyed by caller. + */ +char *stringToLowerCase( const char *inString ); + + + +/** + * Searches for the first occurrence of one string in another. + * + * @param inHaystack the \0-terminated string to search in. + * Must be destroyed by caller if non-const. + * @param inNeedle the \0-terminated string to search for. + * Must be destroyed by caller if non-const. + * + * @return a string pointer into inHaystack where the + * first occurrence of inNeedle starts, or NULL if inNeedle is not found. + */ +char *stringLocateIgnoreCase( const char *inHaystack, + const char *inNeedle ); + + + +/** + * Compares two strings, ignoring case. + * + * @param inStringA the first \0-terminated string. + * Must be destroyed by caller if non-const. + * @param inStringB the second \0-terminated string. + * Must be destroyed by caller if non-const. + * + * @return an integer less than, equal to, or greater than zero if + * inStringA is found, respectively, to be less than, to match, or be + * greater than inStringB. + */ +int stringCompareIgnoreCase( const char *inStringA, + const char *inStringB ); + + + +/** + * Checks if a string starts with a given prefix string. + * + * @param inString a \0-terminated string. + * Must be destroyed by caller if non-const. + * @param inPrefix the prefix to look for as a \0-terminated string. + * Must be destroyed by caller if non-const. + * + * @return true if inString begins with inPrefix, or false otherwise. + */ +char stringStartsWith( const char *inString, const char *inPrefix ); + + + +/** + * Splits a string into parts around a separator string. + * + * Note that splitting strings that start and/or end with the separator + * results in "empty" strings being returned at the start and/or end + * of the parts array. + * + * For example, splitting "a#short#test" around the "#" separator will + * result in the array { "a", "short", "test" }, but splitting + * "#another#short#test#" will result in the array + * { "", "another", "short", "test", "" }. + * + * This differs somewhat from the perl version of split, but it gives the + * caller more information about the string being split. + * + * @param inString the string to split. + * Must be destroyed by caller if non-const. + * @param inSeparator the separator string. + * Must be destroyed by caller if non-const. + * @param outNumParts pointer to where the the number of parts (the length of + * the returned array) should be returned. + * + * @return an array of split parts. + * Must be destroyed by caller. + */ +char **split( const char *inString, const char *inSeparator, + int *outNumParts ); + + + +/** + * Joins a collection of strings using a separator string. + * + * @param inStrings the strings to join. + * Array and strings must be destroyed by caller. + * @param inNumParts the number of strings to join. + * @param inSeparator the separator string. + * Must be destroyed by caller if non-const. + * + * @return the joined string. + * Must be destroyed by caller. + */ +char *join( char **inStrings, int inNumParts, const char *inGlue ); + + + +/** + * Concatonates two strings. + * + * @param inStringA the first string in the concatonation. + * Must be destroyed by caller if non-const. + * @param inStringB the second string in the concatonation. + * Must be destroyed by caller if non-const. + * + * @return the concatonation. + * Must be destroyed by caller. + */ +char *concatonate( const char *inStringA, const char *inStringB ); + + + +/** + * Replaces the first occurrence of a target string with + * a substitute string. + * + * All parameters and return value must be destroyed by caller. + * + * @param inHaystack the string to search for inTarget in. + * @param inTarget the string to search for. + * @param inSubstitute the string to replace the first occurrence + * of the target with. + * @param outFound a pre-allocated character which will be filled + * with true if the target is found, and filled with false + * otherwise. + * + * @return a newly allocated string with the substitution performed. + */ +char *replaceOnce( const char *inHaystack, const char *inTarget, + const char *inSubstitute, + char *outFound ); + + + +/** + * Replaces the all occurrences of a target string with + * a substitute string. + * + * Note that this function is not self-insertion-safe: + * If inSubstitute contains inTarget, this function will + * enter an infinite loop. + * + * All parameters and return value must be destroyed by caller. + * + * @param inHaystack the string to search for inTarget in. + * @param inTarget the string to search for. + * @param inSubstitute the string to replace the all occurrences + * of the target with. + * @param outFound a pre-allocated character which will be filled + * with true if the target is found at least once, + * and filled with false otherwise. + * + * @return a newly allocated string with the substitutions performed. + */ +char *replaceAll( const char *inHaystack, const char *inTarget, + const char *inSubstitute, + char *outFound ); + + + +/** + * Replaces the all occurrences of each target string on a list with + * a corresponding substitute string. + * + * Note that this function is not self-insertion-safe: + * If inSubstituteVector contains elements from inTargetVector, + * this function will enter an infinite loop. + * + * All parameters and return value must be destroyed by caller. + * + * @param inHaystack the string to search for targets in. + * @param inTargetVector the list of strings to search for. + * Vector and contained strings must be destroyed by caller. + * @param inSubstituteVector the corresponding list of strings to + * replace the all occurrences of the targets with. + * Vector and contained strings must be destroyed by caller. + * + * @return a newly allocated string with the substitutions performed. + */ +char *replaceTargetListWithSubstituteList( + const char *inHaystack, + SimpleVector *inTargetVector, + SimpleVector *inSubstituteVector ); + + + + +/** + * Split a string into tokens using whitespace as separators. + * + * @param inString the string to tokenize. + * Must be destroyed by caller. + * + * @return a vector of extracted strings. + * Vector and strings must be destroyed by caller. + */ +SimpleVector *tokenizeString( const char *inString ); + + + +/** + * Prints formatted data elements into a newly allocated string buffer. + * + * Similar to sprintf, except that buffer sizing is automatic (and therefore + * safer than manual sizing). + * + * @param inFormatString the format string to print from. + * @param variable argument list data values to fill in the format string + * with (uses same conventions as printf). + * + * @return a newly allocated buffer containing the \0-terminated printed + * string. + * Must be destroyed by caller. + */ +char *autoSprintf( const char* inFormatString, ... ); + + + +#endif diff --git a/minorGems/util/test/testSnprintf.cpp b/minorGems/util/test/testSnprintf.cpp new file mode 100644 index 0000000..ec6ee35 --- /dev/null +++ b/minorGems/util/test/testSnprintf.cpp @@ -0,0 +1,58 @@ +/* + * Modification History + * + * 2004-January-15 Jason Rohrer + * Created. + */ + +/** + * A test program for snprintf behavior. + */ + + +#include +#include +#include + + + +int main() { + int numStrings = 3; + + // test strings of length 3, 4, and 5 + const char *testStrings[3] = { "tst", "test", "testt" }; + + int result; + + // a buffer of length 4, which IS NOT large + // enough to hold the last two testStrings + char *buffer = (char*)( malloc( 4 ) ); + + int i; + + for( i=0; i + + +class A { + public: + A() { + mV.push_back( 50 ); + mV.push_back( 60 ); + } + + ~A() { + printf( "A's destructor invoked\n" ); + } + + + SimpleVector mV; + + void print() { + printf( "A{ " ); + + for( int i=0; i v; + + A a; + + + v.push_back( a ); + v.push_back( a ); + + // push more back to force vector expansion + for( int i=0; i<10; i++ ) { + v.push_back( a ); + } + + + + /* + printf( "making array\n" ); + + + A *array = new A[100]; + + + printf( "deleting array\n" ); + + delete [] array; + + + + printf( "About to exit\n" ); + */ + printf( "making w\n" ); + SimpleVector w; + + w.push_back( a ); + w.push_back( a ); + + // push more back to force vector expansion + for( int i=0; i<10; i++ ) { + w.push_back( a ); + } + + printf( "deleting from w\n" ); + + // delete a few to test for leak + w.deleteElement( 0 ); + w.deleteElement( 0 ); + + + + + { + + // copy constructor + SimpleVector clone = v; + + printf( "clone = " ); + + for( int i=0; iprint(); + } + printf( "\n" ); + + + // assignment operator + clone = w; + + printf( "clone = " ); + + for( int i=0; iprint(); + } + printf( "\n" ); + + // clone deleted here + } + + + // v still okay? + printf( "v = " ); + + for( int i=0; iprint(); + } + printf( "\n" ); + + // w still okay? + printf( "w = " ); + + for( int i=0; iprint(); + } + printf( "\n" ); + + } + + diff --git a/runToBuild b/runToBuild new file mode 100755 index 0000000..3eb5d57 --- /dev/null +++ b/runToBuild @@ -0,0 +1,39 @@ +#!/bin/bash + +# +# Modification History +# +# 2007-November-12 Jason Rohrer +# Copied from Cultivation. +# + + +cd gamma256/gameSource +chmod u+x ./configure +./configure + + + +echo "Building Passage..." + +make + + + +cd ../.. + +mkdir graphics +mkdir music +mkdir settings + +cp gamma256/gameSource/Passage ./Passage +cp gamma256/documentation/Readme.txt . +cp gamma256/gameSource/graphics/* ./graphics +cp gamma256/gameSource/music/* ./music +cp gamma256/gameSource/settings/* ./settings + +echo "Run Passage to play." + + + +