Skip to content

Commit ecf91ae

Browse files
committed
Implement pitch argument for image.tobytes()
1 parent f0b9361 commit ecf91ae

File tree

5 files changed

+127
-19
lines changed

5 files changed

+127
-19
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
@@ -169,7 +169,7 @@ following formats.
169169
.. function:: tostring
170170

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

@@ -180,7 +180,7 @@ following formats.
180180
.. function:: tobytes
181181

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

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

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

216222
.. versionadded:: 2.1.3
217223
.. versionchanged:: 2.2.0 Now supports keyword arguments.
224+
.. versionchanged:: 2.4.0 Added a 'pitch' argument.
218225

219226
.. ## pygame.image.tobytes ##
220227

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: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ static PyObject *extloadobj = NULL;
4949
static PyObject *extsaveobj = NULL;
5050
static PyObject *extverobj = NULL;
5151

52+
static inline void
53+
pad(char **data, int padding)
54+
{
55+
if (padding) {
56+
memset(*data, 0, padding);
57+
*data += padding;
58+
}
59+
}
60+
5261
static const char *
5362
find_extension(const char *fullname)
5463
{
@@ -414,7 +423,7 @@ tobytes_surf_32bpp_sse42(SDL_Surface *surf, int flipped, char *data,
414423
static void
415424
tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
416425
Uint32 colorkey, char *serialized_image, int color_offset,
417-
int alpha_offset)
426+
int alpha_offset, int padding)
418427
{
419428
int w, h;
420429

@@ -441,7 +450,8 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
441450
sizeof(int) == sizeof(Uint32) &&
442451
4 * sizeof(Uint32) == sizeof(__m128i) &&
443452
!hascolorkey /* No color key */
444-
&& SDL_HasSSE42() == SDL_TRUE
453+
&& !padding &&
454+
SDL_HasSSE42() == SDL_TRUE
445455
/* The SSE code assumes it will always read at least 4 pixels */
446456
&& surf->w >= 4
447457
/* Our SSE code assumes masks are at most 0xff */
@@ -480,6 +490,7 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
480490
: 255);
481491
serialized_image += 4;
482492
}
493+
pad(&serialized_image, padding);
483494
}
484495
}
485496

@@ -490,25 +501,25 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
490501
PyObject *bytes = NULL;
491502
char *format, *data;
492503
SDL_Surface *surf;
493-
int w, h, flipped = 0;
494-
int byte_width;
504+
int w, h, flipped = 0, pitch = -1;
505+
int byte_width, padding;
495506
Py_ssize_t len;
496507
Uint32 Rmask, Gmask, Bmask, Amask, Rshift, Gshift, Bshift, Ashift, Rloss,
497508
Gloss, Bloss, Aloss;
498509
int hascolorkey = 0;
499510
Uint32 color, colorkey;
500511
Uint32 alpha;
501-
static char *kwds[] = {"surface", "format", "flipped", NULL};
512+
static char *kwds[] = {"surface", "format", "flipped", "pitch", NULL};
502513

503514
#ifdef _MSC_VER
504515
/* MSVC static analyzer false alarm: assure format is NULL-terminated by
505516
* making analyzer assume it was initialised */
506517
__analysis_assume(format = "inited");
507518
#endif
508519

509-
if (!PyArg_ParseTupleAndKeywords(arg, kwarg, "O!s|i", kwds,
520+
if (!PyArg_ParseTupleAndKeywords(arg, kwarg, "O!s|ii", kwds,
510521
&pgSurface_Type, &surfobj, &format,
511-
&flipped))
522+
&flipped, &pitch))
512523
return NULL;
513524
surf = pgSurface_AsSurface(surfobj);
514525

@@ -555,16 +566,32 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
555566
return RAISE(PyExc_ValueError, "Unrecognized type of format");
556567
}
557568

558-
bytes = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)byte_width * surf->h);
569+
if (pitch == -1) {
570+
pitch = byte_width;
571+
padding = 0;
572+
}
573+
else if (pitch < byte_width) {
574+
return RAISE(PyExc_ValueError,
575+
"Pitch must be greater than or equal to the width "
576+
"as per the format");
577+
}
578+
else {
579+
padding = pitch - byte_width;
580+
}
581+
582+
bytes = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)pitch * surf->h);
559583
if (!bytes)
560584
return NULL;
561585
PyBytes_AsStringAndSize(bytes, &data, &len);
562586

