# Soya 3D
# Copyright (C) 2003-2004 Jean-Baptiste LAMY -- jiba@tuxfamily.org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


def open_image(filename):
  """open_image(filename) -> Image

Creates and returns a Soya image from file FILENAME.
The image is loaded with PIL; the supported image formats are the same than PIL."""
  cdef _Image image
  import PIL.Image
  image = image_from_pil(PIL.Image.open(filename))
  image._filename = filename
  return image

def image_from_pil(pil_image):
  """image_from_pil(pil_image) -> Image

Creates and returns a Soya image from PIL image PIL_IMAGE."""
  cdef _Image image
  image = Image()
  image.width, image.height = pil_image.size
  if   pil_image.mode == "RGB" : image.nb_color = 3
  elif pil_image.mode == "RGBA": image.nb_color = 4
  elif pil_image.mode == "L"   : image.nb_color = 1
  elif pil_image.mode == "P"   : image.nb_color = 1 # XXX Palette may not be monochrome
  else: raise ValueError("unknown image mode (%s)" % pil_image.mode)
  image.pixels = pil_image.tostring()
  return image

def screenshot(filename = None, int x = 0, int y = 0, int width = 0, int height = 0):
  """screenshot(filename = None, x = 0, y = 0, width = soya.get_screen_width(), height = soya.get_screen_height()) -> PIL.Image.Image

Take a screenshot of the soya rendering screen, and return it as a PIL image object.
If given, the image is saved under FILENAME.
You must have rendered something before (e.g. by calling soya.render, or by running
an Idler)."""
  import PIL.Image
  cdef GLubyte* pixels
  cdef int      size
  width  = width  or renderer.screen_width
  height = height or renderer.screen_height
  size   = 3 * width * height
  pixels = <GLubyte*> malloc(size * sizeof(GLubyte))
  glReadBuffer(GL_FRONT)
  glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels)
  image = PIL.Image.fromstring("RGB", (width, height), PyString_FromStringAndSize(<char*> pixels, size))
  image = image.transpose(PIL.Image.FLIP_TOP_BOTTOM)
  free(pixels)
  if not filename is None: image.save(filename)
  return image


# XXX indexed images are not supported yet (not sure)

cdef class _Image(_CObj):
  """Image object, used in particular for texture."""

  #cdef readonly int nb_color, width, height
  #cdef GLubyte* _pixels
  #cdef public  _filename
  
  def __init__(self, pixels = None, int width = 0, int height = 0, int nb_color = 0):
    """Image(pixels = None, int width = 0, int height = 0, int nb_color = 0) -> Image

Creates a Soya image of dimension WIDTH and HEIGHT, with NB_COLOR color channels
(4 for RGBA), and with data PIXELS (a raw image in a string).

See Image.get(filename), open_image(filename) or image_from_pil(pil_image) for higher
level image loading functions."""
    if nb_color == 0:
      self._pixels = NULL
    else:
      if pixels is None:
        self._pixels  = <GLubyte*> malloc(width * height * nb_color * sizeof(GLubyte))
      else:
        self._pixels  = <GLubyte*> dup(PyString_AS_STRING(pixels), width * height * nb_color)
      self.width    = width
      self.height   = height
      self.nb_color = nb_color
      
  def __dealloc__(self):
    if self._pixels != NULL: free(self._pixels)
    
  cdef __getcstate__(self):
    #return struct.pack("<iii", self.nb_color, self.width, self.height), self.pixels
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_int_endian_safe  (chunk, self.nb_color)
    chunk_add_int_endian_safe  (chunk, self.width)
    chunk_add_int_endian_safe  (chunk, self.height)
    chunk_add_chars_endian_safe(chunk, <char*> self._pixels, self.nb_color * self.width * self.height)
    return drop_chunk_to_string(chunk), self._filename
  
  cdef void __setcstate__(self, cstate):
#    self.nb_color, self.width, self.height = struct.unpack("<iii", cstate[0])
#    self.pixels = cstate[1]
    if isinstance(cstate, tuple):
      self._filename = cstate[1]
      cstate         = cstate[0]
    cdef Chunk* chunk
    chunk = string_to_chunk(cstate)
    chunk_get_int_endian_safe(chunk, &self.nb_color)
    chunk_get_int_endian_safe(chunk, &self.width)
    chunk_get_int_endian_safe(chunk, &self.height)
    self._pixels = <GLubyte*> malloc(self.nb_color * self.width * self.height * sizeof(GLubyte))
    chunk_get_chars_endian_safe(chunk, <char*> self._pixels, self.nb_color * self.width * self.height)
    drop_chunk(chunk)
    
  def __deepcopy__(self, memo):
    """Images are immutable."""
    return self
  
  property pixels:
    def __get__(self):
      return PyString_FromStringAndSize(<char*> self._pixels, self.nb_color * self.width * self.height)
    def __set__(self, string):
      self._pixels = <GLubyte*> dup(PyString_AS_STRING(string), self.nb_color * self.width * self.height)
      
  def has_border(self): return self.check_for_gl() == 2
  
  cdef int check_for_gl(self):
    """_Image.check_for_gl() -> int

Returns 1 if the image is suitable for OpenGL (=if both dimension are power of 2).
Returns 2 if the image is suitable for OpenGL, with a border (=if both dimension are power of 2 + 2 (for the border)).
Else returns 0."""
    if ( self.width      == power_of_2(self.width    )) and ( self.height      == power_of_2(self.height    )): return 1
    if ((self.width - 2) == power_of_2(self.width - 2)) and ((self.height - 2) == power_of_2(self.height - 2)): return 2
    return 0
    
