Adding JPEG/PNG file loading (PVRTC stub)
This commit is contained in:
committed by
Joshua Granick
parent
429f9bac86
commit
9f6debd95b
@@ -2,6 +2,7 @@ package lime.graphics;
|
||||
|
||||
|
||||
import lime.utils.UInt8Array;
|
||||
import lime.system.System;
|
||||
|
||||
#if js
|
||||
import js.html.CanvasElement;
|
||||
@@ -46,6 +47,22 @@ class Image {
|
||||
|
||||
}
|
||||
|
||||
public static function loadFromFile (path:String) {
|
||||
|
||||
#if flash
|
||||
|
||||
throw "Can not load image from file in Flash";
|
||||
|
||||
#elseif (cpp || neko)
|
||||
|
||||
var imageData = lime_load_image (path);
|
||||
|
||||
return (imageData == null ? null : new Image (new UInt8Array (imageData.data), imageData.width, imageData.height));
|
||||
|
||||
#end
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function forcePowerOfTwo () {
|
||||
|
||||
@@ -172,6 +189,13 @@ class Image {
|
||||
}
|
||||
|
||||
|
||||
#if (cpp || neko)
|
||||
|
||||
private static var lime_load_image = System.load("lime", "lime_load_image", 1);
|
||||
|
||||
#end
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<files id="lime">
|
||||
|
||||
<compilerflag value="-DLIME_SDL" if="LIME_SDL" />
|
||||
<compilerflag value="-I/usr/local/include" />
|
||||
<compilerflag value="-Iinclude" />
|
||||
|
||||
<file name="src/ExternalInterface.cpp" />
|
||||
@@ -63,6 +64,9 @@
|
||||
<file name="src/system/ios/System.mm" if="ios" />
|
||||
|
||||
<file name="src/app/UpdateEvent.cpp" />
|
||||
<file name="src/graphics/ImageData.cpp" />
|
||||
<file name="src/graphics/PVRTC.cpp" if="iphone" />
|
||||
<file name="src/graphics/JPEG.cpp" />
|
||||
<file name="src/graphics/PNG.cpp" />
|
||||
<file name="src/graphics/RenderEvent.cpp" />
|
||||
<file name="src/system/System.cpp" />
|
||||
@@ -150,8 +154,11 @@
|
||||
<vflag name="-framework" value="AppKit" />
|
||||
<vflag name="-framework" value="OpenAL"/>
|
||||
|
||||
<lib name="-L/usr/local/lib" />
|
||||
<lib name="/opt/local/lib/libgc.a" if="LIME_NEKO" />
|
||||
<lib name="-lm" if="LIME_NEKO" />
|
||||
<lib name="-lpng" />
|
||||
<lib name="-ljpeg" />
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
#ifndef LIME_GRAPHICS_IMAGE_DATA_H
|
||||
#define LIME_GRAPHICS_IMAGE_DATA_H
|
||||
|
||||
#include <hx/CFFI.h>
|
||||
#include <utils/ByteArray.h>
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
|
||||
struct ImageData {
|
||||
class ImageData {
|
||||
|
||||
public:
|
||||
|
||||
int width;
|
||||
int height;
|
||||
unsigned char data;
|
||||
ByteArray *data;
|
||||
|
||||
ImageData();
|
||||
~ImageData();
|
||||
value Value();
|
||||
|
||||
private:
|
||||
|
||||
value mValue;
|
||||
|
||||
};
|
||||
|
||||
|
||||
23
project/include/graphics/JPEG.h
Normal file
23
project/include/graphics/JPEG.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef LIME_GRAPHICS_JPEG_H
|
||||
#define LIME_GRAPHICS_JPEG_H
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
class ImageData;
|
||||
|
||||
class JPEG {
|
||||
|
||||
|
||||
public:
|
||||
|
||||
static bool Decode (const char *path, ImageData *imageData);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,19 +2,16 @@
|
||||
#define LIME_GRAPHICS_PNG_H
|
||||
|
||||
|
||||
#include <graphics/ImageData.h>
|
||||
#include <utils/ByteArray.h>
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
class ImageData;
|
||||
|
||||
class PNG {
|
||||
|
||||
|
||||
public:
|
||||
|
||||
static void Decode (ByteArray bytes, value imageData);
|
||||
static bool Decode (const char *path, ImageData *imageData);
|
||||
|
||||
|
||||
};
|
||||
|
||||
23
project/include/graphics/PVRTC.h
Normal file
23
project/include/graphics/PVRTC.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef LIME_GRAPHICS_PVRTC_H
|
||||
#define LIME_GRAPHICS_PVRTC_H
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
class ImageData;
|
||||
|
||||
class PVRTC {
|
||||
|
||||
|
||||
public:
|
||||
|
||||
static bool Decode (const char *path, ImageData *imageData);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <hx/CFFI.h>
|
||||
#include <app/Application.h>
|
||||
#include <app/UpdateEvent.h>
|
||||
#include <graphics/ImageData.h>
|
||||
#include <graphics/PNG.h>
|
||||
#include <graphics/JPEG.h>
|
||||
#include <graphics/Renderer.h>
|
||||
#include <graphics/RenderEvent.h>
|
||||
#include <system/System.h>
|
||||
@@ -49,13 +51,6 @@ namespace lime {
|
||||
}
|
||||
|
||||
|
||||
value lime_jpg_decode (value bytes, value imageData) {
|
||||
|
||||
return alloc_null ();
|
||||
|
||||
}
|
||||
|
||||
|
||||
value lime_key_event_manager_register (value callback, value eventObject) {
|
||||
|
||||
KeyEvent::callback = new AutoGCRoot (callback);
|
||||
@@ -110,10 +105,22 @@ namespace lime {
|
||||
}
|
||||
|
||||
|
||||
value lime_png_decode (value bytes, value imageData) {
|
||||
value lime_load_image (value path) {
|
||||
|
||||
ByteArray byteArray (bytes);
|
||||
PNG::Decode (bytes, imageData);
|
||||
ImageData imageData;
|
||||
const char *filePath = val_string (path);
|
||||
|
||||
if (PNG::Decode (filePath, &imageData)) {
|
||||
|
||||
return imageData.Value ();
|
||||
|
||||
}
|
||||
|
||||
if (JPEG::Decode(filePath, &imageData)) {
|
||||
|
||||
return imageData.Value ();
|
||||
|
||||
}
|
||||
|
||||
return alloc_null ();
|
||||
|
||||
@@ -190,13 +197,12 @@ namespace lime {
|
||||
DEFINE_PRIM (lime_application_create, 1);
|
||||
DEFINE_PRIM (lime_application_exec, 1);
|
||||
DEFINE_PRIM (lime_application_get_ticks, 0);
|
||||
DEFINE_PRIM (lime_jpg_decode, 2);
|
||||
DEFINE_PRIM (lime_load_image, 1);
|
||||
DEFINE_PRIM (lime_key_event_manager_register, 2);
|
||||
DEFINE_PRIM (lime_lzma_encode, 1);
|
||||
DEFINE_PRIM (lime_lzma_decode, 1);
|
||||
DEFINE_PRIM (lime_mouse_event_manager_register, 2);
|
||||
DEFINE_PRIM (lime_neko_execute, 1);
|
||||
DEFINE_PRIM (lime_png_decode, 2);
|
||||
DEFINE_PRIM (lime_renderer_create, 1);
|
||||
DEFINE_PRIM (lime_renderer_flip, 1);
|
||||
DEFINE_PRIM (lime_render_event_manager_register, 2);
|
||||
|
||||
38
project/src/graphics/ImageData.cpp
Normal file
38
project/src/graphics/ImageData.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <graphics/ImageData.h>
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
static int id_data;
|
||||
static int id_height;
|
||||
static int id_width;
|
||||
static bool init = false;
|
||||
|
||||
ImageData::ImageData():width(0), height(0), data(0) { }
|
||||
|
||||
ImageData::~ImageData() {
|
||||
|
||||
delete data;
|
||||
|
||||
}
|
||||
|
||||
value ImageData::Value() {
|
||||
|
||||
if (!init) {
|
||||
|
||||
id_width = val_id ("width");
|
||||
id_height = val_id ("height");
|
||||
id_data = val_id ("data");
|
||||
init = true;
|
||||
|
||||
}
|
||||
|
||||
mValue = alloc_empty_object ();
|
||||
alloc_field (mValue, id_width, alloc_int (width));
|
||||
alloc_field (mValue, id_height, alloc_int (height));
|
||||
alloc_field (mValue, id_data, data->mValue);
|
||||
return mValue;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
71
project/src/graphics/JPEG.cpp
Normal file
71
project/src/graphics/JPEG.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
extern "C" {
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
|
||||
}
|
||||
|
||||
#include <graphics/ImageData.h>
|
||||
#include <graphics/JPEG.h>
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
|
||||
extern FILE *OpenRead (const char *);
|
||||
|
||||
|
||||
bool JPEG::Decode (const char *path, ImageData *imageData) {
|
||||
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
|
||||
FILE *file = OpenRead (path);
|
||||
|
||||
cinfo.err = jpeg_std_error (&jerr);
|
||||
jpeg_create_decompress (&cinfo);
|
||||
jpeg_stdio_src (&cinfo, file);
|
||||
|
||||
if (jpeg_read_header (&cinfo, TRUE) == JPEG_HEADER_OK) {
|
||||
|
||||
jpeg_start_decompress (&cinfo);
|
||||
imageData->width = cinfo.output_width;
|
||||
imageData->height = cinfo.output_height;
|
||||
int components = cinfo.num_components;
|
||||
imageData->data = new ByteArray (imageData->width * imageData->height * 4);
|
||||
|
||||
unsigned char *bytes = imageData->data->Bytes();
|
||||
unsigned char *scanline = new unsigned char [imageData->width * imageData->height * components];
|
||||
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
|
||||
jpeg_read_scanlines (&cinfo, &scanline, 1);
|
||||
|
||||
// convert 24-bit scanline to 32-bit
|
||||
const unsigned char *line = scanline;
|
||||
const unsigned char *const end = line + imageData->width * components;
|
||||
|
||||
while (line != end) {
|
||||
|
||||
*bytes++ = *line++;
|
||||
*bytes++ = *line++;
|
||||
*bytes++ = *line++;
|
||||
*bytes++ = 0xFF;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete[] scanline;
|
||||
|
||||
jpeg_finish_decompress (&cinfo);
|
||||
}
|
||||
|
||||
fclose (file);
|
||||
jpeg_destroy_decompress (&cinfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,143 +1,100 @@
|
||||
extern "C" {
|
||||
|
||||
//#include <png.h>
|
||||
#include <png.h>
|
||||
#define PNG_SIG_SIZE 8
|
||||
|
||||
}
|
||||
|
||||
#include <hx/CFFI.h>
|
||||
#include <graphics/ImageData.h>
|
||||
#include <graphics/PNG.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
|
||||
static int id_data;
|
||||
static int id_height;
|
||||
static int id_width;
|
||||
static bool init = false;
|
||||
extern FILE *OpenRead (const char *);
|
||||
|
||||
|
||||
/*static void user_error_fn (png_structp png_ptr, png_const_charp error_msg) {
|
||||
|
||||
longjmp (png_ptr->jmpbuf, 1);
|
||||
|
||||
}
|
||||
|
||||
static void user_warning_fn (png_structp png_ptr, png_const_charp warning_msg) { }
|
||||
static void user_read_data_fn (png_structp png_ptr, png_bytep data, png_size_t length) {
|
||||
|
||||
png_voidp buffer = png_get_io_ptr (png_ptr);
|
||||
((ReadBuf *)buffer)->Read (data, length);
|
||||
|
||||
}
|
||||
|
||||
void user_write_data (png_structp png_ptr, png_bytep data, png_size_t length) {
|
||||
|
||||
QuickVec<unsigned char> *buffer = (QuickVec<unsigned char> *)png_get_io_ptr (png_ptr);
|
||||
buffer->append ((unsigned char *)data, (int)length);
|
||||
|
||||
}
|
||||
|
||||
void user_flush_data (png_structp png_ptr) { }*/
|
||||
|
||||
|
||||
void PNG::Decode (ByteArray bytes, value imageData) {
|
||||
|
||||
/*if (!init) {
|
||||
|
||||
id_data = val_id ("data");
|
||||
id_height = val_id ("height");
|
||||
id_width = val_id ("width");
|
||||
init = true;
|
||||
|
||||
}
|
||||
bool PNG::Decode (const char *path, ImageData *imageData) {
|
||||
|
||||
unsigned char png_sig[PNG_SIG_SIZE];
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
png_uint_32 width, height;
|
||||
int bit_depth, color_type, interlace_type;
|
||||
int bit_depth, color_type;
|
||||
|
||||
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, 0, user_error_fn, user_warning_fn);
|
||||
FILE *file = OpenRead (path);
|
||||
if (!file) return false;
|
||||
|
||||
if (png_ptr == NULL)
|
||||
return;
|
||||
// verify the PNG signature
|
||||
fread(png_sig, PNG_SIG_SIZE, 1, file);
|
||||
if (png_sig_cmp (png_sig, 0, PNG_SIG_SIZE)) {
|
||||
|
||||
info_ptr = png_create_info_struct (png_ptr);
|
||||
fclose (file);
|
||||
return false;
|
||||
|
||||
if (info_ptr == NULL) {
|
||||
}
|
||||
|
||||
if ((png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) {
|
||||
|
||||
fclose (file);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
if ((info_ptr = png_create_info_struct (png_ptr)) == NULL) {
|
||||
|
||||
png_destroy_read_struct (&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
|
||||
return;
|
||||
fclose (file);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
unsigned char* bytes[width * height * 4];
|
||||
RenderTarget target;
|
||||
|
||||
// sets the point which libpng will jump back to in the case of an error
|
||||
if (setjmp (png_jmpbuf (png_ptr))) {
|
||||
|
||||
if (bytes) {
|
||||
|
||||
delete bytes;
|
||||
|
||||
}
|
||||
|
||||
png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
|
||||
return;
|
||||
fclose (file);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
ReadBuf buffer (inData, inDataLen);
|
||||
|
||||
//if (inFile) {
|
||||
|
||||
//png_init_io (png_ptr, inFile);
|
||||
|
||||
//} else {
|
||||
|
||||
png_set_read_fn (png_ptr, (void *)&buffer, user_read_data_fn);
|
||||
|
||||
//}
|
||||
|
||||
png_init_io (png_ptr, file);
|
||||
png_set_sig_bytes (png_ptr, PNG_SIG_SIZE);
|
||||
png_read_info (png_ptr, info_ptr);
|
||||
png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL);
|
||||
|
||||
bool has_alpha = (color_type == PNG_COLOR_TYPE_GRAY_ALPHA || color_type == PNG_COLOR_TYPE_RGB_ALPHA || png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS));
|
||||
width = png_get_image_width (png_ptr, info_ptr);
|
||||
height = png_get_image_height (png_ptr, info_ptr);
|
||||
color_type = png_get_color_type (png_ptr, info_ptr);
|
||||
bit_depth = png_get_bit_depth (png_ptr, info_ptr);
|
||||
|
||||
png_set_expand (png_ptr);
|
||||
png_set_filler (png_ptr, 0xff, PNG_FILLER_AFTER);
|
||||
|
||||
png_set_palette_to_rgb (png_ptr);
|
||||
png_set_gray_to_rgb (png_ptr);
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16 (png_ptr);
|
||||
|
||||
png_set_bgr (png_ptr);
|
||||
const unsigned int stride = width * 4;
|
||||
imageData->width = width;
|
||||
imageData->height = height;
|
||||
imageData->data = new ByteArray (height * stride);
|
||||
|
||||
//result = new ImageData ();
|
||||
//result.width = width;
|
||||
//result.height = height;
|
||||
//result.data = uint8[width * height * 4];
|
||||
png_bytepp row_ptrs = new png_bytep[height];
|
||||
unsigned char *bytes = imageData->data->Bytes();
|
||||
|
||||
png_read_png (png_ptr, (png_bytepp)&bytes);
|
||||
for (size_t i = 0; i < height; i++) {
|
||||
|
||||
png_read_end (png_ptr, info_ptr);
|
||||
row_ptrs[i] = bytes + i * stride;
|
||||
|
||||
}
|
||||
|
||||
png_read_image (png_ptr, row_ptrs);
|
||||
png_read_end (png_ptr, NULL);
|
||||
|
||||
delete[] row_ptrs;
|
||||
png_destroy_read_struct (&png_ptr, &info_ptr, (png_infopp)NULL);
|
||||
|
||||
|
||||
value object = (KeyEvent::eventObject ? KeyEvent::eventObject->get () : alloc_empty_object ());
|
||||
|
||||
alloc_field (object, id_code, alloc_int (event->code));
|
||||
alloc_field (object, id_type, alloc_int (event->type));
|
||||
|
||||
|
||||
alloc_field (imageData, id_width, alloc_int (width));
|
||||
alloc_field (imageData, id_height, alloc_int (height));
|
||||
alloc_field (imageData, id_data, alloc_int (bytes));
|
||||
|
||||
return result;*/
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
58
project/src/graphics/PVRTC.cpp
Normal file
58
project/src/graphics/PVRTC.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
extern "C" {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#include <graphics/ImageData.h>
|
||||
#include <graphics/PVRTC.h>
|
||||
|
||||
|
||||
typedef struct _PVRTexHeader {
|
||||
|
||||
uint32_t headerLength;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t numMipmaps;
|
||||
uint32_t flags;
|
||||
uint32_t dataLength;
|
||||
uint32_t bpp;
|
||||
uint32_t bitmaskRed;
|
||||
uint32_t bitmaskGreen;
|
||||
uint32_t bitmaskBlue;
|
||||
uint32_t bitmaskAlpha;
|
||||
uint32_t pvrTag;
|
||||
uint32_t numSurfs;
|
||||
|
||||
} PVRTexHeader;
|
||||
|
||||
static char gPVRTexIdentifier[5] = "PVR!";
|
||||
|
||||
|
||||
namespace lime {
|
||||
|
||||
|
||||
extern FILE *OpenRead (const char *);
|
||||
|
||||
|
||||
bool PVRTC::Decode (const char *path, ImageData *imageData) {
|
||||
|
||||
PVRTexHeader header;
|
||||
FILE *file = OpenRead (path);
|
||||
|
||||
fread (&header, sizeof(header), 1, file);
|
||||
|
||||
if (gPVRTexIdentifier[0] != ((header.pvrTag >> 0) & 0xFF) ||
|
||||
gPVRTexIdentifier[1] != ((header.pvrTag >> 8) & 0xFF) ||
|
||||
gPVRTexIdentifier[2] != ((header.pvrTag >> 16) & 0xFF) ||
|
||||
gPVRTexIdentifier[3] != ((header.pvrTag >> 24) & 0xFF)) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,6 +48,9 @@ namespace lime {
|
||||
|
||||
void ByteArray::Resize(int inSize)
|
||||
{
|
||||
if (mValue == 0)
|
||||
mValue = val_call1(gByteArrayCreate->get(), alloc_int(inSize) );
|
||||
else
|
||||
val_call2(gByteArrayResize->get(), mValue, alloc_int(inSize) );
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,11 @@ class DefaultAssetLibrary extends AssetLibrary {
|
||||
|
||||
if (Sys.args ().indexOf ("-livereload") > -1) {
|
||||
|
||||
#if mac
|
||||
var path = FileSystem.fullPath ("../Resources/manifest");
|
||||
#else
|
||||
var path = FileSystem.fullPath ("manifest");
|
||||
#end
|
||||
lastModified = FileSystem.stat (path).mtime.getTime ();
|
||||
|
||||
timer = new Timer (2000);
|
||||
@@ -211,39 +215,9 @@ class DefaultAssetLibrary extends AssetLibrary {
|
||||
|
||||
#else
|
||||
|
||||
// TODO: Implement in native backend
|
||||
|
||||
var bytes = getBytes (id);
|
||||
var byteInput = new haxe.io.BytesInput (bytes, 0, bytes.length);
|
||||
var png = new format.png.Reader (byteInput).read ();
|
||||
var data = format.png.Tools.extract32 (png);
|
||||
var header = format.png.Tools.getHeader (png);
|
||||
|
||||
var imageWidth = header.width;
|
||||
var imageHeight = header.height;
|
||||
var imageData = new UInt8Array (ByteArray.fromBytes (data));
|
||||
|
||||
var imageLength = imageWidth * imageHeight;
|
||||
var b, g, r, a;
|
||||
|
||||
for (i in 0...imageLength) {
|
||||
|
||||
b = imageData[i * 4];
|
||||
g = imageData[i * 4 + 1];
|
||||
r = imageData[i * 4 + 2];
|
||||
a = imageData[i * 4 + 3];
|
||||
|
||||
imageData[i * 4] = r;
|
||||
imageData[i * 4 + 1] = g;
|
||||
imageData[i * 4 + 2] = b;
|
||||
imageData[i * 4 + 3] = a;
|
||||
|
||||
}
|
||||
|
||||
return new Image (imageData, imageWidth, imageHeight);
|
||||
|
||||
//if (className.exists(id)) return cast (Type.createInstance (className.get (id), []), BitmapData);
|
||||
//else return BitmapData.load (path.get (id));
|
||||
var image = Image.loadFromFile (path.get (id));
|
||||
if (image == null) return null;
|
||||
return new Image(image.bytes, image.width, image.height);
|
||||
|
||||
#end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user