mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Upgrade to libvips v8.13.0-rc1 (#3230)
* Switch from decompress-zip to extract-zip The former seems to hang when unzipping a ZIP64 file that uses the general purpose bit flag 3 as file entry. See: https://github.com/thejoshwolfe/yauzl#no-streaming-unzip-api * Prefer to call via static member instead Makes it clearer that a static method is being called. * `flatten-orange.jpg`: save without chroma subsampling To ensure no down-scaling of the Cr/Cb channels.
This commit is contained in:
parent
e40a881ab4
commit
afc4c5bf79
@ -15,10 +15,11 @@
|
||||
'_ALLOW_KEYWORD_MACROS'
|
||||
],
|
||||
'sources': [
|
||||
'src/libvips/cplusplus/VError.cpp',
|
||||
'src/libvips/cplusplus/VConnection.cpp',
|
||||
'src/libvips/cplusplus/VError.cpp',
|
||||
'src/libvips/cplusplus/VImage.cpp',
|
||||
'src/libvips/cplusplus/VInterpolate.cpp',
|
||||
'src/libvips/cplusplus/VImage.cpp'
|
||||
'src/libvips/cplusplus/VRegion.cpp'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(sharp_vendor_dir)/include',
|
||||
|
26
package.json
26
package.json
@ -140,9 +140,9 @@
|
||||
"devDependencies": {
|
||||
"async": "^3.2.4",
|
||||
"cc": "^3.0.1",
|
||||
"decompress-zip": "^0.3.3",
|
||||
"documentation": "^13.2.5",
|
||||
"exif-reader": "^1.0.3",
|
||||
"extract-zip": "^2.0.1",
|
||||
"icc": "^2.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^10.0.0",
|
||||
@ -154,19 +154,19 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.12.2",
|
||||
"libvips": "8.13.0-rc1",
|
||||
"integrity": {
|
||||
"darwin-arm64v8": "sha512-p46s/bbJAjkOXzPISZt9HUpG9GWjwQkYnLLRLKzsBJHLtB3X6C6Y/zXI5Hd0DOojcFkks9a0kTN+uDQ/XJY19g==",
|
||||
"darwin-x64": "sha512-6vOHVZnvXwe6EXRsy29jdkUzBE6ElNpXUwd+m8vV7qy32AnXu7B9YemHsZ44vWviIwPZeXF6Nhd9EFLM0wWohw==",
|
||||
"linux-arm64v8": "sha512-XwZdS63yhqLtbFtx/0eoLF/Agf5qtTrI11FMnMRpuBJWd4jHB60RAH+uzYUgoChCmKIS+AeXYMLm4d8Ns2QX8w==",
|
||||
"linux-armv6": "sha512-Rh0Q0kqwPG2MjXWOkPCuPEyiUKFgKJYWLgS835D4MrXgdKr8Tft/eVrc2iGIxt2re30VpDiZ1h0Rby1aCZt2zw==",
|
||||
"linux-armv7": "sha512-heTS/MsmRvu4JljINxP+vDiS9ZZfuGhr3IStb5F7Gc0/QLRhllYAg4rcO8L1eTK9sIIzG5ARvI19+YUZe7WbzA==",
|
||||
"linux-x64": "sha512-SSWAwBFi0hx8V/h/v82tTFGKWTFv9FiCK3Timz5OExuI+sX1Ngrd0PVQaWXOThGNdel/fcD3Bz9YjSt4feBR1g==",
|
||||
"linuxmusl-arm64v8": "sha512-Rhks+5C7p7aO6AucLT1uvzo8ohlqcqCUPgZmN+LZjsPWob/Iix3MfiDYtv/+gTvdeEfXxbCU6/YuPBwHQ7/crA==",
|
||||
"linuxmusl-x64": "sha512-IOyjSQqpWVntqOUpCHVWuQwACwmmjdi15H8Pc+Ma1JkhPogTfVsFQWyL7DuOTD3Yr23EuYGzovUX00duOtfy/g==",
|
||||
"win32-arm64v8": "sha512-A+Qe8Ipewtvw9ldvF6nWed2J8kphzrUE04nFeKCtNx6pfGQ/MAlCKMjt/U8VgUKNjB01zJDUW9XE0+FhGZ/UpQ==",
|
||||
"win32-ia32": "sha512-cMrAvwFdDeAVnLJt0IPMPRKaIFhyXYGTprsM0DND9VUHE8F7dJMR44tS5YkXsGh1QNDtjKT6YuxAVUglmiXtpA==",
|
||||
"win32-x64": "sha512-vLFIfw6aW2zABa8jpgzWDhljnE6glktrddErVyazAIoHl6BFFe/Da+LK1DbXvIYHz7fyOoKhSfCJHCiJG1Vg6w=="
|
||||
"darwin-arm64v8": "sha512-tczrNbjU/+bUhTHilUNd2OL9DgwPvUX3L6LUTQHmLZlatxT5ztj6of1erNXYhgt4lLhruKcKkYYQyRicBoKsRA==",
|
||||
"darwin-x64": "sha512-eonsBwyPEHxFL++bCG+rEUokSOaCbImSJ5VA//Qkv34QAO+vZWzj9RMf/ETvT3pHzrx56Wm3SwpGgJ/BIeyKgg==",
|
||||
"linux-arm64v8": "sha512-/hLYMwmWG3wKyUeDvNZCMJL3mZ+/8mj+VfVbo2DsNsI6MxBnZ4aXg3qMJ17FcEa2sPDHhkVqaJl5DnXGn87gGQ==",
|
||||
"linux-armv6": "sha512-25Ml2+J+KWj7yKnZVcrxZmZHFIwzbW6d/kACT7duz48NmGWDD0jnu/nroAETYiMqS1W5fHlPRiwG82/d/PeIpg==",
|
||||
"linux-armv7": "sha512-7u6RS3IFptQMGRMujyvz1kh8YFPBRdEu9T1zJKYl0/cZkxeNNiljW4PenOQ6cxoPlXivUDpEOoifo4B6a3B4/A==",
|
||||
"linux-x64": "sha512-v6/vcp+71p4NtoEpWUghgboVzavwX8FVa1NXBDVYMx/up4AmQEjEzOicSiLxXc97Ydhk6FJz8Qkrfp05AN0SfQ==",
|
||||
"linuxmusl-arm64v8": "sha512-8DNTwBbAna/R0/ksmaO/2TKZxjdkgSppTjmM9xIAZO7tbknuDCqi0YyJiiCP5Nh5yGtpf+PcQdSu/ca21ICwAQ==",
|
||||
"linuxmusl-x64": "sha512-3B12ehfury7m39sZ1lyTxzcf3hPAcCC/+4l16C0Mkoc6FGKPV/CJFtoCzNjUEsEJR5rMKOFBfsgZOoNk0KEzSQ==",
|
||||
"win32-arm64v8": "sha512-45gnLXiq4EtJNnhhgSY8Kgr5L0ed637Kn2SCW7jhQu9qZS3riLSM5Kk+6L1Kqc+cyNnsFbtWWBWCZp01Tg9HSw==",
|
||||
"win32-ia32": "sha512-LEodsYlTo0gkrfpHFNr9IwSMI7MTBBy/vry0MazcUAIPkiPhx3CCNbPZL7pd7vuHZ4CP5XNsArTb0RaHpMrc+w==",
|
||||
"win32-x64": "sha512-5usGDnN1pmFoqfEuHmdvKF/35NbQLlnal5T2bMWDx9kvLIOy7WSahvSGAeDg9cXjHv9SONX/qdpKuJ55c6aD4g=="
|
||||
},
|
||||
"runtime": "napi",
|
||||
"target": 5
|
||||
|
@ -366,32 +366,33 @@ namespace sharp {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (descriptor->createChannels > 0) {
|
||||
int const channels = descriptor->createChannels;
|
||||
if (channels > 0) {
|
||||
// Create new image
|
||||
if (descriptor->createNoiseType == "gaussian") {
|
||||
int const channels = descriptor->createChannels;
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
|
||||
std::vector<VImage> bands = {};
|
||||
bands.reserve(channels);
|
||||
for (int _band = 0; _band < channels; _band++) {
|
||||
bands.push_back(image.gaussnoise(
|
||||
descriptor->createWidth,
|
||||
descriptor->createHeight,
|
||||
VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
|
||||
bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
|
||||
->set("mean", descriptor->createNoiseMean)
|
||||
->set("sigma", descriptor->createNoiseSigma)));
|
||||
}
|
||||
image = image.bandjoin(bands);
|
||||
image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
|
||||
channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
|
||||
} else {
|
||||
std::vector<double> background = {
|
||||
descriptor->createBackground[0],
|
||||
descriptor->createBackground[1],
|
||||
descriptor->createBackground[2]
|
||||
};
|
||||
if (descriptor->createChannels == 4) {
|
||||
if (channels == 4) {
|
||||
background.push_back(descriptor->createBackground[3]);
|
||||
}
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
|
||||
.copy(VImage::option()->set("interpretation",
|
||||
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
|
||||
.new_from_image(background);
|
||||
}
|
||||
image.get_image()->Type = image.guess_interpretation();
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
|
@ -31,7 +31,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
@ -30,7 +30,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
@ -38,7 +38,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
@ -733,7 +732,7 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
|
||||
g_object_get( target.get_target(), "blob", &blob, NULL );
|
||||
g_object_get( target.get_target(), "blob", &blob, (void *) NULL );
|
||||
}
|
||||
else if( (operation_name = vips_foreign_find_save_buffer( filename )) ) {
|
||||
call_option_string( operation_name, option_string,
|
||||
@ -778,6 +777,32 @@ VImage::write_to_target( const char *suffix, VTarget target,
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region() const
|
||||
{
|
||||
return VRegion::new_from_image( *this );
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region( VipsRect *rect ) const
|
||||
{
|
||||
VRegion region = VRegion::new_from_image( *this );
|
||||
|
||||
region.prepare( rect );
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region( int left, int top, int width, int height ) const
|
||||
{
|
||||
VRegion region = VRegion::new_from_image( *this );
|
||||
|
||||
region.prepare( left, top, width, height );
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
#include "vips-operators.cpp"
|
||||
|
||||
std::vector<VImage>
|
||||
|
@ -31,7 +31,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
27
src/libvips/cplusplus/VRegion.cpp
Normal file
27
src/libvips/cplusplus/VRegion.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
// Object part of VRegion class
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
#include <vips/debug.h>
|
||||
|
||||
VIPS_NAMESPACE_START
|
||||
|
||||
VRegion
|
||||
VRegion::new_from_image( VImage image )
|
||||
{
|
||||
VipsRegion *region;
|
||||
|
||||
if( !(region = vips_region_new( image.get_image() )) ) {
|
||||
throw VError();
|
||||
}
|
||||
|
||||
VRegion out( region );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VIPS_NAMESPACE_END
|
@ -1,5 +1,4 @@
|
||||
// bodies for vips operations
|
||||
// Mon Nov 1 03:31:09 PM CET 2021
|
||||
// this file is generated automatically, do not edit!
|
||||
|
||||
VImage VImage::CMC2LCh( VOption *options ) const
|
||||
@ -943,6 +942,14 @@ VipsBlob *VImage::dzsave_buffer( VOption *options ) const
|
||||
return( buffer );
|
||||
}
|
||||
|
||||
void VImage::dzsave_target( VTarget target, VOption *options ) const
|
||||
{
|
||||
call( "dzsave_target",
|
||||
(options ? options : VImage::option())->
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VImage VImage::embed( int x, int y, int width, int height, VOption *options ) const
|
||||
{
|
||||
VImage out;
|
||||
@ -3521,6 +3528,14 @@ VipsBlob *VImage::tiffsave_buffer( VOption *options ) const
|
||||
return( buffer );
|
||||
}
|
||||
|
||||
void VImage::tiffsave_target( VTarget target, VOption *options ) const
|
||||
{
|
||||
call( "tiffsave_target",
|
||||
(options ? options : VImage::option())->
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VImage VImage::tilecache( VOption *options ) const
|
||||
{
|
||||
VImage out;
|
||||
|
@ -668,7 +668,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
xs.push_back(left);
|
||||
ys.push_back(top);
|
||||
}
|
||||
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
||||
image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
||||
}
|
||||
|
||||
// Reverse premultiplication after all transformations:
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include "stats.h"
|
||||
|
||||
static void* sharp_vips_init(void*) {
|
||||
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
||||
vips_init("sharp");
|
||||
return nullptr;
|
||||
}
|
||||
|
BIN
test/fixtures/expected/embed-lab-into-rgba.png
vendored
BIN
test/fixtures/expected/embed-lab-into-rgba.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.2 KiB |
BIN
test/fixtures/expected/flatten-orange.jpg
vendored
BIN
test/fixtures/expected/flatten-orange.jpg
vendored
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 6.4 KiB |
BIN
test/fixtures/expected/hilutite.jpg
vendored
BIN
test/fixtures/expected/hilutite.jpg
vendored
Binary file not shown.
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 424 KiB |
@ -23,6 +23,7 @@ describe('Alpha transparency', function () {
|
||||
.flatten({
|
||||
background: { r: 255, g: 102, b: 0 }
|
||||
})
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(400, info.width);
|
||||
@ -35,6 +36,7 @@ describe('Alpha transparency', function () {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.resize(400, 300)
|
||||
.flatten({ background: '#ff6600' })
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(400, info.width);
|
||||
|
@ -27,7 +27,9 @@ describe('AVIF', () => {
|
||||
format: 'jpeg',
|
||||
hasAlpha: false,
|
||||
hasProfile: false,
|
||||
height: 14,
|
||||
// 32 / (2048 / 858) = 13.40625
|
||||
// Math.round(13.40625) = 13
|
||||
height: 13,
|
||||
isProgressive: false,
|
||||
space: 'srgb',
|
||||
width: 32
|
||||
@ -70,7 +72,11 @@ describe('AVIF', () => {
|
||||
format: 'heif',
|
||||
hasAlpha: false,
|
||||
hasProfile: false,
|
||||
height: 14,
|
||||
// FIXME(kleisauke): https://github.com/strukturag/libheif/issues/365
|
||||
// $ vips black x.avif 32 13
|
||||
// $ vipsheader x.avif
|
||||
// x.avif: 32x12 uchar, 3 bands, srgb, heifload
|
||||
height: 12,
|
||||
isProgressive: false,
|
||||
pagePrimary: 0,
|
||||
pages: 1,
|
||||
|
@ -5,11 +5,9 @@ const sharp = require('../../');
|
||||
|
||||
const libcFamily = detectLibc.familySync();
|
||||
const usingCache = libcFamily !== detectLibc.MUSL;
|
||||
const usingSimd = !process.env.G_DEBUG;
|
||||
const concurrency =
|
||||
libcFamily === detectLibc.MUSL || process.arch === 'arm'
|
||||
? 1
|
||||
: undefined;
|
||||
const usingSimd = !(process.env.G_DEBUG || process.env.VIPS_NOVECTOR);
|
||||
const concurrency = process.env.VIPS_CONCURRENCY ||
|
||||
(libcFamily === detectLibc.MUSL || process.arch === 'arm' ? 1 : undefined);
|
||||
|
||||
beforeEach(function () {
|
||||
sharp.cache(usingCache);
|
||||
|
@ -343,7 +343,7 @@ describe('Image metadata', function () {
|
||||
assert.strictEqual(depth, 'uchar');
|
||||
assert.strictEqual(isProgressive, false);
|
||||
assert.strictEqual(pages, 10);
|
||||
assert.strictEqual(loop, 2);
|
||||
assert.strictEqual(loop, 3);
|
||||
assert.deepStrictEqual(delay, [...Array(9).fill(3000), 15000]);
|
||||
assert.strictEqual(hasProfile, false);
|
||||
assert.strictEqual(hasAlpha, true);
|
||||
|
@ -6,7 +6,7 @@ const assert = require('assert');
|
||||
|
||||
const eachLimit = require('async/eachLimit');
|
||||
const rimraf = require('rimraf');
|
||||
const DecompressZip = require('decompress-zip');
|
||||
const extractZip = require('extract-zip');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
@ -14,7 +14,8 @@ const fixtures = require('../fixtures');
|
||||
// Verifies all tiles in a given dz output directory are <= size
|
||||
const assertDeepZoomTiles = function (directory, expectedSize, expectedLevels, done) {
|
||||
// Get levels
|
||||
const levels = fs.readdirSync(directory);
|
||||
const dirents = fs.readdirSync(directory, { withFileTypes: true });
|
||||
const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
||||
assert.strictEqual(expectedLevels, levels.length);
|
||||
// Get tiles
|
||||
const tiles = [];
|
||||
@ -67,8 +68,10 @@ const assertZoomifyTiles = function (directory, expectedTileSize, expectedLevels
|
||||
};
|
||||
|
||||
const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels, done) {
|
||||
const levels = fs.readdirSync(directory);
|
||||
assert.strictEqual(expectedLevels, levels.length - 1); // subtract one to account for default blank tile
|
||||
// Get levels
|
||||
const dirents = fs.readdirSync(directory, { withFileTypes: true });
|
||||
const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
||||
assert.strictEqual(expectedLevels, levels.length);
|
||||
|
||||
fs.stat(path.join(directory, 'blank.png'), function (err, stat) {
|
||||
if (err) throw err;
|
||||
@ -94,7 +97,8 @@ const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels,
|
||||
// Verifies tiles at specified level in a given output directory are > size+overlap
|
||||
const assertTileOverlap = function (directory, tileSize, done) {
|
||||
// Get sorted levels
|
||||
const levels = fs.readdirSync(directory).sort((a, b) => a - b);
|
||||
const dirents = fs.readdirSync(directory, { withFileTypes: true });
|
||||
const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name).sort((a, b) => a - b);
|
||||
// Select the highest tile level
|
||||
const highestLevel = levels[levels.length - 1];
|
||||
// Get sorted tiles from greatest level
|
||||
@ -908,14 +912,10 @@ describe('Tile', function () {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.isFile());
|
||||
assert.strictEqual(true, stat.size > 0);
|
||||
new DecompressZip(container)
|
||||
.on('extract', function () {
|
||||
extractZip(container, { dir: path.dirname(extractTo) })
|
||||
.then(() => {
|
||||
assertDeepZoomTiles(directory, 256, 13, done);
|
||||
})
|
||||
.on('error', function (err) {
|
||||
throw err;
|
||||
})
|
||||
.extract({ path: path.dirname(extractTo) });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -942,14 +942,10 @@ describe('Tile', function () {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.isFile());
|
||||
assert.strictEqual(true, stat.size > 0);
|
||||
new DecompressZip(container)
|
||||
.on('extract', function () {
|
||||
extractZip(container, { dir: path.dirname(extractTo) })
|
||||
.then(() => {
|
||||
assertDeepZoomTiles(directory, 256, 13, done);
|
||||
})
|
||||
.on('error', function (err) {
|
||||
throw err;
|
||||
})
|
||||
.extract({ path: path.dirname(extractTo) });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -140,7 +140,6 @@ describe('Utilities', function () {
|
||||
assert.strictEqual('object', typeof sharp.vendor);
|
||||
assert.strictEqual('string', typeof sharp.vendor.current);
|
||||
assert.strictEqual(true, Array.isArray(sharp.vendor.installed));
|
||||
assert.strictEqual(true, sharp.vendor.installed.length > 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user