[HOpenGL] Texture Mapping??

Claude Heiland-Allen claudiusmaximus at goto10.org
Sun Jul 11 17:47:57 EDT 2010


Hi again Hector,

On 11/07/10 16:30, Hector Guilarte wrote:
> Hello Claude,

Sorry for the delay - some things came up.

> My problem is everywhere hahaha. I haven't been able to do anything with
> textures, but those steps do sound like the way to go. Maybe if you
> could explain them a little bit more I'll do it, or maybe with the
> examples you mentioned I'll get it myself.

Ok, here is a dump of relevant portions of my code.  They all require 
(and other imports are noted above the snippets):

import Graphics.UI.GLUT

> When you say 'load' is the hardest: In all the tutorials or codes I've
> seen, I haven't been able to find any function which has that kind of
> signature, but I really have no idea on how to do such function myself
> :s. I really thought maybe OpenGL provided some functions to do it...

The main problem is the variety of different image formats - most are 
compressed which means using corresponding libraries to decompress them 
- and even for uncompressed images they usually have a header with 
useful information such as width, height, channels.

-- For a file containing raw RGBA pixels (1 byte per channel)
-- hardcoded to 1024x1024x4 widthXheightXchannels):
-- this actually does load + upload in one function
import Control.Monad(when)
import System.Exit(exitFailure)
import System.IO(withBinaryFile, IOMode(ReadMode), openBinaryFile, hGetBuf)
import Foreign.Marshal.Alloc(allocaBytes)

