Files
AnyPS5/core/libs/prx/libcairo/Surface.cpp
T

307 lines
10 KiB
C++

#include "prx/libcairo/include/Surface.hpp"
#include <atomic>
#include <cmath>
#include <cstdint>
#include <limits>
#include <new>
namespace {
constexpr int kSuccess = 0;
constexpr int kNullPointer = 7;
constexpr int kInvalidContent = 15;
constexpr int kInvalidFormat = 16;
constexpr int kInvalidStride = 24;
constexpr int kInvalidSize = 32;
constexpr int kNoMemory = 1;
constexpr int kColor = 0x1000;
constexpr int kAlpha = 0x2000;
constexpr int kColorAlpha = 0x3000;
constexpr int kImageSurfaceType = 0;
constexpr int kInvalidFormatValue = -1;
int BitsPerPixel(int format) {
switch (format) {
case 0: case 1: case 5: return 32;
case 2: return 8;
case 3: return 1;
case 4: return 16;
case 6: return 96;
case 7: return 128;
default: return 0;
}
}
int ContentForFormat(int format) {
switch (format) {
case 0: case 7: return kColorAlpha;
case 2: case 3: return kAlpha;
default: return kColor;
}
}
int FormatForContent(int content) {
switch (content) {
case kColor: return 1;
case kAlpha: return 2;
case kColorAlpha: return 0;
default: return -1;
}
}
int Stride(int format, int width) {
const int bits = BitsPerPixel(format);
if (!bits || width < 0) return -1;
const auto bitCount = static_cast<std::int64_t>(width) * bits + 31;
if (bitCount > std::numeric_limits<int>::max()) return -1;
const auto n = bitCount / 32 * 4;
return n > std::numeric_limits<int>::max() ? -1 : static_cast<int>(n);
}
}
struct CairoSurfaceState {
std::atomic<unsigned> references{1};
int status{kSuccess};
int format{kInvalidFormatValue};
int content{kColor};
int width{};
int height{};
int stride{};
unsigned char* data{};
bool ownsData{};
bool isImage{true};
CairoSurface* parent{};
double originX{};
double originY{};
double extentWidth{};
double extentHeight{};
double scaleX{1};
double scaleY{1};
CairoSurfaceState() = default;
explicit CairoSurfaceState(int failure) : status(failure) {}
~CairoSurfaceState();
};
namespace {
CairoSurfaceState* State(CairoSurface* handle) {
return reinterpret_cast<CairoSurfaceState*>(handle);
}
CairoSurface* Handle(CairoSurfaceState* state) {
return reinterpret_cast<CairoSurface*>(state);
}
void ReleaseSurface(CairoSurface* surface);
}
CairoSurfaceState::~CairoSurfaceState() {
if (ownsData) delete[] data;
if (parent) ReleaseSurface(parent);
}
namespace {
void ReleaseSurface(CairoSurface* surface) {
if (!surface || State(surface)->status != kSuccess) return;
if (State(surface)->references.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete State(surface);
}
CairoSurfaceState* NewSurface() {
return new (std::nothrow) CairoSurfaceState;
}
CairoSurface* ErrorSurface(int status) {
static CairoSurfaceState noMemory{kNoMemory};
static CairoSurfaceState nullPointer{kNullPointer};
static CairoSurfaceState invalidContent{kInvalidContent};
static CairoSurfaceState invalidFormat{kInvalidFormat};
static CairoSurfaceState invalidStride{kInvalidStride};
static CairoSurfaceState invalidSize{kInvalidSize};
switch (status) {
case kNullPointer: return Handle(&nullPointer);
case kInvalidContent: return Handle(&invalidContent);
case kInvalidFormat: return Handle(&invalidFormat);
case kInvalidStride: return Handle(&invalidStride);
case kInvalidSize: return Handle(&invalidSize);
default: return Handle(&noMemory);
}
}
CairoSurface* MakeImage(int format, int width, int height,
int suppliedStride, unsigned char* externalData, bool borrowed) {
if (!BitsPerPixel(format)) return ErrorSurface(kInvalidFormat);
const int minimumStride = Stride(format, width);
const std::int64_t magnitude = suppliedStride < 0
? -static_cast<std::int64_t>(suppliedStride)
: suppliedStride;
if (borrowed && (suppliedStride % 4 != 0 ||
(minimumStride >= 0 && magnitude < minimumStride)))
return ErrorSurface(kInvalidStride);
if (minimumStride < 0 || width > 32767 || height < 0 || height > 32767)
return ErrorSurface(kInvalidSize);
if (borrowed && format == 7 && width == 0 && magnitude > 0 && magnitude < 16)
return ErrorSurface(kNoMemory);
const bool ownsPixels = !borrowed || (!externalData && width > 0 && height > 0);
const int stride = !borrowed && (!width || !height) ? 0 :
ownsPixels ? minimumStride : suppliedStride;
if (height && static_cast<std::uint64_t>(height) * stride >
std::numeric_limits<std::size_t>::max())
return ErrorSurface(kInvalidSize);
auto* result = NewSurface();
if (!result) return ErrorSurface(kNoMemory);
result->format = format;
result->content = ContentForFormat(format);
result->width = width;
result->height = height;
result->stride = stride;
result->extentWidth = width;
result->extentHeight = height;
if (!ownsPixels) {
result->data = externalData;
} else {
const auto size = static_cast<std::size_t>(stride) * height;
if (size) {
result->data = new (std::nothrow) unsigned char[size]();
if (!result->data) {
delete result;
return ErrorSurface(kNoMemory);
}
result->ownsData = true;
}
}
return Handle(result);
}
}
extern "C" {
int APS5_VABI cairo_format_stride_for_width(int format, int width) {
return Stride(format, width);
}
CairoSurface* APS5_VABI cairo_image_surface_create(int format, int width, int height) {
return MakeImage(format, width, height, 0, nullptr, false);
}
CairoSurface* APS5_VABI cairo_image_surface_create_for_data(unsigned char* data, int format,
int width, int height, int stride) {
return MakeImage(format, width, height, stride, data, true);
}
unsigned char* APS5_VABI cairo_image_surface_get_data(CairoSurface* surface) {
return surface && State(surface)->status == kSuccess && State(surface)->isImage ? State(surface)->data : nullptr;
}
int APS5_VABI cairo_image_surface_get_format(CairoSurface* surface) {
return surface && State(surface)->status == kSuccess && State(surface)->isImage
? State(surface)->format : kInvalidFormatValue;
}
int APS5_VABI cairo_image_surface_get_width(CairoSurface* surface) {
return surface && State(surface)->status == kSuccess && State(surface)->isImage ? State(surface)->width : 0;
}
int APS5_VABI cairo_image_surface_get_height(CairoSurface* surface) {
return surface && State(surface)->status == kSuccess && State(surface)->isImage ? State(surface)->height : 0;
}
int APS5_VABI cairo_image_surface_get_stride(CairoSurface* surface) {
return surface && State(surface)->status == kSuccess && State(surface)->isImage ? State(surface)->stride : 0;
}
CairoSurface* APS5_VABI cairo_surface_reference(CairoSurface* surface) {
if (surface && State(surface)->status == kSuccess)
State(surface)->references.fetch_add(1, std::memory_order_relaxed);
return surface;
}
void APS5_VABI cairo_surface_destroy(CairoSurface* surface) {
ReleaseSurface(surface);
}
int APS5_VABI cairo_surface_status(CairoSurface* surface) {
return surface ? State(surface)->status : kNullPointer;
}
int APS5_VABI cairo_surface_get_type(CairoSurface*) {
return kImageSurfaceType;
}
int APS5_VABI cairo_surface_get_content(CairoSurface* surface) {
return surface ? State(surface)->content : kColor;
}
void APS5_VABI cairo_surface_flush(CairoSurface*) {
}
void APS5_VABI cairo_surface_mark_dirty_rectangle(CairoSurface*, int, int, int, int) {
}
void APS5_VABI cairo_surface_set_device_scale(CairoSurface* surface, double x, double y) {
if (!surface || State(surface)->status != kSuccess) return;
State(surface)->scaleX = x;
State(surface)->scaleY = y;
}
void APS5_VABI cairo_surface_get_device_scale(CairoSurface* surface, double* x, double* y) {
if (x) *x = surface && State(surface)->status == kSuccess ? State(surface)->scaleX : 1;
if (y) *y = surface && State(surface)->status == kSuccess ? State(surface)->scaleY : 1;
}
CairoSurface* APS5_VABI cairo_surface_create_similar(CairoSurface* other, int content,
int width, int height) {
if (!other) return ErrorSurface(kNullPointer);
if (State(other)->status != kSuccess) return ErrorSurface(State(other)->status);
if (width < 0 || height < 0) return ErrorSurface(kInvalidSize);
int format = FormatForContent(content);
if (format < 0) return ErrorSurface(kInvalidContent);
if (State(other)->isImage && content == State(other)->content)
format = State(other)->format;
const double deviceWidth = static_cast<double>(width) * State(other)->scaleX;
const double deviceHeight = static_cast<double>(height) * State(other)->scaleY;
if (!std::isfinite(deviceWidth) || !std::isfinite(deviceHeight) ||
deviceWidth < 0 || deviceHeight < 0 ||
deviceWidth > 32767 || deviceHeight > 32767)
return ErrorSurface(kInvalidSize);
auto* result = MakeImage(format, static_cast<int>(deviceWidth),
static_cast<int>(deviceHeight), 0, nullptr, false);
if (result && State(result)->status == kSuccess) {
State(result)->scaleX = State(other)->scaleX;
State(result)->scaleY = State(other)->scaleY;
}
return result;
}
CairoSurface* APS5_VABI cairo_surface_create_for_rectangle(CairoSurface* target,
double x, double y,
double width, double height) {
if (!target) return ErrorSurface(kNullPointer);
if (State(target)->status != kSuccess) return ErrorSurface(State(target)->status);
if (width < 0 || height < 0) return ErrorSurface(kInvalidSize);
auto* result = NewSurface();
if (!result) return ErrorSurface(kNoMemory);
result->isImage = false;
result->content = State(target)->content;
result->parent = cairo_surface_reference(target);
result->originX = x;
result->originY = y;
result->extentWidth = width;
result->extentHeight = height;
result->scaleX = State(target)->scaleX;
result->scaleY = State(target)->scaleY;
return Handle(result);
}
}