summaryrefslogtreecommitdiff
path: root/lib/osx_file_icon.mm
blob: 11fb053f7e3feca658ac3b6ae904f5fc8ec3122f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// **************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl.html       *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#include "osx_file_icon.h"
#include <zen/osx_throw_exception.h>
#include <zen/scope_guard.h>
#include <zen/basic_math.h>

namespace
{
osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw OsxError; NSException?
{
    /*
	wxBitmap(NSImage*) is not good enough: it calls "[NSBitmapImageRep imageRepWithData:[img TIFFRepresentation]]"
		=> inefficient: TIFFRepresentation converts all contained images of an NSImage
		=> lacking: imageRepWithData extracts the first contained image only!
		=> wxBitmap(NSImage*) is wxCocoa only, deprecated!
		=> wxWidgets generally is not thread-safe so care must be taken to use wxBitmap from main thread only! (e.g. race-condition on non-atomic ref-count!!!)
		
	-> we need only a single bitmap at a specific size => extract raw bytes for use with wxImage in a thread-safe way!
	*/

    //we choose the Core Graphics solution; for the equivalent App-Kit way see: http://www.cocoabuilder.com/archive/cocoa/193131-is-lockfocus-main-thread-specific.html#193191

    ZEN_OSX_ASSERT(requestedSize > 0);
    NSRect rectProposed = NSMakeRect(0, 0, requestedSize, requestedSize); //this is merely a hint!

	CGImageRef imgRef = [nsImg CGImageForProposedRect:&rectProposed context:nil hints:nil];
    ZEN_OSX_ASSERT(imgRef != NULL); //can this fail? not documented; ownership?? not documented!

    const size_t width  = ::CGImageGetWidth (imgRef);
    const size_t height = ::CGImageGetHeight(imgRef);

    ZEN_OSX_ASSERT(width > 0 && height > 0 && requestedSize > 0);

    int trgWidth  = width;
    int trgHeight = height;

    const int maxExtent = std::max(width, height); //don't stretch small images, but shrink large ones instead!
    if (requestedSize < maxExtent)
    {
        trgWidth  = width  * requestedSize / maxExtent;
        trgHeight = height * requestedSize / maxExtent;
    }

    CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB();
    ZEN_OSX_ASSERT(colorSpace != NULL); //may fail
    ZEN_ON_SCOPE_EXIT(::CGColorSpaceRelease(colorSpace));

    std::vector<unsigned char> buf(trgWidth* trgHeight * 4); //32-bit ARGB, little endian byte order -> already initialized with 0 = fully transparent

    //supported color spaces: https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB
    CGContextRef ctxRef = ::CGBitmapContextCreate(&buf[0],      //void *data,
                                                trgWidth,     //size_t width,
                                                trgHeight,    //size_t height,
                                                8,            //size_t bitsPerComponent,
                                                4 * trgWidth, //size_t bytesPerRow,
                                                colorSpace,   //CGColorSpaceRef colorspace,
                                                kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); //CGBitmapInfo bitmapInfo
    ZEN_OSX_ASSERT(ctxRef != NULL);
    ZEN_ON_SCOPE_EXIT(::CGContextRelease(ctxRef));

    ::CGContextDrawImage(ctxRef, CGRectMake(0, 0, trgWidth, trgHeight), imgRef); //can this fail?  not documented

    //CGContextFlush(ctxRef); //"If you pass [...] a bitmap context, this function does nothing."

    osx::ImageData imgOut(trgWidth, trgHeight);

    auto it = buf.begin();
    auto itOutRgb = imgOut.rgb.begin();
    auto itOutAlpha = imgOut.alpha.begin();
    for (int i = 0; i < trgWidth * trgHeight; ++i)
    {
        const unsigned char b = *it++;
        const unsigned char g = *it++;
        const unsigned char r = *it++;
        const unsigned char a = *it++;

        //unsigned arithmetics caveat!
        auto demultiplex = [&](unsigned char c) { return static_cast<unsigned char>(numeric::confineCpy(a == 0 ? 0 : (c * 255 + a - 1) / a, 0, 255)); }; //=ceil(c * 255 / a)

        *itOutRgb++ = demultiplex(r);
        *itOutRgb++ = demultiplex(g);
        *itOutRgb++ = demultiplex(b);
        *itOutAlpha++ = a;
    }

    return imgOut;
}
}


osx::ImageData osx::getThumbnail(const char* filename, int requestedSize) //throw OsxError
{
    @try
    {
        @autoreleasepool
        {
			NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
            ZEN_OSX_ASSERT(nsFile != nil); //throw OsxError; can this fail?  not documented
            //stringWithCString returns string which is already set to autorelease!

			NSImage* nsImg = [[[NSImage alloc] initWithContentsOfFile:nsFile] autorelease];
            ZEN_OSX_ASSERT(nsImg != nil); //may fail

            return extractBytes(nsImg, requestedSize); //throw OsxError
        }
    }
    @catch (NSException* e)
    {
        throwOsxError(e); //throw OsxError
    }
}


osx::ImageData osx::getFileIcon(const char* filename, int requestedSize) //throw OsxError
{
    @try
    {
        @autoreleasepool
        {
			NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
            ZEN_OSX_ASSERT(nsFile != nil); //throw OsxError; can this fail?  not documented
            //stringWithCString returns string which is already set to autorelease!

			NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFile:nsFile];
            ZEN_OSX_ASSERT(nsImg != nil); //can this fail?  not documented

            return extractBytes(nsImg, requestedSize); //throw OsxError
        }
    }
    @catch (NSException* e)
    {
        throwOsxError(e); //throw OsxError
    }
}


osx::ImageData osx::getDefaultFileIcon(int requestedSize) //throw OsxError
{
    @try
    {
        @autoreleasepool
        {
			NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
            //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:@"dat"];
            ZEN_OSX_ASSERT(nsImg != nil); //can this fail?  not documented

            return extractBytes(nsImg, requestedSize); //throw OsxError
        }
    }
    @catch (NSException* e)
    {
        throwOsxError(e); //throw OsxError
    }
}


osx::ImageData osx::getDefaultFolderIcon(int requestedSize) //throw OsxError
{
    @try
    {
        @autoreleasepool
        {
			NSImage* nsImg = [NSImage imageNamed:NSImageNameFolder];
            //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)];
            ZEN_OSX_ASSERT(nsImg != nil); //may fail

            return extractBytes(nsImg, requestedSize); //throw OsxError
        }
    }
    @catch (NSException* e)
    {
        throwOsxError(e); //throw OsxError
    }
}
bgstack15