Skip to content

Commit 9fb22a4

Browse files
committed
Implement pitch argument for image.tobytes()
1 parent 35e232a commit 9fb22a4

File tree

5 files changed

+120
-18
lines changed

5 files changed

+120
-18
lines changed

buildconfig/stubs/pygame/image.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def tostring(
2020
surface: Surface,
2121
format: _to_string_format,
2222
flipped: bool = False,
23+
pitch: int = -1,
2324
) -> bytes: ...
2425
def fromstring(
2526
bytes: bytes,
@@ -31,7 +32,10 @@ def fromstring(
3132

3233
# the use of tobytes/frombytes is preferred over tostring/fromstring
3334
def tobytes(
34-
surface: Surface, format: _to_string_format, flipped: bool = False
35+
surface: Surface,
36+
format: _to_string_format,
37+
flipped: bool = False,
38+
pitch: int = -1,
3539
) -> bytes: ...
3640
def frombytes(
3741
bytes: bytes,

docs/reST/ref/image.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ following formats.
168168
.. function:: tostring
169169

170170
| :sl:`transfer image to byte buffer`
171-
| :sg:`tostring(Surface, format, flipped=False) -> bytes`
171+
| :sg:`tostring(Surface, format, flipped=False, pitch=-1) -> bytes`
172172
173173
DEPRECATED: This function has the same functionality as :func:`tobytes()`, which is preferred and should be used.
174174

@@ -179,7 +179,7 @@ following formats.
179179
.. function:: tobytes
180180

181181
| :sl:`transfer image to byte buffer`
182-
| :sg:`tobytes(Surface, format, flipped=False) -> bytes`
182+
| :sg:`tobytes(Surface, format, flipped=False, pitch=-1) -> bytes`
183183
184184
Creates a string of bytes that can be transferred with the ``fromstring``
185185
or ``frombytes`` methods in other Python imaging packages. Some Python
@@ -208,12 +208,19 @@ following formats.
208208

209209
* ``ARGB_PREMULT``, 32-bit image with colors scaled by alpha channel, alpha channel first
210210

211+
The 'pitch' argument can be used to specify the pitch/stride per horizontal line
212+
of the image in bytes. It must be equal to or greater than how many bytes
213+
the pixel data of each horizontal line in the image bytes occupies without any
214+
extra padding. By default, it is ``-1``, which means that the pitch/stride is
215+
the same size as how many bytes the pure pixel data of each horizontal line takes.
216+
211217
.. note:: The use of this function is recommended over :func:`tostring` as of pygame 2.1.3.
212218
This function was introduced so it matches nicely with other
213219
libraries (PIL, numpy, etc), and with people's expectations.
214220

215221
.. versionadded:: 2.1.3
216222
.. versionchanged:: 2.2.0 Now supports keyword arguments.
223+
.. versionchanged:: 2.4.0 Added a 'pitch' argument.
217224

218225
.. ## pygame.image.tobytes ##
219226

src_c/doc/image_doc.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
#define DOC_IMAGE_SAVE "save(Surface, file) -> None\nsave(Surface, file, namehint="") -> None\nsave an image to file (or file-like object)"
55
#define DOC_IMAGE_GETSDLIMAGEVERSION "get_sdl_image_version(linked=True) -> None\nget_sdl_image_version(linked=True) -> (major, minor, patch)\nget version number of the SDL_Image library being used"
66
#define DOC_IMAGE_GETEXTENDED "get_extended() -> bool\ntest if extended image formats can be loaded"
7-
#define DOC_IMAGE_TOSTRING "tostring(Surface, format, flipped=False) -> bytes\ntransfer image to byte buffer"
8-
#define DOC_IMAGE_TOBYTES "tobytes(Surface, format, flipped=False) -> bytes\ntransfer image to byte buffer"
7+
#define DOC_IMAGE_TOSTRING "tostring(Surface, format, flipped=False, pitch=-1) -> bytes\ntransfer image to byte buffer"
8+
#define DOC_IMAGE_TOBYTES "tobytes(Surface, format, flipped=False, pitch=-1) -> bytes\ntransfer image to byte buffer"
99
#define DOC_IMAGE_FROMSTRING "fromstring(bytes, size, format, flipped=False, pitch=-1) -> Surface\ncreate new Surface from a byte buffer"
1010
#define DOC_IMAGE_FROMBYTES "frombytes(bytes, size, format, flipped=False, pitch=-1) -> Surface\ncreate new Surface from a byte buffer"
1111
#define DOC_IMAGE_FROMBUFFER "frombuffer(buffer, size, format, pitch=-1) -> Surface\ncreate a new Surface that shares data inside a bytes buffer"

src_c/image.c

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ SaveTGA_RW(SDL_Surface *surface, SDL_RWops *out, int rle);
4545
((flipped) ? (((char *)data) + (height - row - 1) * width) \
4646
: (((char *)data) + row * width))
4747

48+
#define PITCHPAD(data, byte_width, pitch) \
49+
if (pitch > byte_width) { \
50+
memset(data, 0, pitch - byte_width); \
51+
data += pitch - byte_width; \
52+
}
53+
4854
static PyObject *extloadobj = NULL;
4955
static PyObject *extsaveobj = NULL;
5056
static PyObject *extverobj = NULL;
@@ -414,9 +420,10 @@ tobytes_surf_32bpp_sse42(SDL_Surface *surf, int flipped, char *data,
414420
static void
415421
tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
416422
Uint32 colorkey, char *serialized_image, int color_offset,
417-
int alpha_offset)
423+
int alpha_offset, int pitch)
418424
{
419425
int w, h;
426+
int byte_width = surf->w * 4;
420427

421428
Uint32 Rmask = surf->format->Rmask;
422429
Uint32 Gmask = surf->format->Gmask;
@@ -441,7 +448,8 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
441448
sizeof(int) == sizeof(Uint32) &&
442449
4 * sizeof(Uint32) == sizeof(__m128i) &&
443450
!hascolorkey /* No color key */
444-
&& SDL_HasSSE42() == SDL_TRUE
451+
&& byte_width == pitch &&
452+
SDL_HasSSE42() == SDL_TRUE
445453
/* The SSE code assumes it will always read at least 4 pixels */
446454
&& surf->w >= 4
447455
/* Our SSE code assumes masks are at most 0xff */
@@ -480,6 +488,7 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
480488
: 255);
481489
serialized_image += 4;
482490
}
491+
PITCHPAD(serialized_image, byte_width, pitch)
483492
}
484493
}
485494