#     cdef int i, j, n
#     i = 1
#     j = 1
#     n = 1
#     while((n <= self.width) or (n <= self.height)):
#       if (i == 1) and (self.width  == n): i = 0
#       if (j == 1) and (self.height == n): j = 0
#       n = n << 1
#     if (i == 0) and (j == 0): return 1
    
    
  cdef GLuint typ(self):
    """_Image.typ() -> GLuint

Returns the image type, depending on the number of color channels,
i.e. either GL_LUMINANCE, GL_RGB or GL_RGBA."""
    if   self.nb_color == 1: return GL_LUMINANCE
    elif self.nb_color == 3: return GL_RGB
    elif self.nb_color == 4: return GL_RGBA
    raise ValueError("unknown image number of color (%s)" % self.nb_color)
  
  cdef GLuint internal_format(self):
    """_Image.internal_format() -> GLuint

Returns the image internal format, depending on the number of color channels and the
rendering quality."""
    if   quality == QUALITY_LOW:
      if   self.nb_color == 1: return GL_LUMINANCE
      elif self.nb_color == 3: return GL_RGB
      elif self.nb_color == 4: return GL_RGBA
    elif quality == QUALITY_MEDIUM:
      if   self.nb_color == 1: return GL_LUMINANCE8
      elif self.nb_color == 3: return GL_RGB8
      elif self.nb_color == 4: return GL_RGBA8
    elif quality == QUALITY_HIGH:
      if   self.nb_color == 1: return GL_LUMINANCE16
      elif self.nb_color == 3: return GL_RGB16
      elif self.nb_color == 4: return GL_RGBA16
    raise ValueError("unknown image number of color (%s)" % self.nb_color)
  
  def __repr__(self): return "<Image %s>" % self._filename
  
  def to_pil(self):
    import PIL.Image
    if   self.nb_color == 1: return PIL.Image.fromstring("L"   , (self.width, self.height), self.pixels)
    elif self.nb_color == 3: return PIL.Image.fromstring("RGB" , (self.width, self.height), self.pixels)
    elif self.nb_color == 4: return PIL.Image.fromstring("RGBA", (self.width, self.height), self.pixels)
    

cdef int power_of_2(int i):
  if i <= 1   : return    1
  if i <= 2   : return    2
  if i <= 4   : return    4
  if i <= 8   : return    8
  if i <= 16  : return   16
  if i <= 32  : return   32
  if i <= 64  : return   64
  if i <= 128 : return  128
  if i <= 256 : return  256
  if i <= 512 : return  512
  if i <= 1024: return 1024
  if i <= 2048: return 2048
  return 4096
  
cdef GLubyte* pixels_scale_down_2(int nb_color, GLuint* w, GLuint* h, int border, GLubyte* pixels):
  """pixels_scale_down_2(int nb_color, GLuint* w, GLuint* h, int border, GLubyte* pixels) -> GLubyte*

Returns a reduced version of an image. Each dimension is divided by 2.
The image is given by PIXELS, NB_COLOR. W and H are pointers on the dimensions (i.e. the old image dimension at the beginning and then half of the image dimensions).
The returned image is malloc'ed, and must be free'd by the caller.
"""
  cdef int i, j, m, x, y
  cdef GLubyte* new_pixels

  if (w[0] == 1) or (h[0] == 1):
    if w[0] != 1: w[0] = w[0] >> 1
    if h[0] != 1: h[0] = h[0] >> 1
    new_pixels = <GLubyte*> malloc((h[0] + 2 * border) * (w[0] + 2 * border) * nb_color * sizeof(GLubyte))
    for j from 0 <= j < (h[0] + 2 * border):
      for i from 0 <= i < (w[0] + 2 * border):
        for m from 0 <= m < nb_color:
          new_pixels[(i + w[0] * j) * nb_color + m] = pixels[(i + w[0] * j) * nb_color * 2 + m]
  else:
    w[0] = w[0] >> 1
    h[0] = h[0] >> 1
    new_pixels = <GLubyte*> malloc((h[0] + 2 * border) * (w[0] + 2 * border) * nb_color * sizeof(GLubyte))
    #for i from 0 <= i < h[0] * w[0] * nb_color * sizeof(GLubyte):
    #  new_pixels[i] = 0
    for j from 0 <= j < (h[0] + 2 * border):
      y = 2 * j
      for i from 0 <= i < (w[0] + 2 * border):
        x = 2 * i
        for m from 0 <= m < nb_color:
          new_pixels[(i + w[0] * j) * nb_color + m] = <GLubyte> ((
            (<float> pixels[(x     + 2 * w[0] *  y)      * nb_color + m]) +
            (<float> pixels[(x + 1 + 2 * w[0] *  y)      * nb_color + m]) +
            (<float> pixels[(x     + 2 * w[0] * (y + 1)) * nb_color + m]) +
            (<float> pixels[(x + 1 + 2 * w[0] * (y + 1)) * nb_color + m])) / 4.0
            )
  return new_pixels