loadTexture :: FilePath -> IO TextureObject
loadTexture f = do
   withBinaryFile f ReadMode $ \h -> do
     let bytes = 1024 * 1024 * 4
     allocaBytes bytes $ \pixels -> do
       bytes' <- hGetBuf h pixels bytes
       when (bytes' /= bytes) exitFailure
       [tex] <- genObjectNames 1
       texture Texture2D $= Enabled
       textureBinding Texture2D $= Just tex
       build2DMipmaps Texture2D RGBA' 1024 1024
         (PixelData RGBA UnsignedByte pixels)
       textureFilter Texture2D $= ((Linear', Just Linear'), Linear')
       textureWrapMode Texture2D S $= (Repeated, ClampToEdge)
       textureWrapMode Texture2D T $= (Repeated, ClampToEdge)
       textureBinding Texture2D $= Nothing
       texture Texture2D $= Disabled
       return tex


-- To convert SVG via PNG via PPM/PGM to a suitable raw format I use
-- a simple bash script (I called it "svg2rgba.sh")
-- warning: this assumes that the SVG has width = height = 1024 pixels
-- the magic numbers are 3 * width * height and 1 * width * height
-- given "foo.svg", convert it to "foo.rgba" by running:
--   ./svg2rgba.sh foo
#!/bin/bash
SVGFILE="${1}.svg"
PNGFILE="${1}.png"
PPMFILE="${1}.ppm"
PGMFILE="${1}.pgm"
RGBFILE="${1}.rgb"
AFILE="${1}.a"
RGBAFILE="${1}.rgba"
rsvg "${SVGFILE}" "${PNGFILE}"
pngtopnm        "${PNGFILE}" > "${PPMFILE}"
pngtopnm -alpha "${PNGFILE}" > "${PGMFILE}"
tail -c 3145728 "${PPMFILE}" > "${RGBFILE}"
tail -c 1048576 "${PGMFILE}" > "${AFILE}"
./interleave31 "${RGBFILE}" "${AFILE}" > "${RGBAFILE}"
rm -f "${PNGFILE}" "${PPMFILE}" "${PGMFILE}" "${RGBFILE}" "${AFILE}"


-- to interleave the raw data I use this C code
-- again hardcoded to width = height = 1024
-- error checking and speed leaves much to be desired
-- save as "interleave31.c"
-- compile with: gcc -o interleave31 interleave31.c
#include <stdio.h>
int main(int argc, char **argv) {
   if (argc != 3) {
     return 1;
   }
   FILE *in3 = fopen(argv[1], "rb");
   if (in3) {
     FILE *in1 = fopen(argv[2], "rb");
     if (in1) {
       for (int i = 0; i < 1024*1024; ++i) {
         putchar(getc(in3));
         putchar(getc(in3));
         putchar(getc(in3));
         putchar(getc(in1));
       }
       fclose(in1);
     } else {
       return 1;
     }
     fclose(in3);
   } else {
     return 1;
   }
   return 0;
}


> upload :: .... -- upload to the Graphics Card?
> I've used OpenGL in C++ and adding a texture is so easy, Probably I did
> all 3 steps you mention, but I never really worried about them, I just
> did it without paying attention on what was happening underneath.

import Foreign.Ptr(Ptr())
import Foreign.Marshal.Alloc(mallocBytes)

data Image = Image{ iWidth, iHeight, iChannels :: Int, iBuffer :: Ptr () }

image :: Int -> Int -> Int -> IO Image
image w h c
   | w > 0 && h > 0 && c > 0 = do
     b <- mallocBytes $ w * h * c
     return Image{ iWidth = w, iHeight = h, iChannels = c, iBuffer = b }
   | otherwise = error $ "Image.image: " ++ show [w,h,c]

upload :: Image -> IO TextureObject
upload i
   | iChannels i == 4 = do
     [tex] <- genObjectNames 1
     texture Texture2D $= Enabled
     textureBinding Texture2D $= Just tex
     build2DMipmaps Texture2D RGBA' (fromIntegral $ iWidth i) 
(fromIntegral $ iHeight i) (PixelData RGBA UnsignedByte (iBuffer i))
     textureFilter  Texture2D $= ((Linear', Just Linear'), Linear')
     textureWrapMode Texture2D S $= (Repeated, ClampToEdge)
     textureWrapMode Texture2D T $= (Repeated, ClampToEdge)
     textureBinding Texture2D $= Nothing
     texture Texture2D $= Disabled
     return tex
   | otherwise = error $ "Image.upload: " ++ show (iChannels i)

-- Should be simple to adapt the above to handle channels /= 4

> draw :: ... This does sounds like what I used to do in C++ and it's what
> I was hoping to find in Haskell...

data Quad = Quad{ quadX, quadY, quadR :: GLdouble, quadT :: TextureObject }

-- cannot rebind texture within renderPrimitive
-- might be more efficient to use 1 large texture
-- with subportions of that 1 texture for each quad
drawQuad :: Quad -> IO ()
drawQuad Quad{ quadX = x0, quadY = y0, quadR = s, quadT = tex } = do
   let t x y = texCoord $ TexCoord2 (x :: GLdouble) (y :: GLdouble)
       v x y = vertex   $ Vertex2 x y
   textureBinding Texture2D $= Just tex
   renderPrimitive Quads $ do
     color $ Color3 1 1 (1::GLdouble)
     t 0 1 >> v (x0 - s) (y0 + s)
     t 0 0 >> v (x0 - s) (y0 - s)
     t 1 0 >> v (x0 + s) (y0 - s)
     t 1 1 >> v (x0 + s) (y0 + s)

drawQuads :: [Quad] -> IO ()
drawQuads qs = do
   blend $= Enabled
   blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
   texture Texture2D $= Enabled
   mapM_ drawQuad qs
   textureBinding Texture2D $= Nothing
   texture Texture2D $= Disabled
   blend $= Disabled

> Thank you,

Hoping this helps,

> Hector Guilarte


Claude

>
> On Sun, Jul 11, 2010 at 10:06 AM, Claude Heiland-Allen
> <claudiusmaximus at goto10.org <mailto:claudiusmaximus at goto10.org>> wrote:
>
>     Hi Hector,
>
>
>     On 11/07/10 03:02, Hector Guilarte wrote:
>      > I've been looking for a way to map a texture into a Quad
>     primitive with
>      > HOpenGL with no luck.
>     [snip]
>
>     I'm having trouble understanding where your problem is.
>
>     There are three steps to texturing using an image file:
>
>     load   :: FilePath -> IO Image      -- read file to raw pixel buffer
>     upload :: Image -> IO TextureObject -- upload to graphics card
>     draw   :: TextureObject -> IO ()    -- use the texture on something
>
>     where
>     data Image = Image{ width, height, channels :: Int, pixels :: Ptr () }
>
>     Which step is problematic?
>
>     'load' is the hardest IMO, and doesn't have anything to do with OpenGL.
>     'upload' I would implement with 'build2DMipmaps'
>     'draw' is easy: bind the texture, add a 'texCoord' before each 'vertex'
>
>     I could provide some example code for 'upload' and 'draw' if you
>     think it would help.
>
>
>     Claude
>     --
>     http://claudiusmaximus.goto10.org
>
>     _______________________________________________
>     HOpenGL mailing list
>     HOpenGL at haskell.org <mailto:HOpenGL at haskell.org>
>     http://www.haskell.org/mailman/listinfo/hopengl
>
>



More information about the HOpenGL mailing list