diff --git a/README.md b/README.md index ae6088d..ac925d6 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,12 @@ CIS 565 Project3 : CUDA Pathtracer =================== -Fall 2014 - -Due Wed, 10/8 (submit without penalty until Sun, 10/12) - ## INTRODUCTION -In this project, you will implement a CUDA based pathtracer capable of -generating pathtraced rendered images extremely quickly. Building a pathtracer can be viewed as a generalization of building a raytracer, so for those of you who have taken 460/560, the basic concept should not be very new to you. For those of you that have not taken -CIS460/560, raytracing is a technique for generating images by tracing rays of -light through pixels in an image plane out into a scene and following the way -the rays of light bounce and interact with objects in the scene. More -information can be found here: -http://en.wikipedia.org/wiki/Ray_tracing_(graphics). Pathtracing is a generalization of this technique by considering more than just the contribution of direct lighting to a surface. - -Since in this class we are concerned with working in generating actual images -and less so with mundane tasks like file I/O, this project includes basecode -for loading a scene description file format, described below, and various other -things that generally make up the render "harness" that takes care of -everything up to the rendering itself. The core renderer is left for you to -implement. Finally, note that while this basecode is meant to serve as a -strong starting point for a CUDA pathtracer, you are not required to use this -basecode if you wish, and you may also change any part of the basecode -specification as you please, so long as the final rendered result is correct. - -## CONTENTS -The Project3 root directory contains the following subdirectories: - -* src/ contains the source code for the project. Both the Windows Visual Studio - solution and the OSX and Linux makefiles reference this folder for all - source; the base source code compiles on Linux, OSX and Windows without - modification. If you are building on OSX, be sure to uncomment lines 4 & 5 of - the CMakeLists.txt in order to make sure CMake builds against clang. -* data/scenes/ contains an example scene description file. -* renders/ contains an example render of the given example scene file. -* windows/ contains a Windows Visual Studio 2010 project and all dependencies - needed for building and running on Windows 7. If you would like to create a - Visual Studio 2012 or 2013 projects, there are static libraries that you can - use for GLFW that are in external/bin/GLFW (Visual Studio 2012 uses msvc110, - and Visual Studio 2013 uses msvc120) -* external/ contains all the header, static libraries and built binaries for - 3rd party libraries (i.e. glm, GLEW, GLFW) that we use for windowing and OpenGL - extensions - -## RUNNING THE CODE -The main function requires a scene description file (that is provided in data/scenes). -The main function reads in the scene file by an argument as such : -'scene=[sceneFileName]' - -If you are using Visual Studio, you can set this in the Debugging > Command Arguments section -in the Project properties. - -## REQUIREMENTS -In this project, you are given code for: - -* Loading, reading, and storing the scene scene description format -* Example functions that can run on both the CPU and GPU for generating random - numbers, spherical intersection testing, and surface point sampling on cubes -* A class for handling image operations and saving images -* Working code for CUDA-GL interop +In this project, I implemented a CUDA based pathtracer capable of +generating pathtraced rendered images extremely quickly. -You will need to implement the following features: +## Features +I implemented the following basic features: * Raycasting from a camera into a scene through a pixel grid * Diffuse surfaces @@ -69,205 +15,60 @@ You will need to implement the following features: * Sphere surface point sampling * Stream compaction optimization -You are also required to implement at least 2 of the following features: +And the following two extra features: -* Texture mapping -* Bump mapping * Depth of field * Refraction, i.e. glass -* OBJ Mesh loading and rendering -* Interactive camera -* Motion blur -* Subsurface scattering -The 'extra features' list is not comprehensive. If you have a particular feature -you would like to implement (e.g. acceleration structures, etc.) please contact us -first! +## Performance +I implemented path tracing with pixel parallelization and ray parallelization. -For each 'extra feature' you must provide the following analysis : -* overview write up of the feature -* performance impact of the feature -* if you did something to accelerate the feature, why did you do what you did -* compare your GPU version to a CPU version of this feature (you do NOT need to - implement a CPU version) -* how can this feature be further optimized (again, not necessary to implement it, but - should give a roadmap of how to further optimize and why you believe this is the next - step) +* Pixel parallelization -## BASE CODE TOUR -You will be working in three files: raytraceKernel.cu, intersections.h, and -interactions.h. Within these files, areas that you need to complete are marked -with a TODO comment. Areas that are useful to and serve as hints for optional -features are marked with TODO (Optional). Functions that are useful for -reference are marked with the comment LOOK. +In each bounce, allocate a thread for each pixel. It means there are 800*800 threads each loop in this test case. There may be many wasted cycles, some rays have been killed when hitting the light or nothing. -* raytraceKernel.cu contains the core raytracing CUDA kernel. You will need to - complete: - * cudaRaytraceCore() handles kernel launches and memory management; this - function already contains example code for launching kernels, - transferring geometry and cameras from the host to the device, and transferring - image buffers from the host to the device and back. You will have to complete - this function to support passing materials and lights to CUDA. - * raycastFromCameraKernel() is a function that you need to implement. This - function once correctly implemented should handle camera raycasting. - * raytraceRay() is the core raytracing CUDA kernel; all of your pathtracing - logic should be implemented in this CUDA kernel. raytraceRay() should - take in a camera, image buffer, geometry, materials, and lights, and should - trace a ray through the scene and write the resultant color to a pixel in the - image buffer. +* Ray parallelization -* intersections.h contains functions for geometry intersection testing and - point generation. You will need to complete: - * boxIntersectionTest(), which takes in a box and a ray and performs an - intersection test. This function should work in the same way as - sphereIntersectionTest(). - * getRandomPointOnSphere(), which takes in a sphere and returns a random - point on the surface of the sphere with an even probability distribution. - This function should work in the same way as getRandomPointOnCube(). You can - (although do not necessarily have to) use this to generate points on a sphere - to use a point lights, or can use this for area lighting. +In each bounce, allocate a thread for each ray, instead of pixel. Construct a pool of rays; and in each bounce, removed terminated rays from the pool. Here, we can use stream compaction to just keep the active rays. -* interactions.h contains functions for ray-object interactions that define how - rays behave upon hitting materials and objects. You will need to complete: - * getRandomDirectionInSphere(), which generates a random direction in a - sphere with a uniform probability. This function works in a fashion - similar to that of calculateRandomDirectionInHemisphere(), which generates a - random cosine-weighted direction in a hemisphere. - * calculateBSDF(), which takes in an incoming ray, normal, material, and - other information, and returns an outgoing ray. You can either implement - this function for ray-surface interactions, or you can replace it with your own - function(s). +The following chart shows the number of rays and timing in each bounce. Each bounce will have fewer active rays, require fewer blocks and run faster. -You will also want to familiarize yourself with: +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/table.JPG) -* sceneStructs.h, which contains definitions for how geometry, materials, - lights, cameras, and animation frames are stored in the renderer. -* utilities.h, which serves as a kitchen-sink of useful functions +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/RaysPerBounce.JPG) -## NOTES ON GLM -This project uses GLM, the GL Math library, for linear algebra. You need to -know two important points on how GLM is used in this project: +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/TimingPerBounce.JPG) -* In this project, indices in GLM vectors (such as vec3, vec4), are accessed - via swizzling. So, instead of v[0], v.x is used, and instead of v[1], v.y is - used, and so on and so forth. -* GLM Matrix operations work fine on NVIDIA Fermi cards and later, but - pre-Fermi cards do not play nice with GLM matrices. As such, in this project, - GLM matrices are replaced with a custom matrix struct, called a cudaMat4, found - in cudaMat4.h. A custom function for multiplying glm::vec4s and cudaMat4s is - provided as multiplyMV() in intersections.h. +Here is the timing comparation between pixel parallelization and ray parallelization. From the chart, the ray parallelization is of higher efficiency. -## SCENE FORMAT -This project uses a custom scene description format. -Scene files are flat text files that describe all geometry, materials, -lights, cameras, render settings, and animation frames inside of the scene. -Items in the format are delimited by new lines, and comments can be added at -the end of each line preceded with a double-slash. +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/SC.JPG) -Materials are defined in the following fashion: -* MATERIAL (material ID) //material header -* RGB (float r) (float g) (float b) //diffuse color -* SPECX (float specx) //specular exponent -* SPECRGB (float r) (float g) (float b) //specular color -* REFL (bool refl) //reflectivity flag, 0 for - no, 1 for yes -* REFR (bool refr) //refractivity flag, 0 for - no, 1 for yes -* REFRIOR (float ior) //index of refraction - for Fresnel effects -* SCATTER (float scatter) //scatter flag, 0 for - no, 1 for yes -* ABSCOEFF (float r) (float b) (float g) //absorption - coefficient for scattering -* RSCTCOEFF (float rsctcoeff) //reduced scattering - coefficient -* EMITTANCE (float emittance) //the emittance of the - material. Anything >0 makes the material a light source. +## Render Images +* Diffuse surface -Cameras are defined in the following fashion: +The following image shows diffuse surface with soft shadow. -* CAMERA //camera header -* RES (float x) (float y) //resolution -* FOVY (float fovy) //vertical field of - view half-angle. the horizonal angle is calculated from this and the - reslution -* ITERATIONS (float interations) //how many - iterations to refine the image, only relevant for supersampled antialiasing, - depth of field, area lights, and other distributed raytracing applications -* FILE (string filename) //file to output - render to upon completion -* frame (frame number) //start of a frame -* EYE (float x) (float y) (float z) //camera's position in - worldspace -* VIEW (float x) (float y) (float z) //camera's view - direction -* UP (float x) (float y) (float z) //camera's up vector +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/sample.PNG) -Objects are defined in the following fashion: -* OBJECT (object ID) //object header -* (cube OR sphere OR mesh) //type of object, can - be either "cube", "sphere", or "mesh". Note that cubes and spheres are unit - sized and centered at the origin. -* material (material ID) //material to - assign this object -* frame (frame number) //start of a frame -* TRANS (float transx) (float transy) (float transz) //translation -* ROTAT (float rotationx) (float rotationy) (float rotationz) //rotation -* SCALE (float scalex) (float scaley) (float scalez) //scale +* Frenel -An example scene file setting up two frames inside of a Cornell Box can be -found in the scenes/ directory. +The following image shows reflective and refractive surface. The left one is glass-like material with perfect refraction; and the right one is mirror-like material with perfect reflection. -For meshes, note that the base code will only read in .obj files. For more -information on the .obj specification see http://en.wikipedia.org/wiki/Wavefront_.obj_file. +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/fresnel.PNG) -An example of a mesh object is as follows: +* Depth of Field -OBJECT 0 -mesh tetra.obj -material 0 -frame 0 -TRANS 0 5 -5 -ROTAT 0 90 0 -SCALE .01 10 10 +Depth of field refers to the range of distance that appears acceptably sharp. Here're the images of different focal length and lens. -Check the Google group for some sample .obj files of varying complexity. +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/DOF14_0.5.PNG) -## THIRD PARTY CODE POLICY -* Use of any third-party code must be approved by asking on our Google Group. - If it is approved, all students are welcome to use it. Generally, we approve - use of third-party code that is not a core part of the project. For example, - for the ray tracer, we would approve using a third-party library for loading - models, but would not approve copying and pasting a CUDA function for doing - refraction. -* Third-party code must be credited in README.md. -* Using third-party code without its approval, including using another - student's code, is an academic integrity violation, and will result in you - receiving an F for the semester. +Focal length is 14 and lens is 0.5. -## SELF-GRADING -* On the submission date, email your grade, on a scale of 0 to 100, to Harmony, - harmoli+cis565@seas.upenn.com, with a one paragraph explanation. Be concise and - realistic. Recall that we reserve 30 points as a sanity check to adjust your - grade. Your actual grade will be (0.7 * your grade) + (0.3 * our grade). We - hope to only use this in extreme cases when your grade does not realistically - reflect your work - it is either too high or too low. In most cases, we plan - to give you the exact grade you suggest. -* Projects are not weighted evenly, e.g., Project 0 doesn't count as much as - the path tracer. We will determine the weighting at the end of the semester - based on the size of each project. +## Optimization +When implementing the ray-box intersection, I used the following method first. But it runs pretty slow. +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/code%20v2.PNG) -## SUBMISSION -Please change the README to reflect the answers to the questions we have posed -above. Remember: -* this is a renderer, so include images that you've made! -* be sure to back your claims for optimization with numbers and comparisons -* if you reference any other material, please provide a link to it -* you wil not e graded on how fast your path tracer runs, but getting close to - real-time is always nice -* if you have a fast GPU renderer, it is good to show case this with a video to - show interactivity. If you do so, please include a link. +Then I change to the following version two. It runs 10 times faster. In this version, I used a boolean variable to store whether the ray is intersected with the box, and only one return at the end of the function. -Be sure to open a pull request and to send Harmony your grade and why you -believe this is the grade you should get. +![ScreenShot](https://github.com/liying3/Project3-Pathtracer/blob/master/img/code%20v1.PNG) \ No newline at end of file diff --git a/data/scenes/sampleScene2.txt b/data/scenes/sampleScene2.txt new file mode 100644 index 0000000..51d4be0 --- /dev/null +++ b/data/scenes/sampleScene2.txt @@ -0,0 +1,190 @@ +MATERIAL 0 //white diffuse +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 1 //red diffuse +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 2 //green diffuse +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 3 //red glossy +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 4 //white glossy +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 5 //glass +RGB 0 0 0 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 1 +REFRIOR 2.2 +SCATTER 0 +ABSCOEFF .02 5.1 5.7 +RSCTCOEFF 13 +EMITTANCE 0 + +MATERIAL 6 //green glossy +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 1 +REFR 0 +REFRIOR 2.6 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 7 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 8 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 15 + +CAMERA +RES 800 800 +FOVY 25 +ITERATIONS 5000 +FILE test.bmp +frame 0 +EYE 0 4.5 12 +VIEW 0 0 -1 +UP 0 1 0 + +OBJECT 0 +cube +material 0 +frame 0 +TRANS 0 0 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +OBJECT 1 +cube +material 0 +frame 0 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +OBJECT 2 +cube +material 0 +frame 0 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +OBJECT 3 +cube +material 1 +frame 0 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 4 +cube +material 2 +frame 0 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 5 +sphere +material 4 +frame 0 +TRANS 0 2 0 +ROTAT 0 180 0 +SCALE 3 3 3 + +OBJECT 6 +sphere +material 5 +frame 0 +TRANS 2 5 2 +ROTAT 0 180 0 +SCALE 2.5 2.5 2.5 + +OBJECT 7 +sphere +material 6 +frame 0 +TRANS -2 5 -2 +ROTAT 0 180 0 +SCALE 3 3 3 + + +OBJECT 8 +cube +material 8 +frame 0 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .3 3 3 \ No newline at end of file diff --git a/data/texture/earthbump.bmp b/data/texture/earthbump.bmp new file mode 100644 index 0000000..38fc69c Binary files /dev/null and b/data/texture/earthbump.bmp differ diff --git a/data/texture/earthtext.bmp b/data/texture/earthtext.bmp new file mode 100644 index 0000000..dd49689 Binary files /dev/null and b/data/texture/earthtext.bmp differ diff --git a/external/include/BMP/EasyBMP.cpp b/external/include/BMP/EasyBMP.cpp new file mode 100644 index 0000000..4e2b006 --- /dev/null +++ b/external/include/BMP/EasyBMP.cpp @@ -0,0 +1,1905 @@ +/************************************************* +* * +* EasyBMP Cross-Platform Windows Bitmap Library * +* * +* Author: Paul Macklin * +* email: macklin01@users.sourceforge.net * +* support: http://easybmp.sourceforge.net * +* * +* file: EasyBMP.cpp * +* date added: 03-31-2006 * +* date modified: 12-01-2006 * +* version: 1.06 * +* * +* License: BSD (revised/modified) * +* Copyright: 2005-6 by the EasyBMP Project * +* * +* description: Actual source file * +* * +*************************************************/ + +#include "EasyBMP.h" + +/* These functions are defined in EasyBMP.h */ + +bool EasyBMPwarnings = true; + +void SetEasyBMPwarningsOff( void ) +{ EasyBMPwarnings = false; } +void SetEasyBMPwarningsOn( void ) +{ EasyBMPwarnings = true; } +bool GetEasyBMPwarningState( void ) +{ return EasyBMPwarnings; } + +/* These functions are defined in EasyBMP_DataStructures.h */ + +int IntPow( int base, int exponent ) +{ + int i; + int output = 1; + for( i=0 ; i < exponent ; i++ ) + { output *= base; } + return output; +} + +BMFH::BMFH() +{ + bfType = 19778; + bfReserved1 = 0; + bfReserved2 = 0; +} + +void BMFH::SwitchEndianess( void ) +{ + bfType = FlipWORD( bfType ); + bfSize = FlipDWORD( bfSize ); + bfReserved1 = FlipWORD( bfReserved1 ); + bfReserved2 = FlipWORD( bfReserved2 ); + bfOffBits = FlipDWORD( bfOffBits ); + return; +} + +BMIH::BMIH() +{ + biPlanes = 1; + biCompression = 0; + biXPelsPerMeter = DefaultXPelsPerMeter; + biYPelsPerMeter = DefaultYPelsPerMeter; + biClrUsed = 0; + biClrImportant = 0; +} + +void BMIH::SwitchEndianess( void ) +{ + biSize = FlipDWORD( biSize ); + biWidth = FlipDWORD( biWidth ); + biHeight = FlipDWORD( biHeight ); + biPlanes = FlipWORD( biPlanes ); + biBitCount = FlipWORD( biBitCount ); + biCompression = FlipDWORD( biCompression ); + biSizeImage = FlipDWORD( biSizeImage ); + biXPelsPerMeter = FlipDWORD( biXPelsPerMeter ); + biYPelsPerMeter = FlipDWORD( biYPelsPerMeter ); + biClrUsed = FlipDWORD( biClrUsed ); + biClrImportant = FlipDWORD( biClrImportant ); + return; +} + +void BMIH::display( void ) +{ + using namespace std; + cout << "biSize: " << (int) biSize << endl + << "biWidth: " << (int) biWidth << endl + << "biHeight: " << (int) biHeight << endl + << "biPlanes: " << (int) biPlanes << endl + << "biBitCount: " << (int) biBitCount << endl + << "biCompression: " << (int) biCompression << endl + << "biSizeImage: " << (int) biSizeImage << endl + << "biXPelsPerMeter: " << (int) biXPelsPerMeter << endl + << "biYPelsPerMeter: " << (int) biYPelsPerMeter << endl + << "biClrUsed: " << (int) biClrUsed << endl + << "biClrImportant: " << (int) biClrImportant << endl << endl; +} + +void BMFH::display( void ) +{ + using namespace std; + cout << "bfType: " << (int) bfType << endl + << "bfSize: " << (int) bfSize << endl + << "bfReserved1: " << (int) bfReserved1 << endl + << "bfReserved2: " << (int) bfReserved2 << endl + << "bfOffBits: " << (int) bfOffBits << endl << endl; +} + +/* These functions are defined in EasyBMP_BMP.h */ + +RGBApixel BMP::GetPixel( int i, int j ) const +{ + using namespace std; + bool Warn = false; + if( i >= Width ) + { i = Width-1; Warn = true; } + if( i < 0 ) + { i = 0; Warn = true; } + if( j >= Height ) + { j = Height-1; Warn = true; } + if( j < 0 ) + { j = 0; Warn = true; } + if( Warn && EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to access non-existent pixel;" << endl + << " Truncating request to fit in the range [0," + << Width-1 << "] x [0," << Height-1 << "]." << endl; + } + return Pixels[i][j]; +} + +bool BMP::SetPixel( int i, int j, RGBApixel NewPixel ) +{ + Pixels[i][j] = NewPixel; + return true; +} + + +bool BMP::SetColor( int ColorNumber , RGBApixel NewColor ) +{ + using namespace std; + if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to change color table for a BMP object" << endl + << " that lacks a color table. Ignoring request." << endl; + } + return false; + } + if( !Colors ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to set a color, but the color table" << endl + << " is not defined. Ignoring request." << endl; + } + return false; + } + if( ColorNumber >= TellNumberOfColors() ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Requested color number " + << ColorNumber << " is outside the allowed" << endl + << " range [0," << TellNumberOfColors()-1 + << "]. Ignoring request to set this color." << endl; + } + return false; + } + Colors[ColorNumber] = NewColor; + return true; +} + +// RGBApixel BMP::GetColor( int ColorNumber ) const +RGBApixel BMP::GetColor( int ColorNumber ) +{ + RGBApixel Output; + Output.Red = 255; + Output.Green = 255; + Output.Blue = 255; + Output.Alpha = 0; + + using namespace std; + if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to access color table for a BMP object" << endl + << " that lacks a color table. Ignoring request." << endl; + } + return Output; + } + if( !Colors ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Requested a color, but the color table" << endl + << " is not defined. Ignoring request." << endl; + } + return Output; + } + if( ColorNumber >= TellNumberOfColors() ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Requested color number " + << ColorNumber << " is outside the allowed" << endl + << " range [0," << TellNumberOfColors()-1 + << "]. Ignoring request to get this color." << endl; + } + return Output; + } + Output = Colors[ColorNumber]; + return Output; +} + +BMP::BMP() +{ + Width = 1; + Height = 1; + BitDepth = 24; + Pixels = new RGBApixel* [Width]; + Pixels[0] = new RGBApixel [Height]; + Colors = NULL; + + XPelsPerMeter = 0; + YPelsPerMeter = 0; + + MetaData1 = NULL; + SizeOfMetaData1 = 0; + MetaData2 = NULL; + SizeOfMetaData2 = 0; +} + +// BMP::BMP( const BMP& Input ) +BMP::BMP( BMP& Input ) +{ + // first, make the image empty. + + Width = 1; + Height = 1; + BitDepth = 24; + Pixels = new RGBApixel* [Width]; + Pixels[0] = new RGBApixel [Height]; + Colors = NULL; + XPelsPerMeter = 0; + YPelsPerMeter = 0; + + MetaData1 = NULL; + SizeOfMetaData1 = 0; + MetaData2 = NULL; + SizeOfMetaData2 = 0; + + // now, set the correct bit depth + + SetBitDepth( Input.TellBitDepth() ); + + // set the correct pixel size + + SetSize( Input.TellWidth() , Input.TellHeight() ); + + // set the DPI information from Input + + SetDPI( Input.TellHorizontalDPI() , Input.TellVerticalDPI() ); + + // if there is a color table, get all the colors + + if( BitDepth == 1 || BitDepth == 4 || + BitDepth == 8 ) + { + for( int k=0 ; k < TellNumberOfColors() ; k++ ) + { + SetColor( k, Input.GetColor( k )); + } + } + + // get all the pixels + + for( int j=0; j < Height ; j++ ) + { + for( int i=0; i < Width ; i++ ) + { + Pixels[i][j] = *Input(i,j); +// Pixels[i][j] = Input.GetPixel(i,j); // *Input(i,j); + } + } +} + +BMP::~BMP() +{ + int i; + for(i=0;i= Width ) + { i = Width-1; Warn = true; } + if( i < 0 ) + { i = 0; Warn = true; } + if( j >= Height ) + { j = Height-1; Warn = true; } + if( j < 0 ) + { j = 0; Warn = true; } + if( Warn && EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to access non-existent pixel;" << endl + << " Truncating request to fit in the range [0," + << Width-1 << "] x [0," << Height-1 << "]." << endl; + } + return &(Pixels[i][j]); +} + +// int BMP::TellBitDepth( void ) const +int BMP::TellBitDepth( void ) +{ return BitDepth; } + +// int BMP::TellHeight( void ) const +int BMP::TellHeight( void ) +{ return Height; } + +// int BMP::TellWidth( void ) const +int BMP::TellWidth( void ) +{ return Width; } + +// int BMP::TellNumberOfColors( void ) const +int BMP::TellNumberOfColors( void ) +{ + int output = IntPow( 2, BitDepth ); + if( BitDepth == 32 ) + { output = IntPow( 2, 24 ); } + return output; +} + +bool BMP::SetBitDepth( int NewDepth ) +{ + using namespace std; + if( NewDepth != 1 && NewDepth != 4 && + NewDepth != 8 && NewDepth != 16 && + NewDepth != 24 && NewDepth != 32 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: User attempted to set unsupported bit depth " + << NewDepth << "." << endl + << " Bit depth remains unchanged at " + << BitDepth << "." << endl; + } + return false; + } + + BitDepth = NewDepth; + if( Colors ) + { delete [] Colors; } + int NumberOfColors = IntPow( 2, BitDepth ); + if( BitDepth == 1 || BitDepth == 4 || BitDepth == 8 ) + { Colors = new RGBApixel [NumberOfColors]; } + else + { Colors = NULL; } + if( BitDepth == 1 || BitDepth == 4 || BitDepth == 8 ) + { CreateStandardColorTable(); } + + return true; +} + +bool BMP::SetSize(int NewWidth , int NewHeight ) +{ + using namespace std; + if( NewWidth <= 0 || NewHeight <= 0 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: User attempted to set a non-positive width or height." << endl + << " Size remains unchanged at " + << Width << " x " << Height << "." << endl; + } + return false; + } + + int i,j; + + for(i=0;i -1 ) + { + bool Success = false; + if( BitDepth == 32 ) + { Success = Write32bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 24 ) + { Success = Write24bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 8 ) + { Success = Write8bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 4 ) + { Success = Write4bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 1 ) + { Success = Write1bitRow( Buffer, BufferSize, j ); } + if( Success ) + { + int BytesWritten = (int) fwrite( (char*) Buffer, 1, BufferSize, fp ); + if( BytesWritten != BufferSize ) + { Success = false; } + } + if( !Success ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Could not write proper amount of data." << endl; + } + j = -1; + } + j--; + } + + delete [] Buffer; + } + + if( BitDepth == 16 ) + { + // write the bit masks + + ebmpWORD BlueMask = 31; // bits 12-16 + ebmpWORD GreenMask = 2016; // bits 6-11 + ebmpWORD RedMask = 63488; // bits 1-5 + ebmpWORD ZeroWORD; + + if( IsBigEndian() ) + { RedMask = FlipWORD( RedMask ); } + fwrite( (char*) &RedMask , 2 , 1 , fp ); + fwrite( (char*) &ZeroWORD , 2 , 1 , fp ); + + if( IsBigEndian() ) + { GreenMask = FlipWORD( GreenMask ); } + fwrite( (char*) &GreenMask , 2 , 1 , fp ); + fwrite( (char*) &ZeroWORD , 2 , 1 , fp ); + + if( IsBigEndian() ) + { BlueMask = FlipWORD( BlueMask ); } + fwrite( (char*) &BlueMask , 2 , 1 , fp ); + fwrite( (char*) &ZeroWORD , 2 , 1 , fp ); + + int DataBytes = Width*2; + int PaddingBytes = ( 4 - DataBytes % 4 ) % 4; + + // write the actual pixels + + for( j=Height-1 ; j >= 0 ; j-- ) + { + // write all row pixel data + i=0; + int WriteNumber = 0; + while( WriteNumber < DataBytes ) + { + ebmpWORD TempWORD; + + ebmpWORD RedWORD = (ebmpWORD) ((Pixels[i][j]).Red / 8); + ebmpWORD GreenWORD = (ebmpWORD) ((Pixels[i][j]).Green / 4); + ebmpWORD BlueWORD = (ebmpWORD) ((Pixels[i][j]).Blue / 8); + + TempWORD = (RedWORD<<11) + (GreenWORD<<5) + BlueWORD; + if( IsBigEndian() ) + { TempWORD = FlipWORD( TempWORD ); } + + fwrite( (char*) &TempWORD , 2, 1, fp); + WriteNumber += 2; + i++; + } + // write any necessary row padding + WriteNumber = 0; + while( WriteNumber < PaddingBytes ) + { + ebmpBYTE TempBYTE; + fwrite( (char*) &TempBYTE , 1, 1, fp); + WriteNumber++; + } + } + + } + + fclose(fp); + return true; +} + +bool BMP::ReadFromFile( const char* FileName ) +{ + using namespace std; + if( !EasyBMPcheckDataSize() ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Data types are wrong size!" << endl + << " You may need to mess with EasyBMP_DataTypes.h" << endl + << " to fix these errors, and then recompile." << endl + << " All 32-bit and 64-bit machines should be" << endl + << " supported, however." << endl << endl; + } + return false; + } + + FILE* fp = fopen( FileName, "rb" ); + if( fp == NULL ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Cannot open file " + << FileName << " for input." << endl; + } + SetBitDepth(1); + SetSize(1,1); + return false; + } + + // read the file header + + BMFH bmfh; + bool NotCorrupted = true; + + NotCorrupted &= SafeFread( (char*) &(bmfh.bfType) , sizeof(ebmpWORD), 1, fp); + + bool IsBitmap = false; + + if( IsBigEndian() && bmfh.bfType == 16973 ) + { IsBitmap = true; } + if( !IsBigEndian() && bmfh.bfType == 19778 ) + { IsBitmap = true; } + + if( !IsBitmap ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName + << " is not a Windows BMP file!" << endl; + } + fclose( fp ); + return false; + } + + NotCorrupted &= SafeFread( (char*) &(bmfh.bfSize) , sizeof(ebmpDWORD) , 1, fp); + NotCorrupted &= SafeFread( (char*) &(bmfh.bfReserved1) , sizeof(ebmpWORD) , 1, fp); + NotCorrupted &= SafeFread( (char*) &(bmfh.bfReserved2) , sizeof(ebmpWORD) , 1, fp); + NotCorrupted &= SafeFread( (char*) &(bmfh.bfOffBits) , sizeof(ebmpDWORD) , 1 , fp); + + if( IsBigEndian() ) + { bmfh.SwitchEndianess(); } + + // read the info header + + BMIH bmih; + + NotCorrupted &= SafeFread( (char*) &(bmih.biSize) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biWidth) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biHeight) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biPlanes) , sizeof(ebmpWORD) , 1, fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biBitCount) , sizeof(ebmpWORD) , 1, fp); + + NotCorrupted &= SafeFread( (char*) &(bmih.biCompression) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biSizeImage) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biXPelsPerMeter) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biYPelsPerMeter) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biClrUsed) , sizeof(ebmpDWORD) , 1 , fp); + NotCorrupted &= SafeFread( (char*) &(bmih.biClrImportant) , sizeof(ebmpDWORD) , 1 , fp); + + if( IsBigEndian() ) + { bmih.SwitchEndianess(); } + + // a safety catch: if any of the header information didn't read properly, abort + // future idea: check to see if at least most is self-consistent + + if( !NotCorrupted ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName + << " is obviously corrupted." << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + + XPelsPerMeter = bmih.biXPelsPerMeter; + YPelsPerMeter = bmih.biYPelsPerMeter; + + // if bmih.biCompression 1 or 2, then the file is RLE compressed + + if( bmih.biCompression == 1 || bmih.biCompression == 2 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName << " is (RLE) compressed." << endl + << " EasyBMP does not support compression." << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + + // if bmih.biCompression > 3, then something strange is going on + // it's probably an OS2 bitmap file. + + if( bmih.biCompression > 3 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName << " is in an unsupported format." + << endl + << " (bmih.biCompression = " + << bmih.biCompression << ")" << endl + << " The file is probably an old OS2 bitmap or corrupted." + << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + + if( bmih.biCompression == 3 && bmih.biBitCount != 16 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName + << " uses bit fields and is not a" << endl + << " 16-bit file. This is not supported." << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + + // set the bit depth + + int TempBitDepth = (int) bmih.biBitCount; + if( TempBitDepth != 1 && TempBitDepth != 4 + && TempBitDepth != 8 && TempBitDepth != 16 + && TempBitDepth != 24 && TempBitDepth != 32 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName << " has unrecognized bit depth." << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + SetBitDepth( (int) bmih.biBitCount ); + + // set the size + + if( (int) bmih.biWidth <= 0 || (int) bmih.biHeight <= 0 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: " << FileName + << " has a non-positive width or height." << endl; + } + SetSize(1,1); + SetBitDepth(1); + fclose(fp); + return false; + } + SetSize( (int) bmih.biWidth , (int) bmih.biHeight ); + + // some preliminaries + + double dBytesPerPixel = ( (double) BitDepth ) / 8.0; + double dBytesPerRow = dBytesPerPixel * (Width+0.0); + dBytesPerRow = ceil(dBytesPerRow); + + int BytePaddingPerRow = 4 - ( (int) (dBytesPerRow) )% 4; + if( BytePaddingPerRow == 4 ) + { BytePaddingPerRow = 0; } + + // if < 16 bits, read the palette + + if( BitDepth < 16 ) + { + // determine the number of colors specified in the + // color table + + int NumberOfColorsToRead = ((int) bmfh.bfOffBits - 54 )/4; + if( NumberOfColorsToRead > IntPow(2,BitDepth) ) + { NumberOfColorsToRead = IntPow(2,BitDepth); } + + if( NumberOfColorsToRead < TellNumberOfColors() ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: file " << FileName << " has an underspecified" << endl + << " color table. The table will be padded with extra" << endl + << " white (255,255,255,0) entries." << endl; + } + } + + int n; + for( n=0; n < NumberOfColorsToRead ; n++ ) + { + SafeFread( (char*) &(Colors[n]) , 4 , 1 , fp); + } + for( n=NumberOfColorsToRead ; n < TellNumberOfColors() ; n++ ) + { + RGBApixel WHITE; + WHITE.Red = 255; + WHITE.Green = 255; + WHITE.Blue = 255; + WHITE.Alpha = 0; + SetColor( n , WHITE ); + } + + + } + + // skip blank data if bfOffBits so indicates + + int BytesToSkip = bmfh.bfOffBits - 54;; + if( BitDepth < 16 ) + { BytesToSkip -= 4*IntPow(2,BitDepth); } + if( BitDepth == 16 && bmih.biCompression == 3 ) + { BytesToSkip -= 3*4; } + if( BytesToSkip < 0 ) + { BytesToSkip = 0; } + if( BytesToSkip > 0 && BitDepth != 16 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Extra meta data detected in file " << FileName << endl + << " Data will be skipped." << endl; + } + ebmpBYTE* TempSkipBYTE; + TempSkipBYTE = new ebmpBYTE [BytesToSkip]; + SafeFread( (char*) TempSkipBYTE , BytesToSkip , 1 , fp); + delete [] TempSkipBYTE; + } + + // This code reads 1, 4, 8, 24, and 32-bpp files + // with a more-efficient buffered technique. + + int i,j; + if( BitDepth != 16 ) + { + int BufferSize = (int) ( (Width*BitDepth) / 8.0 ); + while( 8*BufferSize < Width*BitDepth ) + { BufferSize++; } + while( BufferSize % 4 ) + { BufferSize++; } + ebmpBYTE* Buffer; + Buffer = new ebmpBYTE [BufferSize]; + j= Height-1; + while( j > -1 ) + { + int BytesRead = (int) fread( (char*) Buffer, 1, BufferSize, fp ); + if( BytesRead < BufferSize ) + { + j = -1; + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Could not read proper amount of data." << endl; + } + } + else + { + bool Success = false; + if( BitDepth == 1 ) + { Success = Read1bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 4 ) + { Success = Read4bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 8 ) + { Success = Read8bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 24 ) + { Success = Read24bitRow( Buffer, BufferSize, j ); } + if( BitDepth == 32 ) + { Success = Read32bitRow( Buffer, BufferSize, j ); } + if( !Success ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Could not read enough pixel data!" << endl; + } + j = -1; + } + } + j--; + } + delete [] Buffer; + } + + if( BitDepth == 16 ) + { + int DataBytes = Width*2; + int PaddingBytes = ( 4 - DataBytes % 4 ) % 4; + + // set the default mask + + ebmpWORD BlueMask = 31; // bits 12-16 + ebmpWORD GreenMask = 992; // bits 7-11 + ebmpWORD RedMask = 31744; // bits 2-6 + + // read the bit fields, if necessary, to + // override the default 5-5-5 mask + + if( bmih.biCompression != 0 ) + { + // read the three bit masks + + ebmpWORD TempMaskWORD; + ebmpWORD ZeroWORD; + + SafeFread( (char*) &RedMask , 2 , 1 , fp ); + if( IsBigEndian() ) + { RedMask = FlipWORD(RedMask); } + SafeFread( (char*) &TempMaskWORD , 2, 1, fp ); + + SafeFread( (char*) &GreenMask , 2 , 1 , fp ); + if( IsBigEndian() ) + { GreenMask = FlipWORD(GreenMask); } + SafeFread( (char*) &TempMaskWORD , 2, 1, fp ); + + SafeFread( (char*) &BlueMask , 2 , 1 , fp ); + if( IsBigEndian() ) + { BlueMask = FlipWORD(BlueMask); } + SafeFread( (char*) &TempMaskWORD , 2, 1, fp ); + } + + // read and skip any meta data + + if( BytesToSkip > 0 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Extra meta data detected in file " + << FileName << endl + << " Data will be skipped." << endl; + } + ebmpBYTE* TempSkipBYTE; + TempSkipBYTE = new ebmpBYTE [BytesToSkip]; + SafeFread( (char*) TempSkipBYTE , BytesToSkip , 1 , fp); + delete [] TempSkipBYTE; + } + + // determine the red, green and blue shifts + + int GreenShift = 0; + ebmpWORD TempShiftWORD = GreenMask; + while( TempShiftWORD > 31 ) + { TempShiftWORD = TempShiftWORD>>1; GreenShift++; } + int BlueShift = 0; + TempShiftWORD = BlueMask; + while( TempShiftWORD > 31 ) + { TempShiftWORD = TempShiftWORD>>1; BlueShift++; } + int RedShift = 0; + TempShiftWORD = RedMask; + while( TempShiftWORD > 31 ) + { TempShiftWORD = TempShiftWORD>>1; RedShift++; } + + // read the actual pixels + + for( j=Height-1 ; j >= 0 ; j-- ) + { + i=0; + int ReadNumber = 0; + while( ReadNumber < DataBytes ) + { + ebmpWORD TempWORD; + SafeFread( (char*) &TempWORD , 2 , 1 , fp ); + if( IsBigEndian() ) + { TempWORD = FlipWORD(TempWORD); } + ReadNumber += 2; + + ebmpWORD Red = RedMask & TempWORD; + ebmpWORD Green = GreenMask & TempWORD; + ebmpWORD Blue = BlueMask & TempWORD; + + ebmpBYTE BlueBYTE = (ebmpBYTE) 8*(Blue>>BlueShift); + ebmpBYTE GreenBYTE = (ebmpBYTE) 8*(Green>>GreenShift); + ebmpBYTE RedBYTE = (ebmpBYTE) 8*(Red>>RedShift); + + (Pixels[i][j]).Red = RedBYTE; + (Pixels[i][j]).Green = GreenBYTE; + (Pixels[i][j]).Blue = BlueBYTE; + + i++; + } + ReadNumber = 0; + while( ReadNumber < PaddingBytes ) + { + ebmpBYTE TempBYTE; + SafeFread( (char*) &TempBYTE , 1, 1, fp); + ReadNumber++; + } + } + + } + + fclose(fp); + return true; +} + +bool BMP::CreateStandardColorTable( void ) +{ + using namespace std; + if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to create color table at a bit" << endl + << " depth that does not require a color table." << endl + << " Ignoring request." << endl; + } + return false; + } + + if( BitDepth == 1 ) + { + int i; + for( i=0 ; i < 2 ; i++ ) + { + Colors[i].Red = i*255; + Colors[i].Green = i*255; + Colors[i].Blue = i*255; + Colors[i].Alpha = 0; + } + return true; + } + + if( BitDepth == 4 ) + { + int i = 0; + int j,k,ell; + + // simplify the code for the first 8 colors + for( ell=0 ; ell < 2 ; ell++ ) + { + for( k=0 ; k < 2 ; k++ ) + { + for( j=0 ; j < 2 ; j++ ) + { + Colors[i].Red = j*128; + Colors[i].Green = k*128; + Colors[i].Blue = ell*128; + i++; + } + } + } + + // simplify the code for the last 8 colors + for( ell=0 ; ell < 2 ; ell++ ) + { + for( k=0 ; k < 2 ; k++ ) + { + for( j=0 ; j < 2 ; j++ ) + { + Colors[i].Red = j*255; + Colors[i].Green = k*255; + Colors[i].Blue = ell*255; + i++; + } + } + } + + // overwrite the duplicate color + i=8; + Colors[i].Red = 192; + Colors[i].Green = 192; + Colors[i].Blue = 192; + + for( i=0 ; i < 16 ; i++ ) + { Colors[i].Alpha = 0; } + return true; + } + + if( BitDepth == 8 ) + { + int i=0; + int j,k,ell; + + // do an easy loop, which works for all but colors + // 0 to 9 and 246 to 255 + for( ell=0 ; ell < 4 ; ell++ ) + { + for( k=0 ; k < 8 ; k++ ) + { + for( j=0; j < 8 ; j++ ) + { + Colors[i].Red = j*32; + Colors[i].Green = k*32; + Colors[i].Blue = ell*64; + Colors[i].Alpha = 0; + i++; + } + } + } + + // now redo the first 8 colors + i=0; + for( ell=0 ; ell < 2 ; ell++ ) + { + for( k=0 ; k < 2 ; k++ ) + { + for( j=0; j < 2 ; j++ ) + { + Colors[i].Red = j*128; + Colors[i].Green = k*128; + Colors[i].Blue = ell*128; + i++; + } + } + } + + // overwrite colors 7, 8, 9 + i=7; + Colors[i].Red = 192; + Colors[i].Green = 192; + Colors[i].Blue = 192; + i++; // 8 + Colors[i].Red = 192; + Colors[i].Green = 220; + Colors[i].Blue = 192; + i++; // 9 + Colors[i].Red = 166; + Colors[i].Green = 202; + Colors[i].Blue = 240; + + // overwrite colors 246 to 255 + i=246; + Colors[i].Red = 255; + Colors[i].Green = 251; + Colors[i].Blue = 240; + i++; // 247 + Colors[i].Red = 160; + Colors[i].Green = 160; + Colors[i].Blue = 164; + i++; // 248 + Colors[i].Red = 128; + Colors[i].Green = 128; + Colors[i].Blue = 128; + i++; // 249 + Colors[i].Red = 255; + Colors[i].Green = 0; + Colors[i].Blue = 0; + i++; // 250 + Colors[i].Red = 0; + Colors[i].Green = 255; + Colors[i].Blue = 0; + i++; // 251 + Colors[i].Red = 255; + Colors[i].Green = 255; + Colors[i].Blue = 0; + i++; // 252 + Colors[i].Red = 0; + Colors[i].Green = 0; + Colors[i].Blue = 255; + i++; // 253 + Colors[i].Red = 255; + Colors[i].Green = 0; + Colors[i].Blue = 255; + i++; // 254 + Colors[i].Red = 0; + Colors[i].Green = 255; + Colors[i].Blue = 255; + i++; // 255 + Colors[i].Red = 255; + Colors[i].Green = 255; + Colors[i].Blue = 255; + + return true; + } + return true; +} + +bool SafeFread( char* buffer, int size, int number, FILE* fp ) +{ + using namespace std; + int ItemsRead; + if( feof(fp) ) + { return false; } + ItemsRead = (int) fread( buffer , size , number , fp ); + if( ItemsRead < number ) + { return false; } + return true; +} + +void BMP::SetDPI( int HorizontalDPI, int VerticalDPI ) +{ + XPelsPerMeter = (int) ( HorizontalDPI * 39.37007874015748 ); + YPelsPerMeter = (int) ( VerticalDPI * 39.37007874015748 ); +} + +// int BMP::TellVerticalDPI( void ) const +int BMP::TellVerticalDPI( void ) +{ + if( !YPelsPerMeter ) + { YPelsPerMeter = DefaultYPelsPerMeter; } + return (int) ( YPelsPerMeter / (double) 39.37007874015748 ); +} + +// int BMP::TellHorizontalDPI( void ) const +int BMP::TellHorizontalDPI( void ) +{ + if( !XPelsPerMeter ) + { XPelsPerMeter = DefaultXPelsPerMeter; } + return (int) ( XPelsPerMeter / (double) 39.37007874015748 ); +} + +/* These functions are defined in EasyBMP_VariousBMPutilities.h */ + +BMFH GetBMFH( const char* szFileNameIn ) +{ + using namespace std; + BMFH bmfh; + + FILE* fp; + fp = fopen( szFileNameIn,"rb"); + + if( !fp ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Cannot initialize from file " + << szFileNameIn << "." << endl + << " File cannot be opened or does not exist." + << endl; + } + bmfh.bfType = 0; + return bmfh; + } + + SafeFread( (char*) &(bmfh.bfType) , sizeof(ebmpWORD) , 1 , fp ); + SafeFread( (char*) &(bmfh.bfSize) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmfh.bfReserved1) , sizeof(ebmpWORD) , 1 , fp ); + SafeFread( (char*) &(bmfh.bfReserved2) , sizeof(ebmpWORD) , 1 , fp ); + SafeFread( (char*) &(bmfh.bfOffBits) , sizeof(ebmpDWORD) , 1 , fp ); + + fclose( fp ); + + if( IsBigEndian() ) + { bmfh.SwitchEndianess(); } + + return bmfh; +} + +BMIH GetBMIH( const char* szFileNameIn ) +{ + using namespace std; + BMFH bmfh; + BMIH bmih; + + FILE* fp; + fp = fopen( szFileNameIn,"rb"); + + if( !fp ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Cannot initialize from file " + << szFileNameIn << "." << endl + << " File cannot be opened or does not exist." + << endl; + } + return bmih; + } + + // read the bmfh, i.e., first 14 bytes (just to get it out of the way); + + ebmpBYTE TempBYTE; + int i; + for( i = 14 ; i > 0 ; i-- ) + { SafeFread( (char*) &TempBYTE , sizeof(ebmpBYTE) , 1, fp ); } + + // read the bmih + + SafeFread( (char*) &(bmih.biSize) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biWidth) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biHeight) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biPlanes) , sizeof(ebmpWORD) , 1 , fp ); + + SafeFread( (char*) &(bmih.biBitCount) , sizeof(ebmpWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biCompression) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biSizeImage) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biXPelsPerMeter) , sizeof(ebmpDWORD) , 1 , fp ); + + SafeFread( (char*) &(bmih.biYPelsPerMeter) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biClrUsed) , sizeof(ebmpDWORD) , 1 , fp ); + SafeFread( (char*) &(bmih.biClrImportant) , sizeof(ebmpDWORD) , 1 , fp ); + + fclose( fp ); + + if( IsBigEndian() ) + { bmih.SwitchEndianess(); } + + return bmih; +} + +void DisplayBitmapInfo( const char* szFileNameIn ) +{ + using namespace std; + FILE* fp; + fp = fopen( szFileNameIn,"rb"); + + if( !fp ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: Cannot initialize from file " + << szFileNameIn << "." << endl + << " File cannot be opened or does not exist." + << endl; + } + return; + } + fclose( fp ); + + // don't duplicate work! Just use the functions from above! + + BMFH bmfh = GetBMFH(szFileNameIn); + BMIH bmih = GetBMIH(szFileNameIn); + + cout << "File information for file " << szFileNameIn + << ":" << endl << endl; + + cout << "BITMAPFILEHEADER:" << endl + << "bfType: " << bmfh.bfType << endl + << "bfSize: " << bmfh.bfSize << endl + << "bfReserved1: " << bmfh.bfReserved1 << endl + << "bfReserved2: " << bmfh.bfReserved2 << endl + << "bfOffBits: " << bmfh.bfOffBits << endl << endl; + + cout << "BITMAPINFOHEADER:" << endl + << "biSize: " << bmih.biSize << endl + << "biWidth: " << bmih.biWidth << endl + << "biHeight: " << bmih.biHeight << endl + << "biPlanes: " << bmih.biPlanes << endl + << "biBitCount: " << bmih.biBitCount << endl + << "biCompression: " << bmih.biCompression << endl + << "biSizeImage: " << bmih.biSizeImage << endl + << "biXPelsPerMeter: " << bmih.biXPelsPerMeter << endl + << "biYPelsPerMeter: " << bmih.biYPelsPerMeter << endl + << "biClrUsed: " << bmih.biClrUsed << endl + << "biClrImportant: " << bmih.biClrImportant << endl << endl; + return; +} + +int GetBitmapColorDepth( const char* szFileNameIn ) +{ + BMIH bmih = GetBMIH( szFileNameIn ); + return (int) bmih.biBitCount; +} + +void PixelToPixelCopy( BMP& From, int FromX, int FromY, + BMP& To, int ToX, int ToY) +{ + *To(ToX,ToY) = *From(FromX,FromY); + return; +} + +void PixelToPixelCopyTransparent( BMP& From, int FromX, int FromY, + BMP& To, int ToX, int ToY, + RGBApixel& Transparent ) +{ + if( From(FromX,FromY)->Red != Transparent.Red || + From(FromX,FromY)->Green != Transparent.Green || + From(FromX,FromY)->Blue != Transparent.Blue ) + { *To(ToX,ToY) = *From(FromX,FromY); } + return; +} + +void RangedPixelToPixelCopy( BMP& From, int FromL , int FromR, int FromB, int FromT, + BMP& To, int ToX, int ToY ) +{ + // make sure the conventions are followed + if( FromB < FromT ) + { int Temp = FromT; FromT = FromB; FromB = Temp; } + + // make sure that the copied regions exist in both bitmaps + if( FromR >= From.TellWidth() ) + { FromR = From.TellWidth()-1; } + if( FromL < 0 ){ FromL = 0; } + + if( FromB >= From.TellHeight() ) + { FromB = From.TellHeight()-1; } + if( FromT < 0 ){ FromT = 0; } + + if( ToX+(FromR-FromL) >= To.TellWidth() ) + { FromR = To.TellWidth()-1+FromL-ToX; } + if( ToY+(FromB-FromT) >= To.TellHeight() ) + { FromB = To.TellHeight()-1+FromT-ToY; } + + int i,j; + for( j=FromT ; j <= FromB ; j++ ) + { + for( i=FromL ; i <= FromR ; i++ ) + { + PixelToPixelCopy( From, i,j, + To, ToX+(i-FromL), ToY+(j-FromT) ); + } + } + + return; +} + +void RangedPixelToPixelCopyTransparent( + BMP& From, int FromL , int FromR, int FromB, int FromT, + BMP& To, int ToX, int ToY , + RGBApixel& Transparent ) +{ + // make sure the conventions are followed + if( FromB < FromT ) + { int Temp = FromT; FromT = FromB; FromB = Temp; } + + // make sure that the copied regions exist in both bitmaps + if( FromR >= From.TellWidth() ) + { FromR = From.TellWidth()-1; } + if( FromL < 0 ){ FromL = 0; } + + if( FromB >= From.TellHeight() ) + { FromB = From.TellHeight()-1; } + if( FromT < 0 ){ FromT = 0; } + + if( ToX+(FromR-FromL) >= To.TellWidth() ) + { FromR = To.TellWidth()-1+FromL-ToX; } + if( ToY+(FromB-FromT) >= To.TellHeight() ) + { FromB = To.TellHeight()-1+FromT-ToY; } + + int i,j; + for( j=FromT ; j <= FromB ; j++ ) + { + for( i=FromL ; i <= FromR ; i++ ) + { + PixelToPixelCopyTransparent( From, i,j, + To, ToX+(i-FromL), ToY+(j-FromT) , + Transparent); + } + } + + return; +} + +bool CreateGrayscaleColorTable( BMP& InputImage ) +{ + using namespace std; + int BitDepth = InputImage.TellBitDepth(); + if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Warning: Attempted to create color table at a bit" << endl + << " depth that does not require a color table." << endl + << " Ignoring request." << endl; + } + return false; + } + int i; + int NumberOfColors = InputImage.TellNumberOfColors(); + + ebmpBYTE StepSize; + if( BitDepth != 1 ) + { StepSize = 255/(NumberOfColors-1); } + else + { StepSize = 255; } + + for( i=0 ; i < NumberOfColors ; i++ ) + { + ebmpBYTE TempBYTE = i*StepSize; + RGBApixel TempColor; + TempColor.Red = TempBYTE; + TempColor.Green = TempBYTE; + TempColor.Blue = TempBYTE; + TempColor.Alpha = 0; + InputImage.SetColor( i , TempColor ); + } + return true; +} + +bool BMP::Read32bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width*4 > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { memcpy( (char*) &(Pixels[i][Row]), (char*) Buffer+4*i, 4 ); } + return true; +} + +bool BMP::Read24bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width*3 > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { memcpy( (char*) &(Pixels[i][Row]), Buffer+3*i, 3 ); } + return true; +} + +bool BMP::Read8bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { + int Index = Buffer[i]; + *( this->operator()(i,Row) )= GetColor(Index); + } + return true; +} + +bool BMP::Read4bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int Shifts[2] = {4 ,0 }; + int Masks[2] = {240,15}; + + int i=0; + int j; + int k=0; + if( Width > 2*BufferSize ) + { return false; } + while( i < Width ) + { + j=0; + while( j < 2 && i < Width ) + { + int Index = (int) ( (Buffer[k]&Masks[j]) >> Shifts[j]); + *( this->operator()(i,Row) )= GetColor(Index); + i++; j++; + } + k++; + } + return true; +} +bool BMP::Read1bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int Shifts[8] = {7 ,6 ,5 ,4 ,3,2,1,0}; + int Masks[8] = {128,64,32,16,8,4,2,1}; + + int i=0; + int j; + int k=0; + + if( Width > 8*BufferSize ) + { return false; } + while( i < Width ) + { + j=0; + while( j < 8 && i < Width ) + { + int Index = (int) ( (Buffer[k]&Masks[j]) >> Shifts[j]); + *( this->operator()(i,Row) )= GetColor(Index); + i++; j++; + } + k++; + } + return true; +} + +bool BMP::Write32bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width*4 > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { memcpy( (char*) Buffer+4*i, (char*) &(Pixels[i][Row]), 4 ); } + return true; +} + +bool BMP::Write24bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width*3 > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { memcpy( (char*) Buffer+3*i, (char*) &(Pixels[i][Row]), 3 ); } + return true; +} + +bool BMP::Write8bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int i; + if( Width > BufferSize ) + { return false; } + for( i=0 ; i < Width ; i++ ) + { Buffer[i] = FindClosestColor( Pixels[i][Row] ); } + return true; +} + +bool BMP::Write4bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int PositionWeights[2] = {16,1}; + + int i=0; + int j; + int k=0; + if( Width > 2*BufferSize ) + { return false; } + while( i < Width ) + { + j=0; + int Index = 0; + while( j < 2 && i < Width ) + { + Index += ( PositionWeights[j]* (int) FindClosestColor( Pixels[i][Row] ) ); + i++; j++; + } + Buffer[k] = (ebmpBYTE) Index; + k++; + } + return true; +} + +bool BMP::Write1bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ) +{ + int PositionWeights[8] = {128,64,32,16,8,4,2,1}; + + int i=0; + int j; + int k=0; + if( Width > 8*BufferSize ) + { return false; } + while( i < Width ) + { + j=0; + int Index = 0; + while( j < 8 && i < Width ) + { + Index += ( PositionWeights[j]* (int) FindClosestColor( Pixels[i][Row] ) ); + i++; j++; + } + Buffer[k] = (ebmpBYTE) Index; + k++; + } + return true; +} + +ebmpBYTE BMP::FindClosestColor( RGBApixel& input ) +{ + using namespace std; + + int i=0; + int NumberOfColors = TellNumberOfColors(); + ebmpBYTE BestI = 0; + int BestMatch = 999999; + + while( i < NumberOfColors ) + { + RGBApixel Attempt = GetColor( i ); + int TempMatch = IntSquare( (int) Attempt.Red - (int) input.Red ) + + IntSquare( (int) Attempt.Green - (int) input.Green ) + + IntSquare( (int) Attempt.Blue - (int) input.Blue ); + if( TempMatch < BestMatch ) + { BestI = (ebmpBYTE) i; BestMatch = TempMatch; } + if( BestMatch < 1 ) + { i = NumberOfColors; } + i++; + } + return BestI; +} + +bool EasyBMPcheckDataSize( void ) +{ + using namespace std; + bool ReturnValue = true; + if( sizeof( ebmpBYTE ) != 1 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: ebmpBYTE has the wrong size (" + << sizeof( ebmpBYTE ) << " bytes)," << endl + << " Compared to the expected 1 byte value" << endl; + } + ReturnValue = false; + } + if( sizeof( ebmpWORD ) != 2 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: ebmpWORD has the wrong size (" + << sizeof( ebmpWORD ) << " bytes)," << endl + << " Compared to the expected 2 byte value" << endl; + } + ReturnValue = false; + } + if( sizeof( ebmpDWORD ) != 4 ) + { + if( EasyBMPwarnings ) + { + cout << "EasyBMP Error: ebmpDWORD has the wrong size (" + << sizeof( ebmpDWORD ) << " bytes)," << endl + << " Compared to the expected 4 byte value" << endl; + } + ReturnValue = false; + } + return ReturnValue; +} + +bool Rescale( BMP& InputImage , char mode, int NewDimension ) +{ + using namespace std; + int CapMode = toupper( mode ); + + BMP OldImage( InputImage ); + + if( CapMode != 'P' && + CapMode != 'W' && + CapMode != 'H' && + CapMode != 'F' ) + { + if( EasyBMPwarnings ) + { + char ErrorMessage [1024]; + sprintf( ErrorMessage, "EasyBMP Error: Unknown rescale mode %c requested\n" , mode ); + cout << ErrorMessage; + } + return false; + } + + int NewWidth =0; + int NewHeight =0; + + int OldWidth = OldImage.TellWidth(); + int OldHeight= OldImage.TellHeight(); + + if( CapMode == 'P' ) + { + NewWidth = (int) floor( OldWidth * NewDimension / 100.0 ); + NewHeight = (int) floor( OldHeight * NewDimension / 100.0 ); + } + if( CapMode == 'F' ) + { + if( OldWidth > OldHeight ) + { CapMode = 'W'; } + else + { CapMode = 'H'; } + } + + if( CapMode == 'W' ) + { + double percent = (double) NewDimension / (double) OldWidth; + NewWidth = NewDimension; + NewHeight = (int) floor( OldHeight * percent ); + } + if( CapMode == 'H' ) + { + double percent = (double) NewDimension / (double) OldHeight; + NewHeight = NewDimension; + NewWidth = (int) floor( OldWidth * percent ); + } + + if( NewWidth < 1 ) + { NewWidth = 1; } + if( NewHeight < 1 ) + { NewHeight = 1; } + + InputImage.SetSize( NewWidth, NewHeight ); + InputImage.SetBitDepth( 24 ); + + int I,J; + double ThetaI,ThetaJ; + + for( int j=0; j < NewHeight-1 ; j++ ) + { + ThetaJ = (double)(j*(OldHeight-1.0)) + /(double)(NewHeight-1.0); + J = (int) floor( ThetaJ ); + ThetaJ -= J; + + for( int i=0; i < NewWidth-1 ; i++ ) + { + ThetaI = (double)(i*(OldWidth-1.0)) + /(double)(NewWidth-1.0); + I = (int) floor( ThetaI ); + ThetaI -= I; + + InputImage(i,j)->Red = (ebmpBYTE) + ( (1.0-ThetaI-ThetaJ+ThetaI*ThetaJ)*(OldImage(I,J)->Red) + +(ThetaI-ThetaI*ThetaJ)*(OldImage(I+1,J)->Red) + +(ThetaJ-ThetaI*ThetaJ)*(OldImage(I,J+1)->Red) + +(ThetaI*ThetaJ)*(OldImage(I+1,J+1)->Red) ); + InputImage(i,j)->Green = (ebmpBYTE) + ( (1.0-ThetaI-ThetaJ+ThetaI*ThetaJ)*OldImage(I,J)->Green + +(ThetaI-ThetaI*ThetaJ)*OldImage(I+1,J)->Green + +(ThetaJ-ThetaI*ThetaJ)*OldImage(I,J+1)->Green + +(ThetaI*ThetaJ)*OldImage(I+1,J+1)->Green ); + InputImage(i,j)->Blue = (ebmpBYTE) + ( (1.0-ThetaI-ThetaJ+ThetaI*ThetaJ)*OldImage(I,J)->Blue + +(ThetaI-ThetaI*ThetaJ)*OldImage(I+1,J)->Blue + +(ThetaJ-ThetaI*ThetaJ)*OldImage(I,J+1)->Blue + +(ThetaI*ThetaJ)*OldImage(I+1,J+1)->Blue ); + } + InputImage(NewWidth-1,j)->Red = (ebmpBYTE) + ( (1.0-ThetaJ)*(OldImage(OldWidth-1,J)->Red) + + ThetaJ*(OldImage(OldWidth-1,J+1)->Red) ); + InputImage(NewWidth-1,j)->Green = (ebmpBYTE) + ( (1.0-ThetaJ)*(OldImage(OldWidth-1,J)->Green) + + ThetaJ*(OldImage(OldWidth-1,J+1)->Green) ); + InputImage(NewWidth-1,j)->Blue = (ebmpBYTE) + ( (1.0-ThetaJ)*(OldImage(OldWidth-1,J)->Blue) + + ThetaJ*(OldImage(OldWidth-1,J+1)->Blue) ); + } + + for( int i=0 ; i < NewWidth-1 ; i++ ) + { + ThetaI = (double)(i*(OldWidth-1.0)) + /(double)(NewWidth-1.0); + I = (int) floor( ThetaI ); + ThetaI -= I; + InputImage(i,NewHeight-1)->Red = (ebmpBYTE) + ( (1.0-ThetaI)*(OldImage(I,OldHeight-1)->Red) + + ThetaI*(OldImage(I,OldHeight-1)->Red) ); + InputImage(i,NewHeight-1)->Green = (ebmpBYTE) + ( (1.0-ThetaI)*(OldImage(I,OldHeight-1)->Green) + + ThetaI*(OldImage(I,OldHeight-1)->Green) ); + InputImage(i,NewHeight-1)->Blue = (ebmpBYTE) + ( (1.0-ThetaI)*(OldImage(I,OldHeight-1)->Blue) + + ThetaI*(OldImage(I,OldHeight-1)->Blue) ); + } + + *InputImage(NewWidth-1,NewHeight-1) = *OldImage(OldWidth-1,OldHeight-1); + return true; +} diff --git a/external/include/BMP/EasyBMP.h b/external/include/BMP/EasyBMP.h new file mode 100644 index 0000000..ead98c1 --- /dev/null +++ b/external/include/BMP/EasyBMP.h @@ -0,0 +1,86 @@ +/************************************************* +* * +* EasyBMP Cross-Platform Windows Bitmap Library * +* * +* Author: Paul Macklin * +* email: macklin01@users.sourceforge.net * +* support: http://easybmp.sourceforge.net * +* * +* file: EasyBMP.h * +* date added: 01-31-2005 * +* date modified: 12-01-2006 * +* version: 1.06 * +* * +* License: BSD (revised/modified) * +* Copyright: 2005-6 by the EasyBMP Project * +* * +* description: Main include file * +* * +*************************************************/ + +#ifdef _MSC_VER +// MS Visual Studio gives warnings when using +// fopen. But fopen_s is not going to work well +// with most compilers, and fopen_s uses different +// syntax than fopen. (i.e., a macro won't work) +// So, we'lll use this: +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#include +#include +#include +#include + +#ifndef EasyBMP +#define EasyBMP + +#ifdef __BCPLUSPLUS__ +// The Borland compiler must use this because something +// is wrong with their cstdio file. +#include +#else +#include +#endif + +#ifdef __GNUC__ +// If g++ specific code is ever required, this is +// where it goes. +#endif + +#ifdef __INTEL_COMPILER +// If Intel specific code is ever required, this is +// where it goes. +#endif + +#ifndef _DefaultXPelsPerMeter_ +#define _DefaultXPelsPerMeter_ +#define DefaultXPelsPerMeter 3780 +// set to a default of 96 dpi +#endif + +#ifndef _DefaultYPelsPerMeter_ +#define _DefaultYPelsPerMeter_ +#define DefaultYPelsPerMeter 3780 +// set to a default of 96 dpi +#endif + +#include "EasyBMP_DataStructures.h" +#include "EasyBMP_BMP.h" +#include "EasyBMP_VariousBMPutilities.h" + +#ifndef _EasyBMP_Version_ +#define _EasyBMP_Version_ 1.06 +#define _EasyBMP_Version_Integer_ 106 +#define _EasyBMP_Version_String_ "1.06" +#endif + +#ifndef _EasyBMPwarnings_ +#define _EasyBMPwarnings_ +#endif + +void SetEasyBMPwarningsOff( void ); +void SetEasyBMPwarningsOn( void ); +bool GetEasyBMPwarningState( void ); + +#endif diff --git a/external/include/BMP/EasyBMP_BMP.h b/external/include/BMP/EasyBMP_BMP.h new file mode 100644 index 0000000..819a976 --- /dev/null +++ b/external/include/BMP/EasyBMP_BMP.h @@ -0,0 +1,86 @@ +/************************************************* +* * +* EasyBMP Cross-Platform Windows Bitmap Library * +* * +* Author: Paul Macklin * +* email: macklin01@users.sourceforge.net * +* support: http://easybmp.sourceforge.net * +* * +* file: EasyBMP_VariousBMPutilities.h * +* date added: 05-02-2005 * +* date modified: 12-01-2006 * +* version: 1.06 * +* * +* License: BSD (revised/modified) * +* Copyright: 2005-6 by the EasyBMP Project * +* * +* description: Defines BMP class * +* * +*************************************************/ + +#ifndef _EasyBMP_BMP_h_ +#define _EasyBMP_BMP_h_ + +bool SafeFread( char* buffer, int size, int number, FILE* fp ); +bool EasyBMPcheckDataSize( void ); + +class BMP +{private: + + int BitDepth; + int Width; + int Height; + RGBApixel** Pixels; + RGBApixel* Colors; + int XPelsPerMeter; + int YPelsPerMeter; + + ebmpBYTE* MetaData1; + int SizeOfMetaData1; + ebmpBYTE* MetaData2; + int SizeOfMetaData2; + + bool Read32bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Read24bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Read8bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Read4bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Read1bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + + bool Write32bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Write24bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Write8bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Write4bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + bool Write1bitRow( ebmpBYTE* Buffer, int BufferSize, int Row ); + + ebmpBYTE FindClosestColor( RGBApixel& input ); + + public: + + int TellBitDepth( void ); + int TellWidth( void ); + int TellHeight( void ); + int TellNumberOfColors( void ); + void SetDPI( int HorizontalDPI, int VerticalDPI ); + int TellVerticalDPI( void ); + int TellHorizontalDPI( void ); + + BMP(); + BMP( BMP& Input ); + ~BMP(); + RGBApixel* operator()(int i,int j); + + RGBApixel GetPixel( int i, int j ) const; + bool SetPixel( int i, int j, RGBApixel NewPixel ); + + bool CreateStandardColorTable( void ); + + bool SetSize( int NewWidth, int NewHeight ); + bool SetBitDepth( int NewDepth ); + bool WriteToFile( const char* FileName ); + bool ReadFromFile( const char* FileName ); + + RGBApixel GetColor( int ColorNumber ); + bool SetColor( int ColorNumber, RGBApixel NewColor ); +}; + +#endif diff --git a/external/include/BMP/EasyBMP_DataStructures.h b/external/include/BMP/EasyBMP_DataStructures.h new file mode 100644 index 0000000..82b6179 --- /dev/null +++ b/external/include/BMP/EasyBMP_DataStructures.h @@ -0,0 +1,104 @@ +/************************************************* +* * +* EasyBMP Cross-Platform Windows Bitmap Library * +* * +* Author: Paul Macklin * +* email: macklin01@users.sourceforge.net * +* support: http://easybmp.sourceforge.net * +* * +* file: EasyBMP_DataStructures.h * +* date added: 05-02-2005 * +* date modified: 12-01-2006 * +* version: 1.06 * +* * +* License: BSD (revised/modified) * +* Copyright: 2005-6 by the EasyBMP Project * +* * +* description: Defines basic data structures for * +* the BMP class * +* * +*************************************************/ + +#ifndef _EasyBMP_Custom_Math_Functions_ +#define _EasyBMP_Custom_Math_Functions_ +inline double Square( double number ) +{ return number*number; } + +inline int IntSquare( int number ) +{ return number*number; } +#endif + +int IntPow( int base, int exponent ); + +#ifndef _EasyBMP_Defined_WINGDI +#define _EasyBMP_Defined_WINGDI + typedef unsigned char ebmpBYTE; + typedef unsigned short ebmpWORD; + typedef unsigned int ebmpDWORD; +#endif + +#ifndef _EasyBMP_DataStructures_h_ +#define _EasyBMP_DataStructures_h_ + +inline bool IsBigEndian() +{ + short word = 0x0001; + if((*(char *)& word) != 0x01 ) + { return true; } + return false; +} + +inline ebmpWORD FlipWORD( ebmpWORD in ) +{ return ( (in >> 8) | (in << 8) ); } + +inline ebmpDWORD FlipDWORD( ebmpDWORD in ) +{ + return ( ((in&0xFF000000)>>24) | ((in&0x000000FF)<<24) | + ((in&0x00FF0000)>>8 ) | ((in&0x0000FF00)<<8 ) ); +} + +// it's easier to use a struct than a class +// because we can read/write all four of the bytes +// at once (as we can count on them being continuous +// in memory + +typedef struct RGBApixel { + ebmpBYTE Blue; + ebmpBYTE Green; + ebmpBYTE Red; + ebmpBYTE Alpha; +} RGBApixel; + +class BMFH{ +public: + ebmpWORD bfType; + ebmpDWORD bfSize; + ebmpWORD bfReserved1; + ebmpWORD bfReserved2; + ebmpDWORD bfOffBits; + + BMFH(); + void display( void ); + void SwitchEndianess( void ); +}; + +class BMIH{ +public: + ebmpDWORD biSize; + ebmpDWORD biWidth; + ebmpDWORD biHeight; + ebmpWORD biPlanes; + ebmpWORD biBitCount; + ebmpDWORD biCompression; + ebmpDWORD biSizeImage; + ebmpDWORD biXPelsPerMeter; + ebmpDWORD biYPelsPerMeter; + ebmpDWORD biClrUsed; + ebmpDWORD biClrImportant; + + BMIH(); + void display( void ); + void SwitchEndianess( void ); +}; + +#endif diff --git a/external/include/BMP/EasyBMP_VariousBMPutilities.h b/external/include/BMP/EasyBMP_VariousBMPutilities.h new file mode 100644 index 0000000..349dda6 --- /dev/null +++ b/external/include/BMP/EasyBMP_VariousBMPutilities.h @@ -0,0 +1,43 @@ +/************************************************* +* * +* EasyBMP Cross-Platform Windows Bitmap Library * +* * +* Author: Paul Macklin * +* email: macklin01@users.sourceforge.net * +* support: http://easybmp.sourceforge.net * +* * +* file: EasyBMP_VariousBMPutilities.h * +* date added: 05-02-2005 * +* date modified: 12-01-2006 * +* version: 1.06 * +* * +* License: BSD (revised/modified) * +* Copyright: 2005-6 by the EasyBMP Project * +* * +* description: Various utilities. * +* * +*************************************************/ + +#ifndef _EasyBMP_VariousBMPutilities_h_ +#define _EasyBMP_VariousBMPutilities_h_ + +BMFH GetBMFH( const char* szFileNameIn ); +BMIH GetBMIH( const char* szFileNameIn ); +void DisplayBitmapInfo( const char* szFileNameIn ); +int GetBitmapColorDepth( const char* szFileNameIn ); +void PixelToPixelCopy( BMP& From, int FromX, int FromY, + BMP& To, int ToX, int ToY); +void PixelToPixelCopyTransparent( BMP& From, int FromX, int FromY, + BMP& To, int ToX, int ToY, + RGBApixel& Transparent ); +void RangedPixelToPixelCopy( BMP& From, int FromL , int FromR, int FromB, int FromT, + BMP& To, int ToX, int ToY ); +void RangedPixelToPixelCopyTransparent( + BMP& From, int FromL , int FromR, int FromB, int FromT, + BMP& To, int ToX, int ToY , + RGBApixel& Transparent ); +bool CreateGrayscaleColorTable( BMP& InputImage ); + +bool Rescale( BMP& InputImage , char mode, int NewDimension ); + +#endif diff --git a/img/DOF14_0.5.PNG b/img/DOF14_0.5.PNG new file mode 100644 index 0000000..e65bc9e Binary files /dev/null and b/img/DOF14_0.5.PNG differ diff --git a/img/RaysPerBounce.JPG b/img/RaysPerBounce.JPG new file mode 100644 index 0000000..5b87798 Binary files /dev/null and b/img/RaysPerBounce.JPG differ diff --git a/img/SC.JPG b/img/SC.JPG new file mode 100644 index 0000000..7063883 Binary files /dev/null and b/img/SC.JPG differ diff --git a/img/TimingPerBounce.JPG b/img/TimingPerBounce.JPG new file mode 100644 index 0000000..15248da Binary files /dev/null and b/img/TimingPerBounce.JPG differ diff --git a/img/code v1.PNG b/img/code v1.PNG new file mode 100644 index 0000000..6474f16 Binary files /dev/null and b/img/code v1.PNG differ diff --git a/img/code v2.PNG b/img/code v2.PNG new file mode 100644 index 0000000..ca20c5f Binary files /dev/null and b/img/code v2.PNG differ diff --git a/img/fresnel.PNG b/img/fresnel.PNG new file mode 100644 index 0000000..674ce82 Binary files /dev/null and b/img/fresnel.PNG differ diff --git a/img/sample.PNG b/img/sample.PNG new file mode 100644 index 0000000..93d9ffd Binary files /dev/null and b/img/sample.PNG differ diff --git a/img/table.JPG b/img/table.JPG new file mode 100644 index 0000000..5744934 Binary files /dev/null and b/img/table.JPG differ diff --git a/src/interactions.h b/src/interactions.h index 7bf6fab..82c1415 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -40,24 +40,69 @@ __host__ __device__ bool calculateScatterAndAbsorption(ray& r, float& depth, Abs // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ glm::vec3 calculateTransmissionDirection(glm::vec3 normal, glm::vec3 incident, float incidentIOR, float transmittedIOR) { - return glm::vec3(0,0,0); + + float cosI = glm::dot(normal, incident); + float n12 = incidentIOR / transmittedIOR; + + if (cosI > 0) //inside + { + n12 = transmittedIOR / incidentIOR; + normal = -normal; + } + + float delt = 1.0f - pow(n12, 2) * (1.0f - pow(glm::dot(normal, incident), 2)); + + if (delt >= 0) + return glm::normalize( (-n12 * glm::dot(normal, incident) - sqrt(delt)) * normal + n12 * incident); + else + return calculateReflectionDirection(normal, incident); } // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ glm::vec3 calculateReflectionDirection(glm::vec3 normal, glm::vec3 incident) { //nothing fancy here - return glm::vec3(0,0,0); + return glm::normalize(incident - 2.0f * normal * (glm::dot(incident, normal))); } -// TODO (OPTIONAL): IMPLEMENT THIS FUNCTION +//// TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ Fresnel calculateFresnel(glm::vec3 normal, glm::vec3 incident, float incidentIOR, float transmittedIOR, glm::vec3 reflectionDirection, glm::vec3 transmissionDirection) { - Fresnel fresnel; + Fresnel fresnel; + + fresnel.reflectionCoefficient = 1; + fresnel.transmissionCoefficient = 0; + + float cosI = glm::dot(normal, incident); + float n12 = incidentIOR / transmittedIOR; + + if (cosI > 0)//inside + { + n12 = transmittedIOR / incidentIOR; + normal = -normal; + } + + reflectionDirection = calculateReflectionDirection(normal, incident); - fresnel.reflectionCoefficient = 1; - fresnel.transmissionCoefficient = 0; - return fresnel; + if(transmittedIOR == 0.0f) + return fresnel; + + float delt = 1 - pow((n12 * sqrt(1 - pow(glm::dot(normal, incident), 2))), 2); + + if (delt < 0) + return fresnel; + delt = sqrt(delt); + + float Rs = pow((incidentIOR * glm::dot(normal, incident) - transmittedIOR * delt)/(incidentIOR * glm::dot(normal, incident) + transmittedIOR * delt), 2); + float Rp = pow((incidentIOR * delt - transmittedIOR * glm::dot(normal, incident))/(incidentIOR * delt + transmittedIOR * glm::dot(normal, incident)), 2); + + fresnel.reflectionCoefficient = (Rs + Rp)/2.0f; + fresnel.transmissionCoefficient = 1 - fresnel.reflectionCoefficient; + + if (cosI > 0) + transmissionDirection = calculateTransmissionDirection(normal, incident, incidentIOR, transmittedIOR); + return fresnel; } + // LOOK: This function demonstrates cosine weighted random direction generation in a sphere! __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 normal, float xi1, float xi2) { @@ -91,7 +136,10 @@ __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 nor // non-cosine (uniform) weighted random direction generation. // This should be much easier than if you had to implement calculateRandomDirectionInHemisphere. __host__ __device__ glm::vec3 getRandomDirectionInSphere(float xi1, float xi2) { - return glm::vec3(0,0,0); + float theta = TWO_PI * xi1; + float u = 2 * xi2 - 1; + + return glm::vec3(sqrt(1-u*u)*cos(theta),sqrt(1-u*u)*sin(theta),u); } // TODO (PARTIALLY OPTIONAL): IMPLEMENT THIS FUNCTION diff --git a/src/intersections.h b/src/intersections.h index c9eafb6..900807e 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -73,7 +73,82 @@ __host__ __device__ glm::vec3 getSignOfRay(ray r){ // Cube intersection test, return -1 if no intersection, otherwise, distance to intersection __host__ __device__ float boxIntersectionTest(staticGeom box, ray r, glm::vec3& intersectionPoint, glm::vec3& normal){ - return -1; + glm::vec3 ro = multiplyMV(box.inverseTransform, glm::vec4(r.origin,1.0f)); + glm::vec3 rd = glm::normalize(multiplyMV(box.inverseTransform, glm::vec4(r.direction,0.0f))); + + ray rt; rt.origin = ro; rt.direction = rd; + + glm::vec3 boxMin(-0.5, -0.5, -0.5); + glm::vec3 boxMax(0.5, 0.5, 0.5); + + float maxNear = FLT_MIN; + float minFar = FLT_MAX; + + bool isIntersect = true; + + for (int i = 0; i < 3; i++) + { + if (rt.direction[i] == 0.0f) + { + if (rt.direction[i] < boxMin[i] || rt.direction[i] > boxMax[i]) + isIntersect = false; + } + else + { + float tNear = (boxMin[i] - rt.origin[i]) / rt.direction[i]; + float tFar = (boxMax[i] - rt.origin[i]) / rt.direction[i]; + + if (tNear > tFar) + { + float tem = tFar; + tFar = tNear; + tNear = tem; + } + + maxNear = glm::max(maxNear, tNear); + minFar = glm::min(minFar, tFar); + + if (maxNear > minFar || minFar < 0) + isIntersect = false; + } + } + + if (isIntersect) + { + float t = 0; + if (maxNear <= 0) + t = minFar; + else + t = maxNear; + + glm::vec3 intersectP = getPointOnRay(rt, t); + glm::vec3 realIntersectionPoint = multiplyMV(box.transform, glm::vec4(intersectP, 1)); + glm::vec3 realOrigin = multiplyMV(box.transform, glm::vec4(0,0,0,1)); + + glm::vec3 norm; + float threshold = 0.001f; + if (abs(intersectP.x - 0.5) < threshold) + norm = glm::vec3(1, 0, 0); + else if (abs(intersectP.x + 0.5) < threshold) + norm = glm::vec3(-1, 0, 0); + else if (abs(intersectP.y - 0.5) < threshold) + norm = glm::vec3(0, 1, 0); + else if (abs(intersectP.y + 0.5) < threshold) + norm = glm::vec3(0, -1, 0); + else if (abs(intersectP.z - 0.5) < threshold) + norm = glm::vec3(0, 0, 1); + else + norm = glm::vec3(0, 0, -1); + + glm::vec3 realNormal = multiplyMV(box.transform, glm::vec4(norm, 1)); + + intersectionPoint = realIntersectionPoint; + normal = glm::normalize(realNormal - realOrigin); + + return glm::length(r.origin - realIntersectionPoint); + } + else + return -1; } // LOOK: Here's an intersection test example from a sphere. Now you just need to figure out cube and, optionally, triangle. @@ -116,6 +191,35 @@ __host__ __device__ float sphereIntersectionTest(staticGeom sphere, ray r, glm:: return glm::length(r.origin - realIntersectionPoint); } + +__host__ __device__ float intersectionTest(ray r, staticGeom* geoms, int numberOfGeoms, + glm::vec3& intersectionPoint, glm::vec3& normal, int &geomId) +{ + float nearestDist = FLT_MAX; + for (int i = 0; i < numberOfGeoms; i++) + { + float dist; + glm::vec3 intersect, n; + + if (geoms[i].type == GEOMTYPE::CUBE) + dist = boxIntersectionTest(geoms[i], r, intersect, n); + else if (geoms[i].type == GEOMTYPE::SPHERE) + dist = sphereIntersectionTest(geoms[i], r, intersect, n); + else if (geoms[i].type == GEOMTYPE::MESH); + else; + + if (dist != -1.0f && dist < nearestDist) + { + nearestDist = dist; + intersectionPoint = intersect; + normal = n; + geomId = i; + } + + } + return nearestDist; +} + // Returns x,y,z half-dimensions of tightest bounding box __host__ __device__ glm::vec3 getRadiuses(staticGeom geom){ glm::vec3 origin = multiplyMV(geom.transform, glm::vec4(0,0,0,1)); @@ -141,7 +245,7 @@ __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float random float side1 = radii.x * radii.y * 4.0f; //x-y face float side2 = radii.z * radii.y * 4.0f; //y-z face float side3 = radii.x * radii.z* 4.0f; //x-z face - float totalarea = 2.0f * (side1+side2+side3); + float totalarea = 4 * PI; // Pick random face, weighted by surface area float russianRoulette = (float)u01(rng); @@ -178,7 +282,22 @@ __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float random // Generates a random point on a given sphere __host__ __device__ glm::vec3 getRandomPointOnSphere(staticGeom sphere, float randomSeed){ - return glm::vec3(0,0,0); + thrust::default_random_engine rng(hash(randomSeed)); + thrust::uniform_real_distribution u01(0,180); + thrust::uniform_real_distribution u02(0,360); + + float r = 0.5f; + float theta = (float)u01(rng) * PI / 180.0f; + float phi = (float)u02(rng) * PI / 180.0f; + + glm::vec3 point; + point.x = r * sin(theta) * cos(phi); + point.y = r * sin(theta) * sin(phi); + point.z = r * cos(theta); + + glm::vec3 randPoint = multiplyMV(sphere.transform, glm::vec4(point,1.0f)); + + return randPoint; } #endif diff --git a/src/main.cpp b/src/main.cpp index b002500..4859186 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,9 +104,9 @@ void runCuda(){ for (int i=0; i < renderScene->materials.size(); i++) { materials[i] = renderScene->materials[i]; } - // execute the kernel - cudaRaytraceCore(dptr, renderCam, targetFrame, iterations, materials, renderScene->materials.size(), geoms, renderScene->objects.size() ); + cudaRaytraceCore(dptr, renderCam, targetFrame, iterations, materials, renderScene->materials.size(), geoms, renderScene->objects.size(), + false, false/*, true, renderScene->texture.width, renderScene->texture.height, renderScene->texture.R, renderScene->texture.G, renderScene->texture.B*/); // unmap buffer object cudaGLUnmapBufferObject(pbo); diff --git a/src/raytraceKernel.cu b/src/raytraceKernel.cu index 9c7bc7d..b95145c 100644 --- a/src/raytraceKernel.cu +++ b/src/raytraceKernel.cu @@ -15,6 +15,9 @@ #include "raytraceKernel.h" #include "intersections.h" #include "interactions.h" +#include "thrust\device_ptr.h" +#include "thrust\remove.h" +#include "thrust\count.h" void checkCUDAError(const char *msg) { cudaError_t err = cudaGetLastError(); @@ -38,10 +41,23 @@ __host__ __device__ glm::vec3 generateRandomNumberFromThread(glm::vec2 resolutio // TODO: IMPLEMENT THIS FUNCTION // Function that does the initial raycast from the camera __host__ __device__ ray raycastFromCameraKernel(glm::vec2 resolution, float time, int x, int y, glm::vec3 eye, glm::vec3 view, glm::vec3 up, glm::vec2 fov){ - ray r; - r.origin = glm::vec3(0,0,0); - r.direction = glm::vec3(0,0,-1); - return r; + + glm::vec3 midPoint = eye + view; + + glm::vec3 A = glm::cross(view, up); + glm::vec3 B = glm::cross(A, view); + + glm::vec3 axisV = B * (float)(glm::length(view) * tan(fov[1] * PI / 180.0f) / glm::length(B)); //fov[0]? + glm::vec3 axisH = A * (glm::length(axisV) * resolution[0] / resolution[1] / glm::length(A)); + + glm::vec3 h = axisH * (2 * x / (float) (resolution[0] - 1) - 1); + glm::vec3 v = axisV * (2 * y / (float) (resolution[1] - 1) - 1); + glm::vec3 p = midPoint + h + v; + + ray r; + r.origin = midPoint; + r.direction = glm::normalize(p - eye); + return r; } //Kernel that blacks out a given image buffer @@ -60,7 +76,7 @@ __global__ void sendImageToPBO(uchar4* PBOpos, glm::vec2 resolution, glm::vec3* int x = (blockIdx.x * blockDim.x) + threadIdx.x; int y = (blockIdx.y * blockDim.y) + threadIdx.y; int index = x + (y * resolution.x); - + int PBOindex = x + ((resolution.y-1-y) * resolution.x); if(x<=resolution.x && y<=resolution.y){ glm::vec3 color; @@ -81,33 +97,221 @@ __global__ void sendImageToPBO(uchar4* PBOpos, glm::vec2 resolution, glm::vec3* } // Each thread writes one pixel location in the texture (textel) - PBOpos[index].w = 0; - PBOpos[index].x = color.x; - PBOpos[index].y = color.y; - PBOpos[index].z = color.z; + PBOpos[PBOindex].w = 0; + PBOpos[PBOindex].x = color.x; + PBOpos[PBOindex].y = color.y; + PBOpos[PBOindex].z = color.z; } } +// pixel parallelization +__global__ void raytraceRayPP(glm::vec2 resolution, float time, cameraData cam, int rayDepth, glm::vec3* colors, + staticGeom* geoms, int numberOfGeoms, material* cudaMaterials, int numberOfMaterials, + bool DOF/*, int textureW, int textureH, int* cudaR, int* cudaG, int* cudaB*/){ + + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * resolution.x); + + if((x<=resolution.x && y<=resolution.y)){ + + glm::vec3 random = generateRandomNumberFromThread(cam.resolution, time, x, y); + + ray r = raycastFromCameraKernel(resolution, time, x + (2.0 * random.x - 1.0), y + (2.0 * random.y - 1.0), cam.position, cam.view, cam.up, cam.fov); + + if (DOF) + { + glm::vec3 pos = r.origin + (float)FOCALLENGTH * r.direction; + r.origin = cam.position + (float)JITTER *(2.0f * random - glm::vec3(1.0)); + r.direction = glm::normalize(pos - r.origin); + } + + int depth = 0; + glm::vec3 color(1.0f, 1.0f, 1.0f); + + while(depth++ < rayDepth) { + glm::vec3 intersect, normal; + int geomId = -1; + float nearestDist = intersectionTest(r, geoms, numberOfGeoms, intersect, normal, geomId); + + if (geomId == -1) + { + color = glm::vec3(0.0); + break; + } + + material curMaterial = cudaMaterials[geoms[geomId].materialid]; + glm::vec3 MRGB = curMaterial.color; + //if (geoms[geomId].materialid == 9) + //{ + // int tId = (int)(x * cudaTexture->width / resolution.x) + (int)(y * cudaTexture->height / resolution.y)* cudaTexture->width; + // MRGB = glm::vec3(cudaR[0]/255.0f, cudaG[0]/255.0f, cudaB[0]/255.0f); + //} + + if (curMaterial.emittance > 0.0f) //light + { + color *= curMaterial.emittance * MRGB; + break; + } + + glm::vec3 random2 = generateRandomNumberFromThread(cam.resolution, time+depth, x, y); + if (curMaterial.hasReflective > EPSILON && curMaterial.hasRefractive > EPSILON) //fresel + { + glm::vec3 reflDir, refrDir; + Fresnel fresnel = calculateFresnel(normal, r.direction, 1.0f, curMaterial.indexOfRefraction, reflDir, refrDir); + + r.direction = (random2.x < fresnel.reflectionCoefficient) ? reflDir : refrDir; + } + else if (curMaterial.hasReflective > EPSILON) //reflection + { + r.direction = calculateReflectionDirection(normal, r.direction); //normalized + color *= MRGB; + } + else if (curMaterial.hasRefractive > EPSILON) //refraction + { + glm::vec3 refractedDirection = calculateTransmissionDirection(normal, r.direction, 1.0, curMaterial.indexOfRefraction); + r.direction = refractedDirection; + r.origin = intersect + 0.01f * r.direction; + continue; + } + else //diffuse + { + r.direction = glm::normalize(calculateRandomDirectionInHemisphere(normal, random2.x, random2.y)); + color *= MRGB; + } + r.origin = intersect + 0.001f * r.direction; + } + + if (depth == rayDepth) + color = glm::vec3(0.0); + + colors[index] = (colors[index] * time + color) / (float)(time+1); + //colors[index] += color; + } +} + +__global__ void rayPixelInitialize(glm::vec2 resolution, float time, cameraData cam, rayPixel* rayPixels, + bool DOF) +{ + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * resolution.x); + + if(x<=resolution.x && y<=resolution.y){ + rayPixels[index].x = x; + rayPixels[index].y = y; + rayPixels[index].color = glm::vec3(1.0, 1.0, 1.0); + rayPixels[index].isTerminated = false; + + glm::vec3 random = generateRandomNumberFromThread(cam.resolution, time, x, y); + rayPixels[index].r = raycastFromCameraKernel(resolution, time, x + (2.0 * random.x - 1.0), y + (2.0 * random.y - 1.0), cam.position, cam.view, cam.up, cam.fov); + + if (DOF) + { + glm::vec3 pos = rayPixels[index].r.origin + (float)FOCALLENGTH * rayPixels[index].r.direction; + rayPixels[index].r.origin = cam.position + (float)JITTER *(2.0f * random - glm::vec3(1.0)); + rayPixels[index].r.direction = glm::normalize(pos - rayPixels[index].r.origin); + } + + } +} + // TODO: IMPLEMENT THIS FUNCTION -// Core raytracer kernel -__global__ void raytraceRay(glm::vec2 resolution, float time, cameraData cam, int rayDepth, glm::vec3* colors, - staticGeom* geoms, int numberOfGeoms){ +// Core raytracer kernel, ray parallelization +__global__ void raytraceRay(int numRays, float time, cameraData cam, int depth, int rayDepth, rayPixel* rayPixels, + staticGeom* geoms, int numberOfGeoms, material* cudaMaterials, int numberOfMaterials/*, + int textureW, int textureH, int* cudaR, int* cudaG, int* cudaB*/){ - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - int index = x + (y * resolution.x); + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if((index < numRays)){ + + rayPixel rayP = rayPixels[index]; + + glm::vec3 intersect, normal; + int geomId = -1; + + float nearestDist = intersectionTest(rayP.r, geoms, numberOfGeoms, intersect, normal, geomId); + + if (geomId == -1) + { + rayP.color = glm::vec3(0.0); + rayP.isTerminated = true; + rayPixels[index] = rayP; + return; + } + + material curMaterial = cudaMaterials[geoms[geomId].materialid]; - if((x<=resolution.x && y<=resolution.y)){ + if (curMaterial.emittance > 0.0f) //light + { + rayP.color *= (curMaterial.emittance * curMaterial.color); + rayP.isTerminated = true; + rayPixels[index] = rayP; + return; + } - colors[index] = generateRandomNumberFromThread(resolution, time, x, y); - } + if (depth == rayDepth) + { + rayP.color = glm::vec3(0.0); + rayP.isTerminated = true; + rayPixels[index] = rayP; + return; + } + + glm::vec3 random2 = generateRandomNumberFromThread(cam.resolution, time+depth, rayP.x, rayP.y); + if (curMaterial.hasReflective > EPSILON && curMaterial.hasRefractive > EPSILON) //fresel + { + glm::vec3 reflDir, refrDir; + Fresnel fresnel = calculateFresnel(normal, rayP.r.direction, 1.0f, curMaterial.indexOfRefraction, reflDir, refrDir); + + rayP.r.direction = (random2.x < fresnel.reflectionCoefficient) ? reflDir : refrDir; + } + else if (curMaterial.hasReflective > EPSILON) //reflection + { + rayP.r.direction = calculateReflectionDirection(normal, rayP.r.direction); //normalized + rayP.color *= curMaterial.color; + } + else if (curMaterial.hasRefractive > EPSILON) //refraction + { + glm::vec3 refractedDirection = calculateTransmissionDirection(normal, rayP.r.direction, 1.0, curMaterial.indexOfRefraction); + rayP.r.direction = refractedDirection; + rayP.r.origin = intersect + 0.01f * rayP.r.direction; + rayPixels[index] = rayP; + return; + } + else //diffuse + { + rayP.r.direction = glm::normalize(calculateRandomDirectionInHemisphere(normal, random2.x, random2.y)); + rayP.color *= curMaterial.color; + } + + rayP.r.origin = intersect + 0.001f * rayP.r.direction; + rayPixels[index] = rayP; + } +} + +__global__ void colorAccumulator(int numRays, rayPixel* rayPixels, glm::vec2 resolution, glm::vec3* colors, float time) +{ + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (index < numRays) + { + rayPixel rayP = rayPixels[index]; + if (rayP.isTerminated) + { + int colorId = rayP.x + rayP.y * resolution.x; + colors[colorId] = (colors[colorId] * time + rayP.color) / (float)(time+1); + } + } } // TODO: FINISH THIS FUNCTION // Wrapper for the __global__ call that sets up the kernel calls and does a ton of memory management -void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iterations, material* materials, int numberOfMaterials, geom* geoms, int numberOfGeoms){ +void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iterations, material* materials, int numberOfMaterials, geom* geoms, int numberOfGeoms, + bool rayParallel, bool DOF/*, bool hasTexture, int textureW, int textureH, int* R, int *G, int *B*/){ - int traceDepth = 1; //determines how many bounces the raytracer traces + int traceDepth = 8; //determines how many bounces the raytracer traces // set up crucial magic int tileSize = 8; @@ -145,17 +349,94 @@ void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iteratio cam.up = renderCam->ups[frame]; cam.fov = renderCam->fov; + // copy material + material* cudaMaterials = NULL; + cudaMalloc((void**)&cudaMaterials, numberOfMaterials*sizeof(material)); + cudaMemcpy( cudaMaterials, materials, numberOfMaterials*sizeof(material), cudaMemcpyHostToDevice); + + //copy texture + /* int *cudaR = NULL, *cudaG = NULL, *cudaB = NULL; + if(hasTexture) + { + cudaMalloc((void**)&cudaR, textureW*textureH*sizeof(int)); + cudaMemcpy( cudaR, R, textureW*textureH*sizeof(int), cudaMemcpyHostToDevice); + + cudaMalloc((void**)&cudaG, textureW*textureH*sizeof(int)); + cudaMemcpy( cudaG, G, textureW*textureH*sizeof(int), cudaMemcpyHostToDevice); + + cudaMalloc((void**)&cudaB, textureW*textureH*sizeof(int)); + cudaMemcpy( cudaB, B, textureW*textureH*sizeof(int), cudaMemcpyHostToDevice); + }*/ + //cudaEvent_t startTime, stopTime; + //cudaEventCreate(&startTime); + //cudaEventCreate(&stopTime); + //cudaEventRecord(startTime, 0); + // kernel launches - raytraceRay<<>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms); + if (rayParallel) + { + rayPixel* rayPixels = NULL; + cudaMalloc((void**)&rayPixels, renderCam->resolution.x * renderCam->resolution.y * sizeof(rayPixel)); + rayPixelInitialize<<>>(renderCam->resolution, (float)iterations, cam, rayPixels, DOF); + cudaThreadSynchronize(); + + int depth = 0; + int numRays = renderCam->resolution.x * renderCam->resolution.y; + threadsPerBlock = dim3(tileSize*tileSize); + + while(depth++ < traceDepth && numRays > 0) { + + //std::cout << depth-1 << ": " << numRays << std::endl; - sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); + fullBlocksPerGrid = dim3((int)ceil(numRays / float(tileSize*tileSize))); + raytraceRay<<>>(numRays, (float)iterations, cam, depth, traceDepth, rayPixels, cudageoms, numberOfGeoms, + cudaMaterials, numberOfMaterials/*, textureW, textureH, cudaR, cudaG, cudaB*/); + colorAccumulator<<>>(numRays, rayPixels, renderCam->resolution, cudaimage, (float)iterations); + + //stream compaction + if (depth == traceDepth) + break; + + thrust::device_ptr beginItr(rayPixels); + thrust::device_ptr endItr = beginItr + numRays; + endItr = thrust::remove_if(beginItr, endItr, isTerminated()); + numRays = (int)(endItr - beginItr); + } + + dim3 threadsPerBlock(tileSize, tileSize); + dim3 fullBlocksPerGrid((int)ceil(float(renderCam->resolution.x)/float(tileSize)), (int)ceil(float(renderCam->resolution.y)/float(tileSize))); + sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); + } + else + { + //pixel parallel + raytraceRayPP<<>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms, + cudaMaterials, numberOfMaterials, DOF/*, textureW, textureH, cudaR, cudaG, cudaB*/); + + sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); + } + + //cudaThreadSynchronize(); + //cudaEventRecord(stopTime, 0); + //cudaEventSynchronize(stopTime); + + //float duration = 0.0f; + //cudaEventElapsedTime(&duration, startTime, stopTime); + + //std::cout << iterations << ": " << duration << std::endl; + // retrieve image from GPU cudaMemcpy( renderCam->image, cudaimage, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3), cudaMemcpyDeviceToHost); // free up stuff, or else we'll leak memory like a madman cudaFree( cudaimage ); cudaFree( cudageoms ); + cudaFree(cudaMaterials); + /*cudaFree(cudaR); + cudaFree(cudaG); + cudaFree(cudaB);*/ + delete geomList; // make certain the kernel has completed diff --git a/src/raytraceKernel.h b/src/raytraceKernel.h index 984e89f..231d49d 100755 --- a/src/raytraceKernel.h +++ b/src/raytraceKernel.h @@ -14,6 +14,10 @@ #include #include "sceneStructs.h" -void cudaRaytraceCore(uchar4* pos, camera* renderCam, int frame, int iterations, material* materials, int numberOfMaterials, geom* geoms, int numberOfGeoms); +#define FOCALLENGTH 14 +#define JITTER 0.5 + +void cudaRaytraceCore(uchar4* pos, camera* renderCam, int frame, int iterations, material* materials, int numberOfMaterials, geom* geoms, int numberOfGeoms, + bool rayParallel, bool DOF/*, bool hasTexture, int textureW, int textureH, int* R, int *G, int *B*/); #endif diff --git a/src/scene.cpp b/src/scene.cpp index 4cbe216..cbfd707 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -29,6 +29,10 @@ scene::scene(string filename){ loadCamera(); cout << " " << endl; } + /*else if (strcmp(tokens[0].c_str(), "TEXTURE")==0){ + loadTexture(); + cout << " " << endl; + }*/ } } } @@ -263,3 +267,31 @@ int scene::loadMaterial(string materialid){ return 1; } } + +//int scene::loadTexture() +//{ +// string line; +// utilityCore::safeGetline(fp_in,line); +// vector tokens = utilityCore::tokenizeString(line); +// if(strcmp(tokens[0].c_str(), "FILE")==0) +// { +// BMP textureFile; +// textureFile.ReadFromFile(tokens[1].c_str()); +// +// texture.width = textureFile.TellWidth(); +// texture.height = textureFile.TellHeight(); +// texture.R = new int[texture.width*texture.height]; +// texture.G = new int[texture.width*texture.height]; +// texture.B = new int[texture.width*texture.height]; +// for (int h = 0; h < texture.height; h++) +// { +// for (int w = 0; w < texture.width; w++) +// { +// texture.R[w + h * texture.width] = textureFile.GetPixel(w, h).Red; +// texture.G[w + h * texture.width] = textureFile.GetPixel(w, h).Green; +// texture.B[w + h * texture.width] = textureFile.GetPixel(w, h).Blue; +// } +// } +// } +// return 1; +//} diff --git a/src/scene.h b/src/scene.h index ea3ddaa..33a813d 100644 --- a/src/scene.h +++ b/src/scene.h @@ -13,6 +13,7 @@ #include #include #include +#include "BMP\EasyBMP.h" using namespace std; @@ -22,6 +23,7 @@ class scene{ int loadMaterial(string materialid); int loadObject(string objectid); int loadCamera(); + //int loadTexture(); public: scene(string filename); ~scene(); @@ -29,6 +31,7 @@ class scene{ vector objects; vector materials; camera renderCam; + //Texture texture; }; #endif diff --git a/src/sceneStructs.h b/src/sceneStructs.h index 5e0c853..c55a436 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -18,6 +18,22 @@ struct ray { glm::vec3 direction; }; +struct rayPixel{ + ray r; + int x; + int y; + glm::vec3 color; + bool isTerminated; +}; + +struct isTerminated { + __host__ __device__ + bool operator()(const rayPixel rayP) + { + return (rayP.isTerminated); + } +}; + struct geom { enum GEOMTYPE type; int materialid; @@ -73,4 +89,12 @@ struct material{ float emittance; }; +struct Texture { + int width; + int height; + int *R; + int *G; + int *B; +}; + #endif //CUDASTRUCTS_H diff --git a/src/utilities.cpp b/src/utilities.cpp index a8e5d90..6f0ae4d 100755 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -4,7 +4,7 @@ // File: utilities.cpp // A collection/kitchen sink of generally useful functions -#define GLM_FORCE_RADIANS +//#define GLM_FORCE_RADIANS #include #include diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj index c45dd79..2c1c287 100644 --- a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj +++ b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj @@ -28,7 +28,7 @@ - + @@ -80,6 +80,7 @@ + @@ -95,6 +96,6 @@ - + \ No newline at end of file diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj.filters b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj.filters index 584fd19..7f23802 100644 --- a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj.filters +++ b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj.filters @@ -19,6 +19,9 @@ {f1a3a052-d6b3-4aff-a9f3-ac85b6c99146} + + {b54ec2fe-4631-45e4-8ffb-f92aae4cf692} + @@ -71,6 +74,9 @@ Source Files\stb_image + + Source Files\BMP +