@@ -490,25 +499,25 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
490499
PyObject *bytes = NULL;
491500
char *format, *data;
492501
SDL_Surface *surf;
493-
int w, h, flipped = 0;
502+
int w, h, flipped = 0, pitch = -1;
494503
int byte_width;
495504
Py_ssize_t len;
496505
Uint32 Rmask, Gmask, Bmask, Amask, Rshift, Gshift, Bshift, Ashift, Rloss,
497506
Gloss, Bloss, Aloss;
498507
int hascolorkey = 0;
499508
Uint32 color, colorkey;
500509
Uint32 alpha;
501-
static char *kwds[] = {"surface", "format", "flipped", NULL};
510+
static char *kwds[] = {"surface", "format", "flipped", "pitch", NULL};
502511

503512
#ifdef _MSC_VER
504513
/* MSVC static analyzer false alarm: assure format is NULL-terminated by
505514
* making analyzer assume it was initialised */
506515
__analysis_assume(format = "inited");
507516
#endif
508517

509-
if (!PyArg_ParseTupleAndKeywords(arg, kwarg, "O!s|i", kwds,
518+
if (!PyArg_ParseTupleAndKeywords(arg, kwarg, "O!s|ii", kwds,
510519
&pgSurface_Type, &surfobj, &format,
511-
&flipped))
520+
&flipped, &pitch))
512521
return NULL;
513522
surf = pgSurface_AsSurface(surfobj);
514523

@@ -555,16 +564,28 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
555564
return RAISE(PyExc_ValueError, "Unrecognized type of format");
556565
}
557566

558-
bytes = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)byte_width * surf->h);
567+
if (pitch == -1) {
568+
pitch = byte_width;
569+
}
570+
else if (pitch < byte_width) {
571+
return RAISE(PyExc_ValueError,
572+
"Pitch must be greater than or equal to the width "
573+
"as per the format");
574+
}
575+
576+
bytes = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)pitch * surf->h);
559577
if (!bytes)
560578
return NULL;
561579
PyBytes_AsStringAndSize(bytes, &data, &len);
562580

