Merge branch 'main' into deprecate-getsize
30
CHANGES.rst
|
@ -5,6 +5,36 @@ Changelog (Pillow)
|
|||
9.2.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Fixed null check for fribidi_version_info in FriBiDi shim #6376
|
||||
[nulano]
|
||||
|
||||
- Added GIF decompression bomb check #6402
|
||||
[radarhere]
|
||||
|
||||
- Handle PCF fonts files with less than 256 characters #6386
|
||||
[dawidcrivelli, radarhere]
|
||||
|
||||
- Improved GIF optimize condition #6378
|
||||
[raygard, radarhere]
|
||||
|
||||
- Reverted to __array_interface__ with the release of NumPy 1.23 #6394
|
||||
[radarhere]
|
||||
|
||||
- Pad PCX palette to 768 bytes when saving #6391
|
||||
[radarhere]
|
||||
|
||||
- Fixed bug with rounding pixels to palette colors #6377
|
||||
[btrekkie, radarhere]
|
||||
|
||||
- Use gnome-screenshot on Linux if available #6361
|
||||
[radarhere, nulano]
|
||||
|
||||
- Fixed loading L mode BMP RLE8 images #6384
|
||||
[radarhere]
|
||||
|
||||
- Fixed incorrect operator in ImageCms error #6370
|
||||
[LostBenjamin, hugovk, radarhere]
|
||||
|
||||
- Limit FPX tile size to avoid extending outside image #6368
|
||||
[radarhere]
|
||||
|
||||
|
|
BIN
Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf
Normal file
BIN
Tests/images/decompression_bomb_extents.gif
Normal file
After Width: | Height: | Size: 368 B |
BIN
Tests/images/hopper_rle8_greyscale.bmp
Normal file
After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -61,6 +61,11 @@ class TestDecompressionBomb:
|
|||
with Image.open("Tests/images/decompression_bomb.gif"):
|
||||
pass
|
||||
|
||||
def test_exception_gif_extents(self):
|
||||
with Image.open("Tests/images/decompression_bomb_extents.gif") as im:
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
im.seek(1)
|
||||
|
||||
def test_exception_bmp(self):
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
||||
|
|
|
@ -134,6 +134,9 @@ def test_rle8():
|
|||
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||
|
||||
with Image.open("Tests/images/hopper_rle8_greyscale.bmp") as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||
|
||||
# This test image has been manually hexedited
|
||||
# to have rows with too much data
|
||||
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
||||
|
|
|
@ -158,6 +158,9 @@ def test_optimize_correctness():
|
|||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||
|
||||
# These do optimize the palette
|
||||
check(256, 511, 256)
|
||||
check(255, 511, 255)
|
||||
check(129, 511, 129)
|
||||
check(128, 511, 128)
|
||||
check(64, 511, 64)
|
||||
check(4, 511, 4)
|
||||
|
@ -167,11 +170,6 @@ def test_optimize_correctness():
|
|||
check(64, 513, 256)
|
||||
check(4, 513, 256)
|
||||
|
||||
# Other limits that don't optimize the palette
|
||||
check(129, 511, 256)
|
||||
check(255, 511, 256)
|
||||
check(256, 511, 256)
|
||||
|
||||
|
||||
def test_optimize_full_l():
|
||||
im = Image.frombytes("L", (16, 16), bytes(range(256)))
|
||||
|
@ -180,6 +178,19 @@ def test_optimize_full_l():
|
|||
assert im.mode == "L"
|
||||
|
||||
|
||||
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||
with Image.open("Tests/images/test.colors.gif") as im:
|
||||
# Reduce dimensions because original is too big for _get_optimize()
|
||||
im = im.resize((591, 443))
|
||||
im_rgb = im.convert("RGB")
|
||||
|
||||
for (optimize, colors) in ((False, 256), (True, 8)):
|
||||
out = BytesIO()
|
||||
im_rgb.save(out, "GIF", optimize=optimize)
|
||||
with Image.open(out) as reloaded:
|
||||
assert len(reloaded.palette.palette) // 3 == colors
|
||||
|
||||
|
||||
def test_roundtrip(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im = hopper()
|
||||
|
@ -982,8 +993,8 @@ def test_append_images(tmp_path):
|
|||
def test_transparent_optimize(tmp_path):
|
||||
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
||||
# transparency.
|
||||
# Need a palette that isn't using the 0 color, and one that's > 128 items where the
|
||||
# transparent color is actually the top palette entry to trigger the bug.
|
||||
# Need a palette that isn't using the 0 color,
|
||||
# where the transparent color is actually the top palette entry to trigger the bug.
|
||||
|
||||
data = bytes(range(1, 254))
|
||||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||
|
@ -993,10 +1004,10 @@ def test_transparent_optimize(tmp_path):
|
|||
im.putpalette(palette)
|
||||
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im.save(out, transparency=253)
|
||||
with Image.open(out) as reloaded:
|
||||
im.save(out, transparency=im.getpixel((252, 0)))
|
||||
|
||||
assert reloaded.info["transparency"] == 253
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
|
||||
|
||||
|
||||
def test_rgb_transparency(tmp_path):
|
||||
|
|
|
@ -20,6 +20,11 @@ def test_sanity(tmp_path):
|
|||
for mode in ("1", "L", "P", "RGB"):
|
||||
_roundtrip(tmp_path, hopper(mode))
|
||||
|
||||
# Test a palette with less than 256 colors
|
||||
im = Image.new("P", (1, 1))
|
||||
im.putpalette((255, 0, 0))
|
||||
_roundtrip(tmp_path, im)
|
||||
|
||||
# Test an unsupported mode
|
||||
f = str(tmp_path / "temp.pcx")
|
||||
im = hopper("RGBA")
|
||||
|
|
|
@ -49,6 +49,14 @@ def test_sanity(request, tmp_path):
|
|||
save_font(request, tmp_path)
|
||||
|
||||
|
||||
def test_less_than_256_characters():
|
||||
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
|
||||
font = PcfFontFile.PcfFontFile(test_file)
|
||||
assert isinstance(font, FontFile.FontFile)
|
||||
# check the number of characters in the font
|
||||
assert len([_f for _f in font.glyph if _f]) == 127
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||
with pytest.raises(SyntaxError):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
@ -34,6 +35,7 @@ def test_toarray():
|
|||
test_with_dtype(numpy.float64)
|
||||
test_with_dtype(numpy.uint8)
|
||||
|
||||
if parse_version(numpy.__version__) >= parse_version("1.23"):
|
||||
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
||||
with pytest.raises(OSError):
|
||||
numpy.array(im_truncated)
|
||||
|
|
|
@ -9,7 +9,7 @@ def test_entropy():
|
|||
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
|
||||
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
|
||||
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
|
||||
assert round(abs(entropy("P") - 5.0530452472519745), 7) == 0
|
||||
assert round(abs(entropy("P") - 5.082506854662517), 7) == 0
|
||||
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
|
||||
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
|
||||
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0
|
||||
|
|
|
@ -16,7 +16,7 @@ def test_getcolors():
|
|||
assert getcolors("L") == 255
|
||||
assert getcolors("I") == 255
|
||||
assert getcolors("F") == 255
|
||||
assert getcolors("P") == 90 # fixed palette
|
||||
assert getcolors("P") == 96 # fixed palette
|
||||
assert getcolors("RGB") is None
|
||||
assert getcolors("RGBA") is None
|
||||
assert getcolors("CMYK") is None
|
||||
|
|
|
@ -10,7 +10,7 @@ def test_histogram():
|
|||
assert histogram("L") == (256, 0, 662)
|
||||
assert histogram("I") == (256, 0, 662)
|
||||
assert histogram("F") == (256, 0, 662)
|
||||
assert histogram("P") == (256, 0, 1871)
|
||||
assert histogram("P") == (256, 0, 1551)
|
||||
assert histogram("RGB") == (768, 4, 675)
|
||||
assert histogram("RGBA") == (1024, 0, 16384)
|
||||
assert histogram("CMYK") == (1024, 0, 16384)
|
||||
|
|
|
@ -65,6 +65,22 @@ def test_quantize_no_dither():
|
|||
assert converted.palette.palette == palette.palette.palette
|
||||
|
||||
|
||||
def test_quantize_no_dither2():
|
||||
im = Image.new("RGB", (9, 1))
|
||||
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
|
||||
|
||||
palette = Image.new("P", (1, 1))
|
||||
data = (0, 0, 0, 32, 32, 32)
|
||||
palette.putpalette(data)
|
||||
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||
|
||||
assert tuple(quantized.palette.palette) == data
|
||||
|
||||
px = quantized.load()
|
||||
for x in range(9):
|
||||
assert px[x, 0] == (0 if x < 5 else 1)
|
||||
|
||||
|
||||
def test_quantize_dither_diff():
|
||||
image = hopper()
|
||||
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
||||
|
|
|
@ -94,4 +94,3 @@ Indices and tables
|
|||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
|
@ -15,7 +15,10 @@ or the clipboard to a PIL image memory.
|
|||
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
|
||||
If the bounding box is omitted, the entire screen is copied.
|
||||
|
||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux (X11))
|
||||
On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it
|
||||
is installed. To capture the default X11 display instead, pass ``xdisplay=""``.
|
||||
|
||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
||||
|
||||
:param bbox: What region to copy. Default is the entire screen.
|
||||
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
9.2.0
|
||||
-----
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
|
@ -46,14 +40,6 @@ Image.coerce_e
|
|||
This undocumented method has been deprecated and will be removed in Pillow 10
|
||||
(2023-07-01).
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
|
@ -68,15 +54,14 @@ The image's palette mode will become "RGBA", and "transparency" will be removed
|
|||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
An additional decompression bomb check has been added for the GIF format.
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Using gnome-screenshot on Linux
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
In :py:meth:`~PIL.ImageGrab.grab` on Linux, if ``xdisplay`` is ``None`` then
|
||||
``gnome-screenshot`` will be used to capture the display if it is installed. To capture
|
||||
the default X11 display instead, pass ``xdisplay=""``.
|
||||
|
|
|
@ -321,7 +321,8 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
|||
# align to 16-bit word boundary
|
||||
if self.fd.tell() % 2 != 0:
|
||||
self.fd.seek(1, os.SEEK_CUR)
|
||||
self.set_as_raw(bytes(data), ("P", 0, self.args[-1]))
|
||||
rawmode = "L" if self.mode == "L" else "P"
|
||||
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
|
||||
return -1, 0
|
||||
|
||||
|
||||
|
|
|
@ -265,6 +265,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
||||
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
||||
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
||||
Image._decompression_bomb_check(self._size)
|
||||
frame_dispose_extent = x0, y0, x1, y1
|
||||
flags = s[8]
|
||||
|
||||
|
@ -824,9 +825,18 @@ def _get_optimize(im, info):
|
|||
if count:
|
||||
used_palette_colors.append(i)
|
||||
|
||||
if optimise or (
|
||||
len(used_palette_colors) <= 128
|
||||
and max(used_palette_colors) > len(used_palette_colors)
|
||||
if optimise or max(used_palette_colors) >= len(used_palette_colors):
|
||||
return used_palette_colors
|
||||
|
||||
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
|
||||
im.palette.mode
|
||||
)
|
||||
current_palette_size = 1 << (num_palette_colors - 1).bit_length()
|
||||
if (
|
||||
# check that the palette would become smaller when saved
|
||||
len(used_palette_colors) <= current_palette_size // 2
|
||||
# check that the palette is not already the smallest possible size
|
||||
and current_palette_size > 2
|
||||
):
|
||||
return used_palette_colors
|
||||
|
||||
|
|
|
@ -671,14 +671,9 @@ class Image:
|
|||
raise ValueError("Could not save to PNG for display") from e
|
||||
return b.getvalue()
|
||||
|
||||
class _ArrayData:
|
||||
def __init__(self, new):
|
||||
self.__array_interface__ = new
|
||||
|
||||
def __array__(self, dtype=None):
|
||||
@property
|
||||
def __array_interface__(self):
|
||||
# numpy array interface support
|
||||
import numpy as np
|
||||
|
||||
new = {}
|
||||
shape, typestr = _conv_type_shape(self)
|
||||
new["shape"] = shape
|
||||
|
@ -690,8 +685,7 @@ class Image:
|
|||
new["data"] = self.tobytes("raw", "L")
|
||||
else:
|
||||
new["data"] = self.tobytes()
|
||||
|
||||
return np.array(self._ArrayData(new), dtype)
|
||||
return new
|
||||
|
||||
def __getstate__(self):
|
||||
return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()]
|
||||
|
|
|
@ -15,15 +15,14 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from . import Image
|
||||
|
||||
if sys.platform == "darwin":
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None):
|
||||
if xdisplay is None:
|
||||
|
@ -62,6 +61,18 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
|||
left, top, right, bottom = bbox
|
||||
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||
return im
|
||||
elif shutil.which("gnome-screenshot"):
|
||||
fh, filepath = tempfile.mkstemp(".png")
|
||||
os.close(fh)
|
||||
subprocess.call(["gnome-screenshot", "-f", filepath])
|
||||
im = Image.open(filepath)
|
||||
im.load()
|
||||
os.unlink(filepath)
|
||||
if bbox:
|
||||
im_cropped = im.crop(bbox)
|
||||
im.close()
|
||||
return im_cropped
|
||||
return im
|
||||
# use xdisplay=None for default display on non-win32/macOS systems
|
||||
if not Image.core.HAVE_XCB:
|
||||
raise OSError("Pillow was built without XCB support")
|
||||
|
|
|
@ -84,8 +84,7 @@ class PcfFontFile(FontFile.FontFile):
|
|||
#
|
||||
# create glyph structure
|
||||
|
||||
for ch in range(256):
|
||||
ix = encoding[ch]
|
||||
for ch, ix in enumerate(encoding):
|
||||
if ix is not None:
|
||||
x, y, l, r, w, a, d, f = metrics[ix]
|
||||
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix]
|
||||
|
@ -219,10 +218,6 @@ class PcfFontFile(FontFile.FontFile):
|
|||
return bitmaps
|
||||
|
||||
def _load_encoding(self):
|
||||
|
||||
# map character code to bitmap index
|
||||
encoding = [None] * 256
|
||||
|
||||
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
||||
|
||||
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
|
||||
|
@ -232,6 +227,9 @@ class PcfFontFile(FontFile.FontFile):
|
|||
|
||||
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
|
||||
|
||||
# map character code to bitmap index
|
||||
encoding = [None] * min(256, nencoding)
|
||||
|
||||
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
||||
|
||||
for i in range(first_col, len(encoding)):
|
||||
|
|
|
@ -198,7 +198,9 @@ def _save(im, fp, filename):
|
|||
if im.mode == "P":
|
||||
# colour palette
|
||||
fp.write(o8(12))
|
||||
fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
|
||||
palette = im.im.getpalette("RGB", "RGB")
|
||||
palette += b"\x00" * (768 - len(palette))
|
||||
fp.write(palette) # 768 bytes
|
||||
elif im.mode == "L":
|
||||
# greyscale palette
|
||||
fp.write(o8(12))
|
||||
|
|
|
@ -200,15 +200,15 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
|
|||
|
||||
/* Find min and max distances to any point in the box */
|
||||
r = palette->palette[i * 4 + 0];
|
||||
tmin = (r < r0) ? RDIST(r, r1) : (r > r1) ? RDIST(r, r0) : 0;
|
||||
tmin = (r < r0) ? RDIST(r, r0) : (r > r1) ? RDIST(r, r1) : 0;
|
||||
tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);
|
||||
|
||||
g = palette->palette[i * 4 + 1];
|
||||
tmin += (g < g0) ? GDIST(g, g1) : (g > g1) ? GDIST(g, g0) : 0;
|
||||
tmin += (g < g0) ? GDIST(g, g0) : (g > g1) ? GDIST(g, g1) : 0;
|
||||
tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);
|
||||
|
||||
b = palette->palette[i * 4 + 2];
|
||||
tmin += (b < b0) ? BDIST(b, b1) : (b > b1) ? BDIST(b, b0) : 0;
|
||||
tmin += (b < b0) ? BDIST(b, b0) : (b > b1) ? BDIST(b, b1) : 0;
|
||||
tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);
|
||||
|
||||
dmin[i] = tmin;
|
||||
|
|
10
src/thirdparty/fribidi-shim/fribidi.c
vendored
|
@ -33,6 +33,7 @@ static void fribidi_get_bracket_types_compat(
|
|||
|
||||
int load_fribidi(void) {
|
||||
int error = 0;
|
||||
const char **p_fribidi_version_info = 0;
|
||||
|
||||
p_fribidi = 0;
|
||||
|
||||
|
@ -87,20 +88,21 @@ int load_fribidi(void) {
|
|||
LOAD_FUNCTION(fribidi_get_par_embedding_levels);
|
||||
|
||||
#ifndef _WIN32
|
||||
fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info");
|
||||
if (error || (fribidi_version_info == 0)) {
|
||||
p_fribidi_version_info = (const char**)dlsym(p_fribidi, "fribidi_version_info");
|
||||
if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
|
||||
dlclose(p_fribidi);
|
||||
p_fribidi = 0;
|
||||
return 2;
|
||||
}
|
||||
#else
|
||||
fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
|
||||
if (error || (fribidi_version_info == 0)) {
|
||||
p_fribidi_version_info = (const char**)GetProcAddress(p_fribidi, "fribidi_version_info");
|
||||
if (error || (p_fribidi_version_info == 0) || (*p_fribidi_version_info == 0)) {
|
||||
FreeLibrary(p_fribidi);
|
||||
p_fribidi = 0;
|
||||
return 2;
|
||||
}
|
||||
#endif
|
||||
fribidi_version_info = *p_fribidi_version_info;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -281,9 +281,9 @@ deps = {
|
|||
"libs": [r"imagequant.lib"],
|
||||
},
|
||||
"harfbuzz": {
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.3.0.zip",
|
||||
"filename": "harfbuzz-4.3.0.zip",
|
||||
"dir": "harfbuzz-4.3.0",
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.4.1.zip",
|
||||
"filename": "harfbuzz-4.4.1.zip",
|
||||
"dir": "harfbuzz-4.4.1",
|
||||
"build": [
|
||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||
cmd_nmake(target="clean"),
|
||||
|
|