Skip to content

Commit fdf7737

Browse files
committed
Implement pitch argument for image.tobytes()
1 parent 69cd394 commit fdf7737

File tree

5 files changed

+119
-17
lines changed

5 files changed

+119
-17
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 specify the pitch/stride per horizontal line
212+
of the image bytes 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: 52 additions & 11 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,6 +448,7 @@ 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 */
451+
&& byte_width == pitch
444452
&& SDL_HasSSE42() == SDL_TRUE
445453
/* The SSE code assumes it will always read at least 4 pixels */
446454
&& surf->w >= 4
@@ -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

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

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

564582
if (!strcmp(format, "P")) {
565583
pgSurface_Lock(surfobj);
566-
for (h = 0; h < surf->h; ++h)
567-
memcpy(DATAROW(data, h, byte_width, surf->h, flipped),
568-
(char *)surf->pixels + (h * surf->pitch), surf->w);
584+
for (h = 0; h < surf->h; ++h) {
585+
Uint8 *ptr = (Uint8 *)DATAROW(data, h, pitch, surf->h, flipped);
586+
memcpy(ptr, (char *)surf->pixels + (h * surf->pitch), surf->w);
587+
if (pitch > byte_width)
588+
memset(ptr + byte_width, 0, pitch - byte_width);
589+
}
569590
pgSurface_Unlock(surfobj);
570591
}
571592
else if (!strcmp(format, "RGB")) {
@@ -583,6 +604,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
583604
data[2] = (char)surf->format->palette->colors[color].b;
584605
data += 3;
585606
}
607+
PITCHPAD(data, byte_width, pitch)
586608
}
587609
break;
588610
case 2:
@@ -596,6 +618,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
596618
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
597619
data += 3;
598620
}
621+
PITCHPAD(data, byte_width, pitch)
599622
}
600623
break;
601624
case 3:
@@ -614,6 +637,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
614637
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
615638
data += 3;
616639
}
640+
PITCHPAD(data, byte_width, pitch)
617641
}
618642
break;
619643
case 4:
@@ -627,6 +651,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
627651
data[2] = (char)(((color & Bmask) >> Bshift) << Rloss);
628652
data += 3;
629653
}
654+
PITCHPAD(data, byte_width, pitch)
630655
}
631656
break;
632657
}
@@ -649,6 +674,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
649674
: (char)255;
650675
data += 4;
651676
}
677+
PITCHPAD(data, byte_width, pitch)
652678
}
653679
break;
654680
case 2:
@@ -668,6 +694,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
668694
: 255);
669695
data += 4;
670696
}
697+
PITCHPAD(data, byte_width, pitch)
671698
}
672699
break;
673700
case 3:
@@ -692,11 +719,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
692719
: 255);
693720
data += 4;
694721
}
722+
PITCHPAD(data, byte_width, pitch)
695723
}
696724
break;
697725
case 4:
698726
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
699-
0, 3);
727+
0, 3, pitch);
700728
break;
701729
}
702730
pgSurface_Unlock(surfobj);
@@ -716,6 +744,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
716744
data[0] = (char)255;
717745
data += 4;
718746
}
747+
PITCHPAD(data, byte_width, pitch)
719748
}
720749
break;
721750
case 2:
@@ -732,6 +761,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
732761
: 255);
733762
data += 4;
734763
}
764+
PITCHPAD(data, byte_width, pitch)
735765
}
736766
break;
737767
case 3:
@@ -753,11 +783,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
753783
: 255);
754784
data += 4;
755785
}
786+
PITCHPAD(data, byte_width, pitch)
756787
}
757788
break;
758789
case 4:
759790
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
760-
1, 0);
791+
1, 0, pitch);
761792
break;
762793
}
763794
pgSurface_Unlock(surfobj);
@@ -777,6 +808,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
777808
data[3] = (char)255;
778809
data += 4;
779810
}
811+
PITCHPAD(data, byte_width, pitch)
780812
}
781813
break;
782814
case 2:
@@ -793,6 +825,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
793825
: 255);
794826
data += 4;
795827
}
828+
PITCHPAD(data, byte_width, pitch)
796829
}
797830
break;
798831
case 3:
@@ -814,6 +847,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
814847
: 255);
815848
data += 4;
816849
}
850+
PITCHPAD(data, byte_width, pitch)
817851
}
818852
break;
819853
case 4:
@@ -830,6 +864,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
830864
: 255);
831865
data += 4;
832866
}
867+
PITCHPAD(data, byte_width, pitch)
833868
}
834869
break;
835870
}
@@ -857,6 +892,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
857892
data[3] = (char)alpha;
858893
data += 4;
859894
}
895+
PITCHPAD(data, byte_width, pitch)
860896
}
861897
break;
862898
case 3:
@@ -883,6 +919,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
883919
data[3] = (char)alpha;
884920
data += 4;
885921
}
922+
PITCHPAD(data, byte_width, pitch)
886923
}
887924
break;
888925
case 4:
@@ -909,6 +946,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
909946
data[3] = (char)alpha;
910947
data += 4;
911948
}
949+
PITCHPAD(data, byte_width, pitch)
912950
}
913951
break;
914952
}
@@ -936,6 +974,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
936974
data[0] = (char)alpha;
937975
data += 4;
938976
}
977+
PITCHPAD(data, byte_width, pitch)
939978
}
940979
break;
941980
case 3:
@@ -962,6 +1001,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9621001
data[0] = (char)alpha;
9631002
data += 4;
9641003
}
1004+
PITCHPAD(data, byte_width, pitch)
9651005
}
9661006
break;
9671007
case 4:
@@ -988,6 +1028,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9881028
data[0] = (char)alpha;
9891029
data += 4;
9901030
}
1031+
PITCHPAD(data, byte_width, pitch)
9911032
}
9921033
break;
9931034
}

0 commit comments

Comments
 (0)