563587
if (!strcmp(format, "P")) {
564588
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);
589+
for (h = 0; h < surf->h; ++h) {
590+
Uint8 *ptr = (Uint8 *)DATAROW(data, h, pitch, surf->h, flipped);
591+
memcpy(ptr, (char *)surf->pixels + (h * surf->pitch), surf->w);
592+
if (padding)
593+
memset(ptr + byte_width, 0, padding);
594+
}
568595
pgSurface_Unlock(surfobj);
569596
}
570597
else if (!strcmp(format, "RGB")) {
@@ -582,6 +609,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
582609
data[2] = (char)surf->format->palette->colors[color].b;
583610
data += 3;
584611
}
612+
pad(&data, padding);
585613
}
586614
break;
587615
case 2:
@@ -595,6 +623,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
595623
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
596624
data += 3;
597625
}
626+
pad(&data, padding);
598627
}
599628
break;
600629
case 3:
@@ -613,6 +642,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
613642
data[2] = (char)(((color & Bmask) >> Bshift) << Bloss);
614643
data += 3;
615644
}
645+
pad(&data, padding);
616646
}
617647
break;
618648
case 4:
@@ -626,6 +656,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
626656
data[2] = (char)(((color & Bmask) >> Bshift) << Rloss);
627657
data += 3;
628658
}
659+
pad(&data, padding);
629660
}
630661
break;
631662
}
@@ -648,6 +679,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
648679
: (char)255;
649680
data += 4;
650681
}
682+
pad(&data, padding);
651683
}
652684
break;
653685
case 2:
@@ -667,6 +699,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
667699
: 255);
668700
data += 4;
669701
}
702+
pad(&data, padding);
670703
}
671704
break;
672705
case 3:
@@ -691,11 +724,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
691724
: 255);
692725
data += 4;
693726
}
727+
pad(&data, padding);
694728
}
695729
break;
696730
case 4:
697731
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
698-
0, 3);
732+
0, 3, padding);
699733
break;
700734
}
701735
pgSurface_Unlock(surfobj);
@@ -715,6 +749,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
715749
data[0] = (char)255;
716750
data += 4;
717751
}
752+
pad(&data, padding);
718753
}
719754
break;
720755
case 2:
@@ -731,6 +766,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
731766
: 255);
732767
data += 4;
733768
}
769+
pad(&data, padding);
734770
}
735771
break;
736772
case 3:
@@ -752,11 +788,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
752788
: 255);
753789
data += 4;
754790
}
791+
pad(&data, padding);
755792
}
756793
break;
757794
case 4:
758795
tobytes_surf_32bpp(surf, flipped, hascolorkey, colorkey, data,
759-
1, 0);
796+
1, 0, padding);
760797
break;
761798
}
762799
pgSurface_Unlock(surfobj);
@@ -776,6 +813,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
776813
data[3] = (char)255;
777814
data += 4;
778815
}
816+
pad(&data, padding);
779817
}
780818
break;
781819
case 2:
@@ -792,6 +830,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
792830
: 255);
793831
data += 4;
794832
}
833+
pad(&data, padding);
795834
}
796835
break;
797836
case 3:
@@ -813,6 +852,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
813852
: 255);
814853
data += 4;
815854
}
855+
pad(&data, padding);
816856
}
817857
break;
818858
case 4:
@@ -829,6 +869,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
829869
: 255);
830870
data += 4;
831871
}
872+
pad(&data, padding);
832873
}
833874
break;
834875
}
@@ -856,6 +897,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
856897
data[3] = (char)alpha;
857898
data += 4;
858899
}
900+
pad(&data, padding);
859901
}
860902
break;
861903
case 3:
@@ -882,6 +924,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
882924
data[3] = (char)alpha;
883925
data += 4;
884926
}
927+
pad(&data, padding);
885928
}
886929
break;
887930
case 4:
@@ -908,6 +951,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
908951
data[3] = (char)alpha;
909952
data += 4;
910953
}
954+
pad(&data, padding);
911955
}
912956
break;
913957
}
@@ -935,6 +979,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
935979
data[0] = (char)alpha;
936980
data += 4;
937981
}
982+
pad(&data, padding);
938983
}
939984
break;
940985
case 3:
@@ -961,6 +1006,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9611006
data[0] = (char)alpha;
9621007
data += 4;
9631008
}
1009+
pad(&data, padding);
9641010
}
9651011
break;
9661012
case 4:
@@ -987,6 +1033,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
9871033
data[0] = (char)alpha;
9881034
data += 4;
9891035
}
1036+
pad(&data, padding);
9901037
}
9911038
break;
9921039
}

0 commit comments

Comments
 (0)