mirror of
https://github.com/arsenetar/dupeguru.git
synced 2024-11-12 18:49:03 +00:00
Andrew Senetar
46d1afb566
- Remove trailing whitespace - Correct single newline at end of files (skip for json) - Update to formatting in a few places due to black
306 lines
8.6 KiB
Objective-C
306 lines
8.6 KiB
Objective-C
/* Created By: Virgil Dupras
|
|
* Created On: 2010-02-04
|
|
* Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
*
|
|
* This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
* which should be included with this package. The terms are also available at
|
|
* http://www.gnu.org/licenses/gpl-3.0.html
|
|
**/
|
|
|
|
#include "common.h"
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <CoreGraphics/CoreGraphics.h>
|
|
#import <ImageIO/ImageIO.h>
|
|
|
|
#define RADIANS( degrees ) ( degrees * M_PI / 180 )
|
|
|
|
static CFStringRef
|
|
pystring2cfstring(PyObject *pystring)
|
|
{
|
|
PyObject *encoded;
|
|
UInt8 *s;
|
|
CFIndex size;
|
|
CFStringRef result;
|
|
|
|
if (PyUnicode_Check(pystring)) {
|
|
encoded = PyUnicode_AsUTF8String(pystring);
|
|
if (encoded == NULL) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
encoded = pystring;
|
|
Py_INCREF(encoded);
|
|
}
|
|
|
|
s = (UInt8*)PyBytes_AS_STRING(encoded);
|
|
size = PyBytes_GET_SIZE(encoded);
|
|
result = CFStringCreateWithBytes(NULL, s, size, kCFStringEncodingUTF8, FALSE);
|
|
Py_DECREF(encoded);
|
|
return result;
|
|
}
|
|
|
|
static PyObject* block_osx_get_image_size(PyObject *self, PyObject *args)
|
|
{
|
|
PyObject *path;
|
|
CFStringRef image_path;
|
|
CFURLRef image_url;
|
|
CGImageSourceRef source;
|
|
CGImageRef image;
|
|
long width, height;
|
|
PyObject *pwidth, *pheight;
|
|
PyObject *result;
|
|
|
|
width = 0;
|
|
height = 0;
|
|
if (!PyArg_ParseTuple(args, "O", &path)) {
|
|
return NULL;
|
|
}
|
|
|
|
image_path = pystring2cfstring(path);
|
|
if (image_path == NULL) {
|
|
return PyErr_NoMemory();
|
|
}
|
|
image_url = CFURLCreateWithFileSystemPath(NULL, image_path, kCFURLPOSIXPathStyle, FALSE);
|
|
CFRelease(image_path);
|
|
|
|
source = CGImageSourceCreateWithURL(image_url, NULL);
|
|
CFRelease(image_url);
|
|
if (source != NULL) {
|
|
image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
|
if (image != NULL) {
|
|
width = CGImageGetWidth(image);
|
|
height = CGImageGetHeight(image);
|
|
CGImageRelease(image);
|
|
}
|
|
CFRelease(source);
|
|
}
|
|
|
|
pwidth = PyLong_FromLong(width);
|
|
if (pwidth == NULL) {
|
|
return NULL;
|
|
}
|
|
pheight = PyLong_FromLong(height);
|
|
if (pheight == NULL) {
|
|
return NULL;
|
|
}
|
|
result = PyTuple_Pack(2, pwidth, pheight);
|
|
Py_DECREF(pwidth);
|
|
Py_DECREF(pheight);
|
|
return result;
|
|
}
|
|
|
|
static CGContextRef
|
|
MyCreateBitmapContext(int width, int height)
|
|
{
|
|
CGContextRef context = NULL;
|
|
CGColorSpaceRef colorSpace;
|
|
void *bitmapData;
|
|
int bitmapByteCount;
|
|
int bitmapBytesPerRow;
|
|
|
|
bitmapBytesPerRow = (width * 4);
|
|
bitmapByteCount = (bitmapBytesPerRow * height);
|
|
|
|
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
|
|
|
// calloc() must be used to allocate bitmapData here because the buffer has to be zeroed.
|
|
// If it's not zeroes, when images with transparency are drawn in the context, this buffer
|
|
// will stay with undefined pixels, which means that two pictures with the same pixels will
|
|
// most likely have different blocks (which is not supposed to happen).
|
|
bitmapData = calloc(bitmapByteCount, 1);
|
|
if (bitmapData == NULL) {
|
|
fprintf(stderr, "Memory not allocated!");
|
|
return NULL;
|
|
}
|
|
|
|
context = CGBitmapContextCreate(bitmapData, width, height, 8, bitmapBytesPerRow, colorSpace,
|
|
(CGBitmapInfo)kCGImageAlphaNoneSkipLast);
|
|
if (context== NULL) {
|
|
free(bitmapData);
|
|
fprintf(stderr, "Context not created!");
|
|
return NULL;
|
|
}
|
|
CGColorSpaceRelease(colorSpace);
|
|
return context;
|
|
}
|
|
|
|
static PyObject* getblock(unsigned char *imageData, int imageWidth, int imageHeight, int boxX, int boxY, int boxW, int boxH)
|
|
{
|
|
int i,j, totalR, totalG, totalB;
|
|
|
|
totalR = totalG = totalB = 0;
|
|
for(i=boxY; i<boxY+boxH; i++) {
|
|
for(j=boxX; j<boxX+boxW; j++) {
|
|
int offset = (i * imageWidth * 4) + (j * 4);
|
|
totalR += *(imageData + offset);
|
|
totalG += *(imageData + offset + 1);
|
|
totalB += *(imageData + offset + 2);
|
|
}
|
|
}
|
|
int pixelCount = boxH * boxW;
|
|
totalR /= pixelCount;
|
|
totalG /= pixelCount;
|
|
totalB /= pixelCount;
|
|
|
|
return inttuple(3, totalR, totalG, totalB);
|
|
}
|
|
|
|
static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
|
|
{
|
|
PyObject *path, *result;
|
|
CFStringRef image_path;
|
|
CFURLRef image_url;
|
|
CGImageSourceRef source;
|
|
CGImageRef image;
|
|
size_t width, height, image_width, image_height;
|
|
int block_count, block_width, block_height, orientation, i;
|
|
|
|
if (!PyArg_ParseTuple(args, "Oii", &path, &block_count, &orientation)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (PySequence_Length(path) == 0) {
|
|
PyErr_SetString(PyExc_ValueError, "empty path");
|
|
return NULL;
|
|
}
|
|
|
|
if ((orientation > 8) || (orientation < 0)) {
|
|
orientation = 0; // simplifies checks later since we can only have values in 0-8
|
|
}
|
|
|
|
image_path = pystring2cfstring(path);
|
|
if (image_path == NULL) {
|
|
return PyErr_NoMemory();
|
|
}
|
|
image_url = CFURLCreateWithFileSystemPath(NULL, image_path, kCFURLPOSIXPathStyle, FALSE);
|
|
CFRelease(image_path);
|
|
|
|
source = CGImageSourceCreateWithURL(image_url, NULL);
|
|
CFRelease(image_url);
|
|
if (source == NULL) {
|
|
return PyErr_NoMemory();
|
|
}
|
|
|
|
image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
|
if (image == NULL) {
|
|
CFRelease(source);
|
|
return PyErr_NoMemory();
|
|
}
|
|
|
|
|
|
width = image_width = CGImageGetWidth(image);
|
|
height = image_height = CGImageGetHeight(image);
|
|
if (orientation >= 5) {
|
|
// orientations 5-8 rotate the photo sideways, so we have to swap width and height
|
|
width = image_height;
|
|
height = image_width;
|
|
}
|
|
|
|
CGContextRef context = MyCreateBitmapContext(width, height);
|
|
|
|
if (orientation == 2) {
|
|
// Flip X
|
|
CGContextTranslateCTM(context, width, 0);
|
|
CGContextScaleCTM(context, -1, 1);
|
|
}
|
|
else if (orientation == 3) {
|
|
// Rot 180
|
|
CGContextTranslateCTM(context, width, height);
|
|
CGContextRotateCTM(context, RADIANS(180));
|
|
}
|
|
else if (orientation == 4) {
|
|
// Flip Y
|
|
CGContextTranslateCTM(context, 0, height);
|
|
CGContextScaleCTM(context, 1, -1);
|
|
}
|
|
else if (orientation == 5) {
|
|
// Flip X + Rot CW 90
|
|
CGContextTranslateCTM(context, width, 0);
|
|
CGContextScaleCTM(context, -1, 1);
|
|
CGContextTranslateCTM(context, 0, height);
|
|
CGContextRotateCTM(context, RADIANS(-90));
|
|
}
|
|
else if (orientation == 6) {
|
|
// Rot CW 90
|
|
CGContextTranslateCTM(context, 0, height);
|
|
CGContextRotateCTM(context, RADIANS(-90));
|
|
}
|
|
else if (orientation == 7) {
|
|
// Rot CCW 90 + Flip X
|
|
CGContextTranslateCTM(context, width, 0);
|
|
CGContextScaleCTM(context, -1, 1);
|
|
CGContextTranslateCTM(context, width, 0);
|
|
CGContextRotateCTM(context, RADIANS(90));
|
|
}
|
|
else if (orientation == 8) {
|
|
// Rot CCW 90
|
|
CGContextTranslateCTM(context, width, 0);
|
|
CGContextRotateCTM(context, RADIANS(90));
|
|
}
|
|
CGRect myBoundingBox = CGRectMake(0, 0, image_width, image_height);
|
|
CGContextDrawImage(context, myBoundingBox, image);
|
|
unsigned char *bitmapData = CGBitmapContextGetData(context);
|
|
CGContextRelease(context);
|
|
|
|
CGImageRelease(image);
|
|
CFRelease(source);
|
|
if (bitmapData == NULL) {
|
|
return PyErr_NoMemory();
|
|
}
|
|
|
|
block_width = max(width/block_count, 1);
|
|
block_height = max(height/block_count, 1);
|
|
|
|
result = PyList_New(block_count * block_count);
|
|
if (result == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for(i=0; i<block_count; i++) {
|
|
int j, top;
|
|
top = min(i*block_height, height-block_height);
|
|
for(j=0; j<block_count; j++) {
|
|
int left;
|
|
left = min(j*block_width, width-block_width);
|
|
PyObject *block = getblock(bitmapData, width, height, left, top, block_width, block_height);
|
|
if (block == NULL) {
|
|
Py_DECREF(result);
|
|
return NULL;
|
|
}
|
|
PyList_SET_ITEM(result, i*block_count+j, block);
|
|
}
|
|
}
|
|
|
|
free(bitmapData);
|
|
return result;
|
|
}
|
|
|
|
static PyMethodDef BlockOsxMethods[] = {
|
|
{"get_image_size", block_osx_get_image_size, METH_VARARGS, ""},
|
|
{"getblocks", block_osx_getblocks, METH_VARARGS, ""},
|
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
|
};
|
|
|
|
static struct PyModuleDef BlockOsxDef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"_block_osx",
|
|
NULL,
|
|
-1,
|
|
BlockOsxMethods,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
PyObject *
|
|
PyInit__block_osx(void)
|
|
{
|
|
PyObject *m = PyModule_Create(&BlockOsxDef);
|
|
if (m == NULL) {
|
|
return NULL;
|
|
}
|
|
return m;
|
|
}
|