563581
if (!strcmp(format, "P")) {
564582
pgSurface_Lock(surfobj);
565-
for (h = 0; h < surf->h; ++h)
566-
memcpy(DATAROW(data, h, byte_width, surf->h, flipped),
567-
(char *)surf->pixels + (h * surf->pitch), surf->w);
583+
for (h = 0; h < surf->h; ++h) {
584+
Uint8 *ptr = (Uint8 *)DATAROW(data, h, pitch, surf->h, flipped);
585+
memcpy(ptr, (char *)surf->pixels + (h * surf->pitch), surf->w);
586+
if (pitch > byte_width)
587+
memset(ptr + byte_width, 0, pitch - byte_width);
588+
}
568589
pgSurface_Unlock(surfobj);
569590
}
570591
else if (!strcmp(format, "RGB")) {
@@ -582,6 +603,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
582603
data[2] = (char)surf->format->palette->colors[color].b;
583604
data += 3;
584605
}
606+
PITCHPAD(data, byte_width, pitch)
585607
}
586608
break;
587609
case 2:
@@ -595,6 +617,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
595617
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
596618
data += 3;
597619
}
620+
PITCHPAD(data, byte_width, pitch)
598621
}
599622
break;
600623
case 3:
@@ -613,6 +636,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
613636
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
614637
data += 3;
615638
}
639+
PITCHPAD(data, byte_width, pitch)
616640
}
617641
break;
618642
case 4:
@@ -626,6 +650,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
626650
data[2] = (char)(((color & Bmask) >> Bshift) << Rloss);
627651
data += 3;
628652
}
653+
PITCHPAD(data, byte_width, pitch)
629654
}
630655
break;
631656
}
@@ -648,6 +673,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
648673
: (char)255;
649674
data += 4;
650675
}
676+
PITCHPAD(data, byte_width, pitch)
651677
}
652678
break;
653679
case 2:
@@ -667,6 +693,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
667693
: 255);
668694
data += 4;
669695
}
696+
PITCHPAD(data, byte_width, pitch)
670697
}
671698
break;
672699
case 3:
@@ -691,11 +718,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
691718
: 255);
692719
data += 4;
693720
}
721+
PITCHPAD(data, byte_width, pitch)
694722
}
695723
break;
696724
case 4:
697725
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
698-
0, 3);
726+
0, 3, pitch);
699727
break;
700728
}
701729
pgSurface_Unlock(surfobj);
@@ -715,6 +743,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
715743
data[0] = (char)255;
716744
data += 4;
717745
}
746+
PITCHPAD(data, byte_width, pitch)
718747
}
719748
break;
720749
case 2:
@@ -731,6 +760,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
731760
: 255);
732761
data += 4;
733762
}
763+
PITCHPAD(data, byte_width, pitch)
734764
}
735765
break;
736766
case 3:
@@ -752,11 +782,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
752782
: 255);
753783
data += 4;
754784
}
785+
PITCHPAD(data, byte_width, pitch)
755786
}
756787
break;
757788
case 4:
758789
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
759-
1, 0);
790+
1, 0, pitch);
760791
break;
761792
}
762793
pgSurface_Unlock(surfobj);
@@ -776,6 +807,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
776807
data[3] = (char)255;
777808
data += 4;
778809
}
810+
PITCHPAD(data, byte_width, pitch)
779811
}
780812
break;
781813
case 2:
@@ -792,6 +824,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
792824
: 255);
793825
data += 4;
794826
}
827+
PITCHPAD(data, byte_width, pitch)
795828
}
796829
break;
797830
case 3:
@@ -813,6 +846,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
813846
: 255);
814847
data += 4;
815848
}
849+
PITCHPAD(data, byte_width, pitch)
816850
}
817851
break;
818852
case 4:
@@ -829,6 +863,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
829863
: 255);
830864
data += 4;
831865
}
866+
PITCHPAD(data, byte_width, pitch)
832867
}
833868
break;
834869
}
@@ -856,6 +891,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
856891
data[3] = (char)alpha;
857892
data += 4;
858893
}
894+
PITCHPAD(data, byte_width, pitch)
859895
}
860896
break;
861897
case 3:
@@ -882,6 +918,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
882918
data[3] = (char)alpha;
883919
data += 4;
884920
}
921+
PITCHPAD(data, byte_width, pitch)
885922
}
886923
break;
887924
case 4:
@@ -908,6 +945,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
908945
data[3] = (char)alpha;
909946
data += 4;
910947
}
948+
PITCHPAD(data, byte_width, pitch)
911949
}
912950
break;
913951
}
@@ -935,6 +973,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
935973
data[0] = (char)alpha;
936974
data += 4;
937975
}
976+
PITCHPAD(data, byte_width, pitch)
938977
}
939978
break;
940979
case 3:
@@ -961,6 +1000,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9611000
data[0] = (char)alpha;
9621001
data += 4;
9631002
}
1003+
PITCHPAD(data, byte_width, pitch)
9641004
}
9651005
break;
9661006
case 4:
@@ -987,6 +1027,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9871027
data[0] = (char)alpha;
9881028
data += 4;
9891029
}
1030+
PITCHPAD(data, byte_width, pitch)
9901031
}
9911032
break;
9921033
}

0 commit comments

Comments
 (0)