Image.cpp
00001 00002 // 00003 // SFML - Simple and Fast Multimedia Library 00004 // Copyright (C) 2007-2009 Laurent Gomila ([email protected]) 00005 // 00006 // This software is provided 'as-is', without any express or implied warranty. 00007 // In no event will the authors be held liable for any damages arising from the use of this software. 00008 // 00009 // Permission is granted to anyone to use this software for any purpose, 00010 // including commercial applications, and to alter it and redistribute it freely, 00011 // subject to the following restrictions: 00012 // 00013 // 1. The origin of this software must not be misrepresented; 00014 // you must not claim that you wrote the original software. 00015 // If you use this software in a product, an acknowledgment 00016 // in the product documentation would be appreciated but is not required. 00017 // 00018 // 2. Altered source versions must be plainly marked as such, 00019 // and must not be misrepresented as being the original software. 00020 // 00021 // 3. This notice may not be removed or altered from any source distribution. 00022 // 00024 00026 // Headers 00028 #include <SFML/Graphics/Image.hpp> 00029 #include <SFML/Graphics/ImageLoader.hpp> 00030 #include <SFML/Graphics/RenderWindow.hpp> 00031 #include <SFML/Graphics/GraphicsContext.hpp> 00032 #include <algorithm> 00033 #include <iostream> 00034 #include <vector> 00035 #include <string.h> 00036 00037 00038 namespace sf 00039 { 00043 Image::Image() : 00044 myWidth (0), 00045 myHeight (0), 00046 myTextureWidth (0), 00047 myTextureHeight (0), 00048 myTexture (0), 00049 myIsSmooth (true), 00050 myNeedTextureUpdate(false), 00051 myNeedArrayUpdate (false) 00052 { 00053 00054 } 00055 00056 00060 Image::Image(const Image& Copy) : 00061 Resource<Image> (Copy), 00062 myWidth (Copy.myWidth), 00063 myHeight (Copy.myHeight), 00064 myTextureWidth (Copy.myTextureWidth), 00065 myTextureHeight (Copy.myTextureHeight), 00066 myTexture (0), 00067 myIsSmooth (Copy.myIsSmooth), 00068 myPixels (Copy.myPixels), 00069 myNeedTextureUpdate(false), 00070 myNeedArrayUpdate (false) 00071 { 00072 CreateTexture(); 00073 } 00074 00075 00079 Image::Image(unsigned int Width, unsigned int Height, const Color& Col) : 00080 myWidth (0), 00081 myHeight (0), 00082 myTextureWidth (0), 00083 myTextureHeight (0), 00084 myTexture (0), 00085 myIsSmooth (true), 00086 myNeedTextureUpdate(false), 00087 myNeedArrayUpdate (false) 00088 { 00089 Create(Width, Height, Col); 00090 } 00091 00092 00096 Image::Image(unsigned int Width, unsigned int Height, const Uint8* Data) : 00097 myWidth (0), 00098 myHeight (0), 00099 myTextureWidth (0), 00100 myTextureHeight (0), 00101 myTexture (0), 00102 myIsSmooth (true), 00103 myNeedTextureUpdate(false), 00104 myNeedArrayUpdate (false) 00105 { 00106 LoadFromPixels(Width, Height, Data); 00107 } 00108 00109 00113 Image::~Image() 00114 { 00115 // Destroy the OpenGL texture 00116 DestroyTexture(); 00117 } 00118 00119 00123 bool Image::LoadFromFile(const std::string& Filename) 00124 { 00125 // Let the image loader load the image into our pixel array 00126 bool Success = priv::ImageLoader::GetInstance().LoadImageFromFile(Filename, myPixels, myWidth, myHeight); 00127 00128 if (Success) 00129 { 00130 // Loading succeeded : we can create the texture 00131 if (CreateTexture()) 00132 return true; 00133 } 00134 00135 // Oops... something failed 00136 Reset(); 00137 00138 return false; 00139 } 00140 00141 00145 bool Image::LoadFromMemory(const char* Data, std::size_t SizeInBytes) 00146 { 00147 // Check parameters 00148 if (!Data || (SizeInBytes == 0)) 00149 { 00150 std::cerr << "Failed to image font from memory, no data provided" << std::endl; 00151 return false; 00152 } 00153 00154 // Let the image loader load the image into our pixel array 00155 bool Success = priv::ImageLoader::GetInstance().LoadImageFromMemory(Data, SizeInBytes, myPixels, myWidth, myHeight); 00156 00157 if (Success) 00158 { 00159 // Loading succeeded : we can create the texture 00160 if (CreateTexture()) 00161 return true; 00162 } 00163 00164 // Oops... something failed 00165 Reset(); 00166 00167 return false; 00168 } 00169 00170 00174 bool Image::LoadFromPixels(unsigned int Width, unsigned int Height, const Uint8* Data) 00175 { 00176 if (Data) 00177 { 00178 // Store the texture dimensions 00179 myWidth = Width; 00180 myHeight = Height; 00181 00182 // Fill the pixel buffer with the specified raw data 00183 const Color* Ptr = reinterpret_cast<const Color*>(Data); 00184 myPixels.assign(Ptr, Ptr + Width * Height); 00185 00186 // We can create the texture 00187 if (CreateTexture()) 00188 { 00189 return true; 00190 } 00191 else 00192 { 00193 // Oops... something failed 00194 Reset(); 00195 return false; 00196 } 00197 } 00198 else 00199 { 00200 // No data provided : create a white image 00201 return Create(Width, Height, Color(255, 255, 255, 255)); 00202 } 00203 } 00204 00205 00209 bool Image::SaveToFile(const std::string& Filename) const 00210 { 00211 // Check if the array of pixels needs to be updated 00212 EnsureArrayUpdate(); 00213 00214 // Let the image loader save our pixel array into the image 00215 return priv::ImageLoader::GetInstance().SaveImageToFile(Filename, myPixels, myWidth, myHeight); 00216 } 00217 00218 00222 bool Image::Create(unsigned int Width, unsigned int Height, Color Col) 00223 { 00224 // Store the texture dimensions 00225 myWidth = Width; 00226 myHeight = Height; 00227 00228 // Recreate the pixel buffer and fill it with the specified color 00229 myPixels.clear(); 00230 myPixels.resize(Width * Height, Col); 00231 00232 // We can create the texture 00233 if (CreateTexture()) 00234 { 00235 return true; 00236 } 00237 else 00238 { 00239 // Oops... something failed 00240 Reset(); 00241 return false; 00242 } 00243 } 00244 00245 00249 void Image::CreateMaskFromColor(Color ColorKey, Uint8 Alpha) 00250 { 00251 // Check if the array of pixels needs to be updated 00252 EnsureArrayUpdate(); 00253 00254 // Calculate the new color (old color with no alpha) 00255 Color NewColor(ColorKey.r, ColorKey.g, ColorKey.b, Alpha); 00256 00257 // Replace the old color by the new one 00258 std::replace(myPixels.begin(), myPixels.end(), ColorKey, NewColor); 00259 00260 // The texture will need to be updated 00261 myNeedTextureUpdate = true; 00262 } 00263 00264 00270 void Image::Copy(const Image& Source, unsigned int DestX, unsigned int DestY, const IntRect& SourceRect, bool ApplyAlpha) 00271 { 00272 // Make sure both images are valid 00273 if ((Source.myWidth == 0) || (Source.myHeight == 0) || (myWidth == 0) || (myHeight == 0)) 00274 return; 00275 00276 // Make sure both images have up-to-date arrays 00277 EnsureArrayUpdate(); 00278 Source.EnsureArrayUpdate(); 00279 00280 // Adjust the source rectangle 00281 IntRect SrcRect = SourceRect; 00282 if (SrcRect.GetWidth() == 0 || (SrcRect.GetHeight() == 0)) 00283 { 00284 SrcRect.Left = 0; 00285 SrcRect.Top = 0; 00286 SrcRect.Right = Source.myWidth; 00287 SrcRect.Bottom = Source.myHeight; 00288 } 00289 else 00290 { 00291 if (SrcRect.Left < 0) SrcRect.Left = 0; 00292 if (SrcRect.Top < 0) SrcRect.Top = 0; 00293 if (SrcRect.Right > static_cast<int>(Source.myWidth)) SrcRect.Right = Source.myWidth; 00294 if (SrcRect.Bottom > static_cast<int>(Source.myHeight)) SrcRect.Bottom = Source.myHeight; 00295 } 00296 00297 // Then find the valid bounds of the destination rectangle 00298 int Width = SrcRect.GetWidth(); 00299 int Height = SrcRect.GetHeight(); 00300 if (DestX + Width > myWidth) Width = myWidth - DestX; 00301 if (DestY + Height > myHeight) Height = myHeight - DestY; 00302 00303 // Make sure the destination area is valid 00304 if ((Width <= 0) || (Height <= 0)) 00305 return; 00306 00307 // Precompute as much as possible 00308 int Pitch = Width * 4; 00309 int Rows = Height; 00310 int SrcStride = Source.myWidth * 4; 00311 int DstStride = myWidth * 4; 00312 const Uint8* SrcPixels = Source.GetPixelsPtr() + (SrcRect.Left + SrcRect.Top * Source.myWidth) * 4; 00313 Uint8* DstPixels = reinterpret_cast<Uint8*>(&myPixels[0]) + (DestX + DestY * myWidth) * 4; 00314 00315 // Copy the pixels 00316 if (ApplyAlpha) 00317 { 00318 // Interpolation using alpha values, pixel by pixel (slower) 00319 for (int i = 0; i < Rows; ++i) 00320 { 00321 for (int j = 0; j < Width; ++j) 00322 { 00323 // Get a direct pointer to the components of the current pixel 00324 const Uint8* Src = SrcPixels + j * 4; 00325 Uint8* Dst = DstPixels + j * 4; 00326 00327 // Interpolate RGB components using the alpha value of the source pixel 00328 Uint8 Alpha = Src[3]; 00329 Dst[0] = (Src[0] * Alpha + Dst[0] * (255 - Alpha)) / 255; 00330 Dst[1] = (Src[1] * Alpha + Dst[1] * (255 - Alpha)) / 255; 00331 Dst[2] = (Src[2] * Alpha + Dst[2] * (255 - Alpha)) / 255; 00332 } 00333 00334 SrcPixels += SrcStride; 00335 DstPixels += DstStride; 00336 } 00337 } 00338 else 00339 { 00340 // Optimized copy ignoring alpha values, row by row (faster) 00341 for (int i = 0; i < Rows; ++i) 00342 { 00343 memcpy(DstPixels, SrcPixels, Pitch); 00344 SrcPixels += SrcStride; 00345 DstPixels += DstStride; 00346 } 00347 } 00348 00349 // The texture will need an update 00350 myNeedTextureUpdate = true; 00351 } 00352 00353 00358 bool Image::CopyScreen(RenderWindow& Window, const IntRect& SourceRect) 00359 { 00360 // Adjust the source rectangle 00361 IntRect SrcRect = SourceRect; 00362 if (SrcRect.GetWidth() == 0 || (SrcRect.GetHeight() == 0)) 00363 { 00364 SrcRect.Left = 0; 00365 SrcRect.Top = 0; 00366 SrcRect.Right = Window.GetWidth(); 00367 SrcRect.Bottom = Window.GetHeight(); 00368 } 00369 else 00370 { 00371 if (SrcRect.Left < 0) SrcRect.Left = 0; 00372 if (SrcRect.Top < 0) SrcRect.Top = 0; 00373 if (SrcRect.Right > static_cast<int>(Window.GetWidth())) SrcRect.Right = Window.GetWidth(); 00374 if (SrcRect.Bottom > static_cast<int>(Window.GetHeight())) SrcRect.Bottom = Window.GetHeight(); 00375 } 00376 00377 // Store the texture dimensions 00378 myWidth = SrcRect.GetWidth(); 00379 myHeight = SrcRect.GetHeight(); 00380 00381 // We can then create the texture 00382 if (Window.SetActive() && CreateTexture()) 00383 { 00384 GLint PreviousTexture; 00385 GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture)); 00386 00387 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00388 GLCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, SrcRect.Left, SrcRect.Top, myWidth, myHeight)); 00389 00390 GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture)); 00391 00392 myNeedTextureUpdate = false; 00393 myNeedArrayUpdate = true; 00394 00395 return true; 00396 } 00397 else 00398 { 00399 Reset(); 00400 return false; 00401 } 00402 } 00403 00404 00408 void Image::SetPixel(unsigned int X, unsigned int Y, const Color& Col) 00409 { 00410 // First check if the array of pixels needs to be updated 00411 EnsureArrayUpdate(); 00412 00413 // Check if pixel is whithin the image bounds 00414 if ((X >= myWidth) || (Y >= myHeight)) 00415 { 00416 std::cerr << "Cannot set pixel (" << X << "," << Y << ") for image " 00417 << "(width = " << myWidth << ", height = " << myHeight << ")" << std::endl; 00418 return; 00419 } 00420 00421 myPixels[X + Y * myWidth] = Col; 00422 00423 // The texture will need to be updated 00424 myNeedTextureUpdate = true; 00425 } 00426 00427 00431 const Color& Image::GetPixel(unsigned int X, unsigned int Y) const 00432 { 00433 // First check if the array of pixels needs to be updated 00434 EnsureArrayUpdate(); 00435 00436 // Check if pixel is whithin the image bounds 00437 if ((X >= myWidth) || (Y >= myHeight)) 00438 { 00439 std::cerr << "Cannot get pixel (" << X << "," << Y << ") for image " 00440 << "(width = " << myWidth << ", height = " << myHeight << ")" << std::endl; 00441 return Color::Black; 00442 } 00443 00444 return myPixels[X + Y * myWidth]; 00445 } 00446 00447 00453 const Uint8* Image::GetPixelsPtr() const 00454 { 00455 // First check if the array of pixels needs to be updated 00456 EnsureArrayUpdate(); 00457 00458 if (!myPixels.empty()) 00459 { 00460 return reinterpret_cast<const Uint8*>(&myPixels[0]); 00461 } 00462 else 00463 { 00464 std::cerr << "Trying to access the pixels of an empty image" << std::endl; 00465 return NULL; 00466 } 00467 } 00468 00469 00473 void Image::Bind() const 00474 { 00475 // First check if the texture needs to be updated 00476 EnsureTextureUpdate(); 00477 00478 // Bind it 00479 if (myTexture) 00480 { 00481 GLCheck(glEnable(GL_TEXTURE_2D)); 00482 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00483 } 00484 } 00485 00486 00490 void Image::SetSmooth(bool Smooth) 00491 { 00492 if (Smooth != myIsSmooth) 00493 { 00494 myIsSmooth = Smooth; 00495 00496 if (myTexture) 00497 { 00498 // Make sure we have a valid context 00499 priv::GraphicsContext Ctx; 00500 00501 GLint PreviousTexture; 00502 GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture)); 00503 00504 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00505 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST)); 00506 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST)); 00507 00508 GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture)); 00509 } 00510 } 00511 } 00512 00513 00517 unsigned int Image::GetWidth() const 00518 { 00519 return myWidth; 00520 } 00521 00522 00526 unsigned int Image::GetHeight() const 00527 { 00528 return myHeight; 00529 } 00530 00531 00535 bool Image::IsSmooth() const 00536 { 00537 return myIsSmooth; 00538 } 00539 00540 00545 FloatRect Image::GetTexCoords(const IntRect& Rect) const 00546 { 00547 float Width = static_cast<float>(myTextureWidth); 00548 float Height = static_cast<float>(myTextureHeight); 00549 00550 return FloatRect(Rect.Left / Width, 00551 Rect.Top / Height, 00552 Rect.Right / Width, 00553 Rect.Bottom / Height); 00554 } 00555 00556 00560 unsigned int Image::GetValidTextureSize(unsigned int Size) 00561 { 00562 // Make sure we have a valid context 00563 priv::GraphicsContext Ctx; 00564 00565 if (glewIsSupported("GL_ARB_texture_non_power_of_two") != 0) 00566 { 00567 // If hardware supports NPOT textures, then just return the unmodified size 00568 return Size; 00569 } 00570 else 00571 { 00572 // If hardware doesn't support NPOT textures, we calculate the nearest power of two 00573 unsigned int PowerOfTwo = 1; 00574 while (PowerOfTwo < Size) 00575 PowerOfTwo *= 2; 00576 00577 return PowerOfTwo; 00578 } 00579 } 00580 00581 00585 Image& Image::operator =(const Image& Other) 00586 { 00587 Image Temp(Other); 00588 00589 std::swap(myWidth, Temp.myWidth); 00590 std::swap(myHeight, Temp.myHeight); 00591 std::swap(myTextureWidth, Temp.myTextureWidth); 00592 std::swap(myTextureHeight, Temp.myTextureHeight); 00593 std::swap(myTexture, Temp.myTexture); 00594 std::swap(myIsSmooth, Temp.myIsSmooth); 00595 std::swap(myNeedArrayUpdate, Temp.myNeedArrayUpdate); 00596 std::swap(myNeedTextureUpdate, Temp.myNeedTextureUpdate); 00597 myPixels.swap(Temp.myPixels); 00598 00599 return *this; 00600 } 00601 00602 00606 bool Image::CreateTexture() 00607 { 00608 // Check if texture parameters are valid before creating it 00609 if (!myWidth || !myHeight) 00610 return false; 00611 00612 // Make sure we have a valid context 00613 priv::GraphicsContext Ctx; 00614 00615 // Adjust internal texture dimensions depending on NPOT textures support 00616 unsigned int TextureWidth = GetValidTextureSize(myWidth); 00617 unsigned int TextureHeight = GetValidTextureSize(myHeight); 00618 00619 // Check the maximum texture size 00620 GLint MaxSize; 00621 GLCheck(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &MaxSize)); 00622 if ((TextureWidth > static_cast<unsigned int>(MaxSize)) || (TextureHeight > static_cast<unsigned int>(MaxSize))) 00623 { 00624 std::cerr << "Failed to create image, its internal size is too high (" << TextureWidth << "x" << TextureHeight << ")" << std::endl; 00625 return false; 00626 } 00627 00628 // Destroy the previous OpenGL texture if it already exists with another size 00629 if ((TextureWidth != myTextureWidth) || (TextureHeight != myTextureHeight)) 00630 { 00631 DestroyTexture(); 00632 myTextureWidth = TextureWidth; 00633 myTextureHeight = TextureHeight; 00634 } 00635 00636 // Create the OpenGL texture 00637 if (!myTexture) 00638 { 00639 GLint PreviousTexture; 00640 GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture)); 00641 00642 GLuint Texture = 0; 00643 GLCheck(glGenTextures(1, &Texture)); 00644 GLCheck(glBindTexture(GL_TEXTURE_2D, Texture)); 00645 GLCheck(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, myTextureWidth, myTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); 00646 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)); 00647 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)); 00648 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST)); 00649 GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST)); 00650 myTexture = static_cast<unsigned int>(Texture); 00651 00652 GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture)); 00653 } 00654 00655 myNeedTextureUpdate = true; 00656 00657 return true; 00658 } 00659 00660 00665 void Image::EnsureTextureUpdate() const 00666 { 00667 if (myNeedTextureUpdate) 00668 { 00669 // Copy the pixels 00670 if (myTexture && !myPixels.empty()) 00671 { 00672 GLint PreviousTexture; 00673 GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture)); 00674 00675 // Update the texture with the pixels array in RAM 00676 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00677 GLCheck(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, myWidth, myHeight, GL_RGBA, GL_UNSIGNED_BYTE, &myPixels[0])); 00678 00679 GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture)); 00680 } 00681 00682 myNeedTextureUpdate = false; 00683 } 00684 } 00685 00686 00691 void Image::EnsureArrayUpdate() const 00692 { 00693 if (myNeedArrayUpdate) 00694 { 00695 // Save the previous texture 00696 GLint PreviousTexture; 00697 GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture)); 00698 00699 // Resize the destination array of pixels 00700 myPixels.resize(myWidth * myHeight); 00701 00702 if ((myWidth == myTextureWidth) && (myHeight == myTextureHeight)) 00703 { 00704 // Texture and array have the same size, we can use a direct copy 00705 00706 // Copy pixels from texture to array 00707 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00708 GLCheck(glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &myPixels[0])); 00709 } 00710 else 00711 { 00712 // Texture and array don't have the same size, we have to use a slower algorithm 00713 00714 // All the pixels will first be copied to a temporary array 00715 std::vector<Color> AllPixels(myTextureWidth * myTextureHeight); 00716 GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture)); 00717 GLCheck(glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &AllPixels[0])); 00718 00719 // The we copy the useful pixels from the temporary array to the final one 00720 const Color* Src = &AllPixels[0]; 00721 Color* Dst = &myPixels[0]; 00722 for (unsigned int i = 0; i < myHeight; ++i) 00723 { 00724 std::copy(Src, Src + myWidth, Dst); 00725 Src += myTextureWidth; 00726 Dst += myWidth; 00727 } 00728 } 00729 00730 // Restore the previous texture 00731 GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture)); 00732 00733 myNeedArrayUpdate = false; 00734 } 00735 } 00736 00737 00741 void Image::Reset() 00742 { 00743 DestroyTexture(); 00744 00745 myWidth = 0; 00746 myHeight = 0; 00747 myTextureWidth = 0; 00748 myTextureHeight = 0; 00749 myTexture = 0; 00750 myIsSmooth = true; 00751 myNeedTextureUpdate = false; 00752 myNeedArrayUpdate = false; 00753 myPixels.clear(); 00754 } 00755 00756 00760 void Image::DestroyTexture() 00761 { 00762 // Destroy the internal texture 00763 if (myTexture) 00764 { 00765 // Make sure we have a valid context 00766 priv::GraphicsContext Ctx; 00767 00768 GLuint Texture = static_cast<GLuint>(myTexture); 00769 GLCheck(glDeleteTextures(1, &Texture)); 00770 myTexture = 0; 00771 myNeedTextureUpdate = false; 00772 myNeedArrayUpdate = false; 00773 } 00774 } 00775 00776 } // namespace sf
:: Copyright © 2007-2008 Laurent Gomila, all rights reserved :: Documentation generated by doxygen 1.5.2 ::