text: /*
    pygame - Python Game Library
    Copyright (C) 2000-2001  Pete Shinners
  
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
  
    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
    Pete Shinners
    pete@shinners.org
  */
  
  #include <stddef.h>
  #include "pygame.h"
  
  #if IS_SDLv2
  #include "palette.h"
  #endif /* IS_SDLv2 */
  
  #include "pgcompat.h"
  
  #include "doc/pixelcopy_doc.h"
  
  #if IS_SDLv1
  #include <SDL_byteorder.h>
  #else /* IS_SDLv2 */
  #include <SDL_endian.h>
  #endif /* IS_SDLv2 */
  
  typedef enum {
      VIEWKIND_RED,
      VIEWKIND_GREEN,
      VIEWKIND_BLUE,
      VIEWKIND_ALPHA,
      VIEWKIND_COLORKEY,
      VIEWKIND_RGB
  } _pc_view_kind_t;
  
  typedef union {
      Uint32 value;
      Uint8 bytes[sizeof(Uint32)];
  } _pc_pixel_t;
  
  static int
  _validate_view_format(const char *format)
  {
      int i = 0;
  
      /* Skip size/byte order prefix or item count */
      switch (format[i]) {
          case '@':
          case '=':
          case '<':
          case '>':
          case '!':
              ++i;
              break;
          case '2':
          case '3':
          case '4':
          case '5':
          case '6':
          case '7':
          case '8':
          case '9':
              /* Only allowed for fill bytes */
              if (format[i + 1] == 'x') {
                  ++i;
              }
              break;
  
              /* default: assume it is a format character */
      }
      /* an item count of 1 is accepted */
      if (format[i] == '1') {
          ++i;
      }
      switch (format[i]) {
          case 'x':
          case 'b':
          case 'B':
          case 'h':
          case 'H':
          case 'i':
          case 'I':
          case 'l':
          case 'L':
          case 'q':
          case 'Q':
              ++i;
              break;
  
              /* default: unrecognized character; raise error later */
      }
      if (format[i] != '\0') {
          PyErr_SetString(PyExc_ValueError, "Unsupport array item type");
          return -1;
      }
  
      return 0;
  }
  
  static int
  _is_swapped(Py_buffer *view_p)
  {
      char ch = view_p->format[0];
  
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      if (ch == '>' || ch == '!') {
          return 1;
      }
  #else
      if (ch == '<') {
          return 1;
      }
  #endif
      return 0;
  }
  
  static int
  _view_kind(PyObject *obj, void *view_kind_vptr)
  {
      unsigned long ch;
      _pc_view_kind_t *view_kind_ptr = (_pc_view_kind_t *)view_kind_vptr;
  
      if (PyUnicode_Check(obj)) {
          if (PyUnicode_GET_SIZE(obj) != 1) {
              PyErr_SetString(PyExc_TypeError,
                              "expected a length 1 string for argument 3");
              return 0;
          }
          ch = *PyUnicode_AS_UNICODE(obj);
      }
      else if (Bytes_Check(obj)) {
          if (Bytes_GET_SIZE(obj) != 1) {
              PyErr_SetString(PyExc_TypeError,
                              "expected a length 1 string for argument 3");
              return 0;
          }
          ch = *Bytes_AS_STRING(obj);
      }
      else {
          PyErr_Format(PyExc_TypeError,
                       "expected a length one string for argument 3: got '%s'",
                       Py_TYPE(obj)->tp_name);
          return 0;
      }
      switch (ch) {
          case 'R':
          case 'r':
              *view_kind_ptr = VIEWKIND_RED;
              break;
          case 'G':
          case 'g':
              *view_kind_ptr = VIEWKIND_GREEN;
              break;
          case 'B':
          case 'b':
              *view_kind_ptr = VIEWKIND_BLUE;
              break;
          case 'A':
          case 'a':
              *view_kind_ptr = VIEWKIND_ALPHA;
              break;
          case 'C':
          case 'c':
              *view_kind_ptr = VIEWKIND_COLORKEY;
              break;
          case 'P':
          case 'p':
              *view_kind_ptr = VIEWKIND_RGB;
              break;
          default:
              PyErr_Format(PyExc_TypeError,
                           "unrecognized view kind '%c' for argument 3",
                           (int)ch);
              return 0;
      }
      return 1;
  }
  
  static int
  _copy_mapped(Py_buffer *view_p, SDL_Surface *surf)
  {
      int pixelsize = surf->format->BytesPerPixel;
      int intsize = view_p->itemsize;
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      char *src = (char *)surf->pixels;
  #else
      char *src = (char *)surf->pixels + pixelsize - 1;
  #endif
      char *dst = (char *)view_p->buf;
      int w = surf->w;
      int h = surf->h;
      Py_intptr_t dx_src = surf->format->BytesPerPixel;
      Py_intptr_t dy_src = surf->pitch;
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      Py_intptr_t dz_src = 1;
  #else
      Py_intptr_t dz_src = -1;
  #endif
      Py_intptr_t dx_dst = view_p->strides[0];
      Py_intptr_t dy_dst = view_p->strides[1];
      Py_intptr_t dz_dst = 1;
      Py_intptr_t x, y, z;
  
      if (view_p->shape[0] != w || view_p->shape[1] != h) {
          PyErr_Format(PyExc_ValueError,
                       "Expected a (%d, %d) target: got (%d, %d)", w, h,
                       (int)view_p->shape[0], (int)view_p->shape[1]);
          return -1;
      }
      if (intsize < pixelsize) {
          PyErr_Format(PyExc_ValueError,
                       "Expected at least a target byte size of %d: got %d",
                       pixelsize, intsize);
          return -1;
      }
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      if (_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #else
      if (!_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #endif
      for (x = 0; x < w; ++x) {
          for (y = 0; y < h; ++y) {
              for (z = 0; z < pixelsize; ++z) {
                  dst[dx_dst * x + dy_dst * y + dz_dst * z] =
                      src[dx_src * x + dy_src * y + dz_src * z];
              }
              while (z < intsize) {
                  dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
                  ++z;
              }
          }
      }
  
      return 0;
  }
  
  static int
  _copy_colorplane(Py_buffer *view_p, SDL_Surface *surf,
                   _pc_view_kind_t view_kind, Uint8 opaque, Uint8 clear)
  {
      SDL_PixelFormat *format = surf->format;
      int pixelsize = surf->format->BytesPerPixel;
  #if IS_SDLv1
      Uint32 flags = surf->flags;
  #else /* IS_SDLv2 */
      SDL_BlendMode mode;
  #endif /* IS_SDLv2 */
      int intsize = (int)view_p->itemsize;
      char *src = (char *)surf->pixels;
      char *dst = (char *)view_p->buf;
      int w = surf->w;
      int h = surf->h;
      Py_intptr_t dx_src = surf->format->BytesPerPixel;
      Py_intptr_t dy_src = surf->pitch;
      Py_intptr_t dx_dst = view_p->strides[0];
      Py_intptr_t dy_dst = view_p->strides[1];
      Py_intptr_t dz_dst = 1;
      Py_intptr_t dz_pix;
      Py_intptr_t x, y, z;
      Uint8 r, g, b, a;
      Uint8 *element = 0;
      _pc_pixel_t pixel = {0};
      Uint32 colorkey;
  
      if (view_p->shape[0] != w || view_p->shape[1] != h) {
          PyErr_Format(PyExc_ValueError,
                       "Expected a (%d, %d) target: got (%d, %d)", w, h,
                       (int)view_p->shape[0], (int)view_p->shape[1]);
          return -1;
      }
      if (intsize < 1) {
          PyErr_Format(PyExc_ValueError,
                       "Expected at least a target byte size of 1: got %d",
                       intsize);
          return -1;
      }
  #if IS_SDLv2
      if (SDL_GetSurfaceBlendMode(surf, &mode) < 0) {
          PyErr_SetString(pgExc_SDLError, SDL_GetError());
          return -1;
      }
  #endif
      /* Select appropriate color plane element within the pixel */
      switch (view_kind) {
          case VIEWKIND_RED:
              element = &r;
              break;
          case VIEWKIND_GREEN:
              element = &g;
              break;
          case VIEWKIND_BLUE:
              element = &b;
              break;
          default: /* VIEWKIND_ALPHA or VIEWKIND_COLORKEY */
              /* element is unused for VIEWKIND_COLORKEY */
              assert(view_kind == VIEWKIND_ALPHA ||
                     view_kind == VIEWKIND_COLORKEY);
              element = &a;
      }
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      dz_pix = 0;
      if (_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #else
      dz_pix = (unsigned)(sizeof(Uint32) - pixelsize);
      if (!_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #endif
  #if IS_SDLv1
      if (view_kind == VIEWKIND_COLORKEY && flags & SDL_SRCCOLORKEY) {
          colorkey = format->colorkey;
  #else  /* IS_SDLv2 */
      if (view_kind == VIEWKIND_COLORKEY &&
          SDL_GetColorKey(surf, &colorkey) == 0) {
  #endif /* IS_SDLv2 */
          for (x = 0; x < w; ++x) {
              for (y = 0; y < h; ++y) {
                  for (z = 0; z < pixelsize; ++z) {
                      pixel.bytes[dz_pix + z] = src[dx_src * x + dy_src * y + z];
                  }
                  dst[dx_dst * x + dy_dst * y] =
                      pixel.value == colorkey ? clear : opaque;
                  for (z = 1; z < intsize; ++z) {
                      dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
                  }
              }
          }
      }
  #if IS_SDLv1
      else if ((view_kind != VIEWKIND_COLORKEY) &&
               (view_kind != VIEWKIND_ALPHA || flags & SDL_SRCALPHA)) {
  #else  /* IS_SDLv2 */
      else if ((view_kind != VIEWKIND_COLORKEY) &&
               (view_kind != VIEWKIND_ALPHA || mode != SDL_BLENDMODE_NONE)) {
  #endif /* IS_SDLv2 */
          for (x = 0; x < w; ++x) {
              for (y = 0; y < h; ++y) {
                  for (z = 0; z < pixelsize; ++z) {
                      pixel.bytes[dz_pix + z] = src[dx_src * x + dy_src * y + z];
                  }
                  SDL_GetRGBA(pixel.value, format, &r, &g, &b, &a);
                  dst[dx_dst * x + dy_dst * y] = *element;
                  for (z = 1; z < intsize; ++z) {
                      dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
                  }
              }
          }
      }
      else {
          for (x = 0; x < w; ++x) {
              for (y = 0; y < h; ++y) {
                  dst[dx_dst * x + dy_dst * y] = opaque;
                  for (z = 1; z < intsize; ++z) {
                      dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
                  }
              }
          }
      }
  
      return 0;
  }
  
  static int
  _copy_unmapped(Py_buffer *view_p, SDL_Surface *surf)
  {
      SDL_PixelFormat *format = surf->format;
      int pixelsize = surf->format->BytesPerPixel;
      int intsize = (int)view_p->itemsize;
      char *src = (char *)surf->pixels;
      char *dst = (char *)view_p->buf;
      int w = surf->w;
      int h = surf->h;
      Py_intptr_t dx_src = surf->format->BytesPerPixel;
      Py_intptr_t dy_src = surf->pitch;
      Py_intptr_t dx_dst = view_p->strides[0];
      Py_intptr_t dy_dst = view_p->strides[1];
      Py_intptr_t dp_dst = view_p->strides[2];
      Py_intptr_t dz_dst = 1;
      Py_intptr_t dz_pix;
      Py_intptr_t x, y, z;
      _pc_pixel_t pixel = {0};
      Uint8 r, g, b;
  
      if (view_p->shape[0] != w || view_p->shape[1] != h ||
          view_p->shape[2] != 3) {
          PyErr_Format(PyExc_ValueError,
                       "Expected a (%d, %d, 3) target: got (%d, %d, %d)", w, h,
                       (int)view_p->shape[0], (int)view_p->shape[1],
                       (int)view_p->shape[2]);
          return -1;
      }
      if (intsize < 1) {
          PyErr_Format(PyExc_ValueError,
                       "Expected at least a target byte size of 1: got %d",
                       intsize);
          return -1;
      }
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      dz_pix = 0;
      if (_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #else
      dz_pix = (unsigned)(sizeof(Uint32) - pixelsize);
      if (!_is_swapped(view_p)) {
          dst += intsize - 1;
          dz_dst = -1;
      }
  #endif
      for (x = 0; x < w; ++x) {
          for (y = 0; y < h; ++y) {
              for (z = 0; z < pixelsize; ++z) {
                  pixel.bytes[dz_pix + z] = src[dx_src * x + dy_src * y + z];
              }
              SDL_GetRGB(pixel.value, format, &r, &g, &b);
              dst[dx_dst * x + dy_dst * y] = r;
              for (z = 1; z < intsize; ++z) {
                  dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
              }
              dst[dx_dst * x + dy_dst * y + dp_dst] = g;
              for (z = 1; z < intsize; ++z) {
                  dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
              }
              dst[dx_dst * x + dy_dst * y + 2 * dp_dst] = b;
              for (z = 1; z < intsize; ++z) {
                  dst[dx_dst * x + dy_dst * y + dz_dst * z] = 0;
              }
          }
      }
  
      return 0;
  }
  
  /*macros used to blit arrays*/
  #define COPYMACRO_2D(DST, SRC)                                               \
      for (loopy = 0; loopy < sizey; ++loopy) {                                \
          DST *imgrow = (DST *)(((char *)surf->pixels) + loopy * surf->pitch); \
          Uint8 *datarow = (Uint8 *)array_data + stridey * loopy;              \
          for (loopx = 0; loopx < sizex; ++loopx)                              \
              *(imgrow + loopx) = (DST) * (SRC *)(datarow + stridex * loopx);  \
      }
  
  #define COPYMACRO_3D(DST, SRC)                                              \
      for (loopy = 0; loopy < sizey; ++loopy) {                               \
          DST *pix = (DST *)(((char *)surf->pixels) + surf->pitch * loopy);   \
          char *data = array_data + stridey * loopy;                          \
          for (loopx = 0; loopx < sizex; ++loopx) {                           \
              *pix++ = (DST)((*(SRC *)(data) >> Rloss << Rshift) |            \
                             (*(SRC *)(data + stridez) >> Gloss << Gshift) |  \
                             (*(SRC *)(data + stridez2) >> Bloss << Bshift) | \
                             alpha);                                          \
              data += stridex;                                                \
          }                                                                   \
      }
  
  static PyObject *
  array_to_surface(PyObject *self, PyObject *arg)
  {
      PyObject *surfobj, *arrayobj;
      pg_buffer pg_view;
      Py_buffer *view_p = (Py_buffer *)&pg_view;
      char *array_data;
      SDL_Surface *surf;
      SDL_PixelFormat *format;
      int loopx, loopy;
      int stridex, stridey, stridez = 0, stridez2 = 0, sizex, sizey;
      int Rloss, Gloss, Bloss, Rshift, Gshift, Bshift;
  
      if (!PyArg_ParseTuple(arg, "O!O", &pgSurface_Type, &surfobj, &arrayobj)) {
          return NULL;
      }
      surf = pgSurface_AsSurface(surfobj);
      format = surf->format;
  
      if (pgObject_GetBuffer(arrayobj, &pg_view, PyBUF_RECORDS_RO)) {
          return 0;
      }
  
      if (_validate_view_format(view_p->format)) {
          return 0;
      }
  
      if (!(view_p->ndim == 2 || (view_p->ndim == 3 && view_p->shape[2] == 3))) {
          return RAISE(PyExc_ValueError, "must be a valid 2d or 3d array\n");
      }
  
      if (surf->format->BytesPerPixel == 0 || surf->format->BytesPerPixel > 4)
          return RAISE(PyExc_ValueError, "unsupport bit depth for surface");
  
      stridex = view_p->strides[0];
      stridey = view_p->strides[1];
      if (view_p->ndim == 3) {
          stridez = view_p->strides[2];
          stridez2 = stridez * 2;
      }
      else {
          stridez = 1;
          stridez2 = 2;
      }
      sizex = view_p->shape[0];
      sizey = view_p->shape[1];
      Rloss = format->Rloss;
      Gloss = format->Gloss;
      Bloss = format->Bloss;
      Rshift = format->Rshift;
      Gshift = format->Gshift;
      Bshift = format->Bshift;
  
      /* Do any required broadcasting. */
      if (sizex == 1) {
          sizex = surf->w;
          stridex = 0;
      }
      if (sizey == 1) {
          sizey = surf->h;
          stridey = 0;
      }
  
      if (sizex != surf->w || sizey != surf->h) {
          pgBuffer_Release(&pg_view);
          return RAISE(PyExc_ValueError, "array must match surface dimensions");
      }
      if (!pgSurface_LockBy(surfobj, arrayobj)) {
          pgBuffer_Release(&pg_view);
          return NULL;
      }
  
      array_data = (char *)view_p->buf;
  
      switch (surf->format->BytesPerPixel) {
          case 1:
              if (view_p->ndim == 2) {
                  switch (view_p->itemsize) {
                      case sizeof(Uint8):
                          COPYMACRO_2D(Uint8, Uint8);
                          break;
                      case sizeof(Uint16):
                          COPYMACRO_2D(Uint8, Uint16);
                          break;
                      case sizeof(Uint32):
                          COPYMACRO_2D(Uint8, Uint32);
                          break;
                      case sizeof(Uint64):
                          COPYMACRO_2D(Uint8, Uint64);
                          break;
                      default:
                          pgBuffer_Release(&pg_view);
                          if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                              return NULL;
                          }
                          return RAISE(PyExc_ValueError,
                                       "unsupported datatype for array\n");
                  }
              }
              else {
                  pgBuffer_Release(&pg_view);
                  if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                      return NULL;
                  }
                  return RAISE(PyExc_ValueError,
                               "unsupported datatype for array\n");
              }
              break;
          case 2:
              if (view_p->ndim == 2) {
                  switch (view_p->itemsize) {
                      case sizeof(Uint16):
                          COPYMACRO_2D(Uint16, Uint16);
                          break;
                      case sizeof(Uint32):
                          COPYMACRO_2D(Uint16, Uint32);
                          break;
                      case sizeof(Uint64):
                          COPYMACRO_2D(Uint16, Uint64);
                          break;
                      default:
                          pgBuffer_Release(&pg_view);
                          if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                              return NULL;
                          }
                          return RAISE(PyExc_ValueError,
                                       "unsupported datatype for array\n");
                  }
              }
              else {
                  Uint16 alpha = 0;
                  if (format->Amask) {
                      alpha = 255 >> format->Aloss << format->Ashift;
                  }
                  switch (view_p->itemsize) {
                      case sizeof(Uint8):
                          COPYMACRO_3D(Uint16, Uint8);
                          break;
                      case sizeof(Uint16):
                          COPYMACRO_3D(Uint16, Uint16);
                          break;
                      case sizeof(Uint32):
                          COPYMACRO_3D(Uint16, Uint32);
                          break;
                      case sizeof(Uint64):
                          COPYMACRO_3D(Uint16, Uint64);
                          break;
                      default:
                          pgBuffer_Release(&pg_view);
                          if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                              return NULL;
                          }
                          return RAISE(PyExc_ValueError,
                                       "unsupported datatype for array\n");
                  }
              }
              break;
          case 3:
              /* Assumption: The rgb components of a 24 bit pixel are in
                 separate bytes.
              */
              if (view_p->itemsize >= (view_p->ndim == 2 ? 3 : 1) &&
                  view_p->itemsize <= 9) {
                  size_t stridez_0 = 0;
                  size_t stridez_1 = 0;
                  size_t stridez_2 = 0;
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
                  if (view_p->ndim == 2) {
                      stridez_1 = 1;
                      stridez_2 = 2;
                  }
                  else {
                      size_t offset =
                          _is_swapped(view_p) ? view_p->itemsize - 1 : 0;
                      stridez_0 =
                          ((Rshift == 0 ? 0
                                        : (Gshift == 0 ? stridez : stridez2)) +
                           offset);
                      stridez_1 =
                          ((Rshift == 8 ? 0
                                        : (Gshift == 8 ? stridez : stridez2)) +
                           offset);
                      stridez_2 =
                          ((Rshift == 16 ? 0
                                         : (Gshift == 16 ? stridez : stridez2)) +
                           offset);
                  }
  #else
                  if (view_p->ndim == 2) {
                      stridez_0 = view_p->itemsize - 3;
                      stridez_1 = stridez_0 + 1;
                      stridez_2 = stridez_1 + 1;
                  }
                  else {
                      size_t offset =
                          _is_swapped(view_p) ? 0 : view_p->itemsize - 1;
                      stridez_2 =
                          ((Rshift == 0 ? 0
                                        : (Gshift == 0 ? stridez : stridez2)) +
                           offset);
                      stridez_1 =
                          ((Rshift == 8 ? 0
                                        : (Gshift == 8 ? stridez : stridez2)) +
                           offset);
                      stridez_0 =
                          ((Rshift == 16 ? 0
                                         : (Gshift == 16 ? stridez : stridez2)) +
                           offset);
                  }
  #endif
                  for (loopy = 0; loopy < sizey; ++loopy) {
                      Uint8 *pix = ((Uint8 *)surf->pixels) + surf->pitch * loopy;
                      Uint8 *data = (Uint8 *)array_data + stridey * loopy;
                      Uint8 *end = pix + 3 * sizex;
                      while (pix != end) {
                          *pix++ = *(data + stridez_0);
                          *pix++ = *(data + stridez_1);
                          *pix++ = *(data + stridez_2);
                          data += stridex;
                      }
                  }
              }
              else {
                  pgBuffer_Release(&pg_view);
                  if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                      return NULL;
                  }
                  return RAISE(PyExc_ValueError,
                               "unsupported datatype for array\n");
              }
              break;
          case 4:
              if (view_p->ndim == 2) {
                  switch (view_p->itemsize) {
                      case sizeof(Uint32):
                          COPYMACRO_2D(Uint32, Uint32);
                          break;
                      case sizeof(Uint64):
                          COPYMACRO_2D(Uint32, Uint64);
                          break;
                      default:
                          pgBuffer_Release(&pg_view);
                          if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                              return NULL;
                          }
                          return RAISE(PyExc_ValueError,
                                       "unsupported datatype for array\n");
                  }
              }
              else {
                  Uint32 alpha = 0;
                  if (format->Amask) {
                      alpha = 255 >> format->Aloss << format->Ashift;
                  }
                  switch (view_p->itemsize) {
                      case sizeof(Uint8):
                          COPYMACRO_3D(Uint32, Uint8);
                          break;
                      case sizeof(Uint16):
                          COPYMACRO_3D(Uint32, Uint16);
                          break;
                      case sizeof(Uint32):
                          COPYMACRO_3D(Uint32, Uint32);
                          break;
                      case sizeof(Uint64):
                          COPYMACRO_3D(Uint32, Uint64);
                          break;
                      default:
                          pgBuffer_Release(&pg_view);
                          if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                              return NULL;
                          }
                          return RAISE(PyExc_ValueError,
                                       "unsupported datatype for array\n");
                  }
              }
              break;
          default:
              pgBuffer_Release(&pg_view);
              if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
                  return NULL;
              }
              return RAISE(PyExc_RuntimeError,
                           "unsupported bit depth for image");
      }
  
      pgBuffer_Release(&pg_view);
      if (!pgSurface_UnlockBy(surfobj, arrayobj)) {
          return NULL;
      }
      Py_RETURN_NONE;
  }
  
  static PyObject *
  surface_to_array(PyObject *self, PyObject *args, PyObject *kwds)
  {
      PyObject *arrayobj;
      PyObject *surfobj;
      pg_buffer pg_view;
      Py_buffer *view_p = (Py_buffer *)&pg_view;
      _pc_view_kind_t view_kind = VIEWKIND_RGB;
      Uint8 opaque = 255;
      Uint8 clear = 0;
      SDL_Surface *surf;
      char *keywords[] = {"array", "surface", "kind", "opaque", "clear", 0};
  
      if (!PyArg_ParseTupleAndKeywords(
              args, kwds, "OO!|O&BB", keywords, &arrayobj, &pgSurface_Type,
              &surfobj, _view_kind, (void *)&view_kind, &opaque, &clear)) {
          return 0;
      }
      if (!pgSurface_Lock(surfobj)) {
          return 0;
      }
      surf = pgSurface_AsSurface(surfobj);
  
      if (pgObject_GetBuffer(arrayobj, &pg_view, PyBUF_RECORDS)) {
          pgSurface_Unlock(surfobj);
          return 0;
      }
      if (_validate_view_format(view_p->format)) {
          pgBuffer_Release(&pg_view);
          pgSurface_Unlock(surfobj);
          return 0;
      }
  
      if (view_p->ndim == 2) {
          if (view_kind == VIEWKIND_RGB) {
              if (_copy_mapped(view_p, surf)) {
                  pgBuffer_Release(&pg_view);
                  pgSurface_Unlock(surfobj);
                  return 0;
              }
          }
          else {
              if (_copy_colorplane(view_p, surf, view_kind, opaque, clear)) {
                  pgBuffer_Release(&pg_view);
                  pgSurface_Unlock(surfobj);
                  return 0;
              }
          }
      }
      else if (view_p->ndim == 3) {
          if (view_kind != VIEWKIND_RGB) {
              PyErr_SetString(PyExc_ValueError,
                              "color planes only supported for 2d targets");
              pgBuffer_Release(&pg_view);
              pgSurface_Unlock(surfobj);
              return 0;
          }
          if (_copy_unmapped(view_p, surf)) {
              pgBuffer_Release(&pg_view);
              pgSurface_Unlock(surfobj);
              return 0;
          }
      }
      else {
          pgBuffer_Release(&pg_view);
          pgSurface_Unlock(surfobj);
          PyErr_Format(PyExc_ValueError, "Unsupported array depth %d",
                       (int)view_p->ndim);
          return 0;
      }
  
      pgBuffer_Release(&pg_view);
      if (!pgSurface_Unlock(surfobj)) {
          return 0;
      }
      Py_RETURN_NONE;
  }
  
  static PyObject *
  map_array(PyObject *self, PyObject *args)
  {
  #define PIXELCOPY_MAX_DIM 10
      PyObject *src_array;
      PyObject *tar_array;
      PyObject *format_surf;
      SDL_PixelFormat *format;
      pg_buffer src_pg_view;
      Py_buffer *src_view_p = 0;
      Uint8 *src;
      int src_ndim;
      Py_intptr_t src_strides[PIXELCOPY_MAX_DIM];
      const int src_red = 0;
      int src_green;
      int src_blue;
      pg_buffer tar_pg_view;
      Py_buffer *tar_view_p = 0;
      Uint8 *tar;
      int ndim;
      Py_intptr_t *shape;
      Py_intptr_t *tar_strides;
      int tar_itemsize;
      int tar_byte0 = 0;
      int tar_byte1 = 0;
      int tar_byte2 = 0;
      int tar_byte3 = 0;
      int tar_padding_start;
      int tar_padding_end;
      Py_intptr_t counters[PIXELCOPY_MAX_DIM];
      int src_advances[PIXELCOPY_MAX_DIM];
      int tar_advances[PIXELCOPY_MAX_DIM];
      int dim_diff;
      int dim;
      int topdim;
      _pc_pixel_t pixel = {0};
      int pix_bytesize;
      int i;
  
      if (!PyArg_ParseTuple(args, "OOO!", &tar_array, &src_array,
                            &pgSurface_Type, &format_surf)) {
          return 0;
      }
  
      if (!pgSurface_Lock(format_surf)) {
          return 0;
      }
  
      /* Determine array shapes and check validity
       */
      if (pgObject_GetBuffer(tar_array, &tar_pg_view, PyBUF_RECORDS)) {
          goto fail;
      }
      tar_view_p = (Py_buffer *)&tar_pg_view;
      tar = (Uint8 *)tar_view_p->buf;
      if (_validate_view_format(tar_view_p->format)) {
          PyErr_SetString(PyExc_ValueError, "expected an integer target array");
          goto fail;
      }
      ndim = tar_view_p->ndim;
      tar_itemsize = tar_view_p->itemsize;
      shape = tar_view_p->shape;
      tar_strides = tar_view_p->strides;
      if (ndim < 1) {
          PyErr_SetString(PyExc_ValueError, "target array must be at least 1D");
          goto fail;
      }
      if (ndim > PIXELCOPY_MAX_DIM) {
          PyErr_Format(PyExc_ValueError, "target array exceeds %d dimensions",
                       (int)PIXELCOPY_MAX_DIM);
          goto fail;
      }
      if (pgObject_GetBuffer(src_array, &src_pg_view, PyBUF_RECORDS_RO)) {
          goto fail;
      }
      src_view_p = (Py_buffer *)&src_pg_view;
      if (_validate_view_format(src_view_p->format)) {
          goto fail;
      }
      src = (Uint8 *)src_view_p->buf;
      src_ndim = src_view_p->ndim;
      if (src_ndim < 1) {
          PyErr_SetString(PyExc_ValueError, "source array must be at least 1D");
          goto fail;
      }
      if (src_view_p->shape[src_ndim - 1] != 3) {
          PyErr_Format(PyExc_ValueError,
                       "Expected a (..., 3) source array: got (..., %zd)",
                       src_view_p->shape[src_ndim - 1]);
          goto fail;
      }
      if (ndim < src_ndim - 1) {
          PyErr_Format(PyExc_ValueError,
                       "%d dimensional target has too few dimensions for"
                       " %d dimensional source",
                       ndim, src_ndim);
          goto fail;
      }
      for (dim = 0; dim != ndim; ++dim) {
          src_strides[dim] = 0;
      }
      dim_diff = ndim - src_ndim + 1;
      for (dim = dim_diff; dim != ndim; ++dim) {
          if (src_view_p->shape[dim - dim_diff] == 1) {
              src_strides[dim] = 0;
          }
          else if (src_view_p->shape[dim - dim_diff] == shape[dim]) {
              src_strides[dim] = src_view_p->strides[dim - dim_diff];
          }
          else {
              PyErr_Format(PyExc_ValueError,
                           "size mismatch between dimension %d of source and"
                           " dimension %d of target",
                           dim - dim_diff, dim);
              goto fail;
          }
      }
      for (dim = 0; dim != ndim - 1; ++dim) {
          tar_advances[dim] =
              tar_strides[dim] - shape[dim + 1] * tar_strides[dim + 1];
          src_advances[dim] =
              src_strides[dim] - shape[dim + 1] * src_strides[dim + 1];
      }
  
      /* Determine souce and destination pixel formats
       */
      format = pgSurface_AsSurface(format_surf)->format;
      pix_bytesize = format->BytesPerPixel;
      if (tar_itemsize < pix_bytesize) {
          PyErr_SetString(PyExc_ValueError,
                          "target array itemsize is too small for pixel format");
          goto fail;
      }
      src_green = src_view_p->strides[src_ndim - 1];
      src_blue = 2 * src_green;
      switch (pix_bytesize) {
          case 1:
              break;
  #if SDL_BYTEORDER == SDL_BIG_ENDIAN
          case 2:
              if (_is_swapped(tar_view_p)) {
                  tar_byte2 = 1;
              }
              else {
                  tar_byte3 = 1;
              }
              break;
          case 3:
              if (_is_swapped(tar_view_p)) {
                  tar_byte1 = 2;
                  tar_byte2 = 1;
              }
              else {
                  tar_byte2 = 1;
                  tar_byte3 = 2;
              }
              break;
  #else
          case 2:
              if (_is_swapped(tar_view_p)) {
                  tar_byte0 = 1;
              }
              else {
                  tar_byte1 = 1;
              }
              break;
          case 3:
              if (_is_swapped(tar_view_p)) {
                  tar_byte0 = 2;
                  tar_byte1 = 1;
              }
              else {
                  tar_byte1 = 1;
                  tar_byte2 = 2;
              }
              break;
  #endif
          case 4:
              if (_is_swapped(tar_view_p)) {
                  tar_byte0 = 3;
                  tar_byte1 = 2;
                  tar_byte2 = 1;
              }
              else {
                  tar_byte1 = 1;
                  tar_byte2 = 2;
                  tar_byte3 = 3;
              }
              break;
          default:
              PyErr_Format(PyExc_ValueError,
                           "%d bytes per pixel target format not supported",
                           pix_bytesize);
              goto fail;
      }
  
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
      if (_is_swapped(tar_view_p)) {
          tar += tar_itemsize - pix_bytesize;
          tar_padding_start = pix_bytesize - tar_itemsize;
          tar_padding_end = 0;
      }
      else {
          tar_padding_start = pix_bytesize;
          tar_padding_end = tar_itemsize;
      }
  #else
      if (_is_swapped(tar_view_p)) {
          tar_padding_start = pix_bytesize;
          tar_padding_end = tar_itemsize;
      }
      else {
          tar += tar_itemsize - pix_bytesize;
          tar_padding_start = pix_bytesize - tar_itemsize;
          tar_padding_end = 0;
      }
  #endif
  
      /* Iterate over arrays, left index varying slowest, copying pixels
       */
      dim = 0;
      topdim = ndim - 1;
      counters[0] = shape[0];
      while (counters[0]) {
          if (!counters[dim]) {
              /* Leave loop, moving left one index
               */
              --dim;
              tar += tar_advances[dim];
              src += src_advances[dim];
              --counters[dim];
          }
          else if (dim == topdim) {
              /* Next iteration of inner most loop: copy pixel
               */
              pixel.value = SDL_MapRGB(format, src[src_red], src[src_green],
                                       src[src_blue]);
              /* Bytes are copied from the pixel in most to least significant
               * byte order. If destination bytes get overwritten, when the
               * destination size is less than 4 bytes, only zero pad bytes
               * of a pixel get clobbered in the destination write.
               */
  #if SDL_BYTEORDER == SDL_LIL_ENDIAN
              tar[tar_byte3] = pixel.bytes[3];
              tar[tar_byte2] = pixel.bytes[2];
              tar[tar_byte1] = pixel.bytes[1];
              tar[tar_byte0] = pixel.bytes[0];
  #else
              tar[tar_byte0] = pixel.bytes[0];
              tar[tar_byte1] = pixel.bytes[1];
              tar[tar_byte2] = pixel.bytes[2];
              tar[tar_byte3] = pixel.bytes[3];
  #endif
              for (i = tar_padding_start; i < tar_padding_end; ++i) {
                  tar[i] = 0;
              }
              tar += tar_strides[dim];
              src += src_strides[dim];
              --counters[dim];
          }
          else {
              /* Enter loop for next index to the right
               */
              dim += 1;
              counters[dim] = shape[dim];
          }
      }
  
      /* Cleanup
       */
      pgBuffer_Release(&src_pg_view);
      pgBuffer_Release(&tar_pg_view);
      if (!pgSurface_Unlock(format_surf)) {
          return 0;
      }
      Py_RETURN_NONE;
  
  fail:
      if (src_view_p) {
          pgBuffer_Release(&src_pg_view);
      }
      if (tar_view_p) {
          pgBuffer_Release(&tar_pg_view);
      }
      pgSurface_Unlock(format_surf);
      return 0;
  }
  
  static PyObject *
  make_surface(PyObject *self, PyObject *arg)
  {
      pg_buffer pg_view;
      Py_buffer *view_p = (Py_buffer *)&pg_view;
      PyObject *surfobj;
      PyObject *args;
      PyObject *result;
      SDL_Surface *surf;
      int sizex, sizey, bitsperpixel;
      Uint32 rmask, gmask, bmask;
  
      if (pgObject_GetBuffer(arg, &pg_view, PyBUF_RECORDS_RO)) {
          return 0;
      }
  
      if (!(view_p->ndim == 2 || (view_p->ndim == 3 && view_p->shape[2] == 3))) {
          pgBuffer_Release(&pg_view);
          return RAISE(PyExc_ValueError, "must be a valid 2d or 3d array\n");
      }
      if (_validate_view_format(view_p->format)) {
          pgBuffer_Release(&pg_view);
          return NULL;
      }
  
      if (view_p->ndim == 2) {
          bitsperpixel = 8;
  #if IS_SDLv1
          rmask = 0xFF >> 6 << 5;
          gmask = 0xFF >> 5 << 2;
          bmask = 0xFF >> 6;
  #else  /* IS_SDLv2 */
          rmask = 0;
          gmask = 0;
          bmask = 0;
  #endif /* IS_SDLv2 */
      }
      else {
          bitsperpixel = 32;
          rmask = 0xFF << 16;
          gmask = 0xFF << 8;
          bmask = 0xFF;
      }
      sizex = view_p->shape[0];
      sizey = view_p->shape[1];
  
      surf = SDL_CreateRGBSurface(0, sizex, sizey, bitsperpixel, rmask, gmask,
                                  bmask, 0);
      if (!surf) {
          pgBuffer_Release(&pg_view);
          return RAISE(pgExc_SDLError, SDL_GetError());
      }
  #if IS_SDLv2
      if (SDL_ISPIXELFORMAT_INDEXED(surf->format->format)) {
          /* Give the surface something other than an all white palette.
           *          */
          if (SDL_SetPaletteColors(surf->format->palette, default_palette_colors,
                                   0, default_palette_size - 1) != 0) {
              PyErr_SetString(pgExc_SDLError, SDL_GetError());
              SDL_FreeSurface(surf);
              return 0;
          }
      }
  #endif /* IS_SDLv2 */
      surfobj = pgSurface_New(surf);
      if (!surfobj) {
          pgBuffer_Release(&pg_view);
          SDL_FreeSurface(surf);
          return 0;
      }
  
      args = Py_BuildValue("(OO)", surfobj, arg);
      if (!args) {
          pgBuffer_Release(&pg_view);
          Py_DECREF(surfobj);
          return 0;
      }
  
      result = array_to_surface(self, args);
      pgBuffer_Release(&pg_view);
      Py_DECREF(args);
  
      if (!result) {
          Py_DECREF(surfobj);
          return 0;
      }
      Py_DECREF(result);
      return surfobj;
  }
  
  static PyMethodDef _pixelcopy_methods[] = {
      {"array_to_surface", array_to_surface, METH_VARARGS,
       DOC_PYGAMEPIXELCOPYARRAYTOSURFACE},
      {"surface_to_array", (PyCFunction)surface_to_array,
       METH_VARARGS | METH_KEYWORDS, DOC_PYGAMEPIXELCOPYSURFACETOARRAY},
      {"map_array", map_array, METH_VARARGS, DOC_PYGAMEPIXELCOPYMAPARRAY},
      {"make_surface", make_surface, METH_O, DOC_PYGAMEPIXELCOPYMAKESURFACE},
      {0, 0, 0, 0}};
  
  MODINIT_DEFINE(pixelcopy)
  {
  #if PY3
      static struct PyModuleDef _module = {PyModuleDef_HEAD_INIT,
                                           "pixelcopy",
                                           DOC_PYGAMEPIXELCOPY,
                                           -1,
                                           _pixelcopy_methods,
                                           NULL,
                                           NULL,
                                           NULL,
                                           NULL};
  #endif
  
      /* imported needed apis; Do this first so if there is an error
         the module is not loaded.
      */
      import_pygame_base();
      if (PyErr_Occurred()) {
          MODINIT_ERROR;
      }
      import_pygame_surface();
      if (PyErr_Occurred()) {
          MODINIT_ERROR;
      }
  
  #if PY3
      return PyModule_Create(&_module);
  #else
      Py_InitModule3("pixelcopy", _pixelcopy_methods, DOC_PYGAMEPIXELCOPY);
  #endif
  }
  