1/*
2 * Copyright 2010-2014, Haiku Inc. All rights reserved.
3 * Copyright 2010 Wim van der Meer <WPJvanderMeer@gmail.com>
4 * Copyright Karsten Heimrich, host.haiku@gmx.de. All rights reserved.
5 * Distributed under the terms of the MIT License.
6 *
7 * Authors:
8 *		Axel D��rfler
9 *		Karsten Heimrich
10 *		Fredrik Mod��en
11 *		Christophe Huriaux
12 *		Wim van der Meer
13 */
14
15
16#include "Utility.h"
17
18#include <Bitmap.h>
19#include <BitmapStream.h>
20#include <Catalog.h>
21#include <Clipboard.h>
22#include <Entry.h>
23#include <File.h>
24#include <FindDirectory.h>
25#include <Locale.h>
26#include <Looper.h>
27#include <MimeType.h>
28#include <NodeInfo.h>
29#include <Path.h>
30#include <Region.h>
31#include <Screen.h>
32#include <String.h>
33#include <Translator.h>
34#include <View.h>
35
36#include <AutoDeleter.h>
37
38
39#undef B_TRANSLATION_CONTEXT
40#define B_TRANSLATION_CONTEXT "Screenshot"
41
42
43const char* Utility::sDefaultFileNameBase = B_TRANSLATE_MARK_COMMENT(
44	"screenshot", "Base filename of screenshot files");
45
46
47Utility::Utility()
48	:
49	wholeScreen(NULL),
50	cursorBitmap(NULL),
51	cursorAreaBitmap(NULL)
52{
53}
54
55
56Utility::~Utility()
57{
58	delete wholeScreen;
59	delete cursorBitmap;
60	delete cursorAreaBitmap;
61}
62
63
64void
65Utility::CopyToClipboard(const BBitmap& screenshot) const
66{
67	if (be_clipboard->Lock()) {
68		be_clipboard->Clear();
69		BMessage* clipboard = be_clipboard->Data();
70		if (clipboard != NULL) {
71			BMessage* bitmap = new BMessage();
72			screenshot.Archive(bitmap);
73			clipboard->AddMessage("image/bitmap", bitmap);
74			be_clipboard->Commit();
75		}
76		be_clipboard->Unlock();
77	}
78}
79
80
81/*!	Save the screenshot to the file with the specified filename and type.
82	Note that any existing file with the same filename will be overwritten
83	without warning.
84*/
85status_t
86Utility::Save(BBitmap* screenshot, const char* fileName, uint32 imageType)
87	const
88{
89	BString fileNameString(fileName);
90
91	// Generate a default filename when none is given
92	if (fileNameString.Compare("") == 0) {
93		BPath homePath;
94		if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK)
95			return B_ERROR;
96
97		BEntry entry;
98		int32 index = 1;
99		BString extension = FileNameExtension(imageType);
100		do {
101			fileNameString.SetTo(homePath.Path());
102			fileNameString << "/" << B_TRANSLATE_NOCOLLECT(sDefaultFileNameBase)
103				<< index++ << extension;
104			entry.SetTo(fileNameString);
105		} while (entry.Exists());
106	}
107
108	// Create the file
109	BFile file(fileNameString, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
110	if (file.InitCheck() != B_OK)
111		return B_ERROR;
112
113	// Write the screenshot bitmap to the file
114	BBitmapStream stream(screenshot);
115	BTranslatorRoster* roster = BTranslatorRoster::Default();
116	status_t status = roster->Translate(&stream, NULL, NULL, &file, imageType,
117		B_TRANSLATOR_BITMAP);
118
119	BBitmap* bitmap;
120	stream.DetachBitmap(&bitmap);
121		// The stream takes over ownership of the bitmap
122
123	if (status != B_OK)
124		return status;
125
126	// Set the file MIME attribute (don't mind too much if this fails)
127	BNodeInfo nodeInfo(&file);
128	if (nodeInfo.InitCheck() == B_OK)
129		nodeInfo.SetType(_MimeType(imageType));
130
131	return B_OK;
132}
133
134
135BBitmap*
136Utility::MakeScreenshot(bool includeMouse, bool activeWindow,
137	bool includeBorder) const
138{
139	if (wholeScreen == NULL)
140		return NULL;
141
142	if (includeMouse && cursorBitmap != NULL) {
143		// Import the cursor bitmap into wholeScreen
144		wholeScreen->ImportBits(cursorBitmap,
145			B_ORIGIN, cursorPosition, cursorBitmap->Bounds().Size());
146	} else if (cursorAreaBitmap != NULL) {
147		// Import the cursor area bitmap into wholeScreen
148		wholeScreen->ImportBits(cursorAreaBitmap,
149			B_ORIGIN, cursorPosition, cursorAreaBitmap->Bounds().Size());
150	}
151
152	BBitmap* screenshot = NULL;
153
154	if (activeWindow && activeWindowFrame.IsValid()) {
155		BRect frame(activeWindowFrame);
156		if (includeBorder) {
157			frame.InsetBy(-borderSize, -borderSize);
158			frame.top -= tabFrame.bottom - tabFrame.top;
159		}
160
161		screenshot = new BBitmap(frame.OffsetToCopy(B_ORIGIN),
162			includeBorder ? B_RGBA32 : B_RGB32, true);
163
164		if (screenshot->ImportBits(wholeScreen, frame.LeftTop(),
165				B_ORIGIN, frame.Size()) != B_OK) {
166			delete screenshot;
167			return NULL;
168		}
169
170		if (includeBorder)
171			_MakeTabSpaceTransparent(screenshot, frame);
172	} else
173		screenshot = new BBitmap(wholeScreen);
174
175	return screenshot;
176}
177
178
179BString
180Utility::FileNameExtension(uint32 imageType) const
181{
182	BMimeType mimeType(_MimeType(imageType));
183
184	BMessage message;
185	if (mimeType.GetFileExtensions(&message) == B_OK) {
186		BString extension;
187		if (message.FindString("extensions", 0, &extension) == B_OK) {
188			extension.Prepend(".");
189			return extension;
190		}
191	}
192
193	return "";
194}
195
196
197status_t
198Utility::FindTranslator(uint32 imageType, translator_id& id,
199	BString* _mimeType) const
200{
201	translator_id* translators = NULL;
202	int32 numTranslators = 0;
203
204	BTranslatorRoster* roster = BTranslatorRoster::Default();
205	status_t status = roster->GetAllTranslators(&translators, &numTranslators);
206	if (status != B_OK)
207		return status;
208
209	ArrayDeleter<translator_id> deleter(translators);
210
211	for (int32 x = 0; x < numTranslators; x++) {
212		const translation_format* formats = NULL;
213		int32 numFormats;
214
215		if (roster->GetOutputFormats(translators[x], &formats, &numFormats)
216				== B_OK) {
217			for (int32 i = 0; i < numFormats; ++i) {
218				if (formats[i].type == imageType) {
219					id = translators[x];
220					if (_mimeType != NULL)
221						*_mimeType = formats[i].MIME;
222					return B_OK;
223				}
224			}
225		}
226	}
227
228	return B_ERROR;
229}
230
231
232BString
233Utility::_MimeType(uint32 imageType) const
234{
235	translator_id id;
236	BString type;
237	FindTranslator(imageType, id, &type);
238	return type;
239}
240
241
242/*!	Makes the space around the tab transparent, and also makes sure that the
243	contents of the window aren't, as the screen does not have an alpha channel.
244*/
245void
246Utility::_MakeTabSpaceTransparent(BBitmap* screenshot, BRect frame) const
247{
248	if (!frame.IsValid() || screenshot->ColorSpace() != B_RGBA32)
249		return;
250
251	// Set the transparency to opaque on the complete bitmap
252	uint8* pixel = (uint8*)screenshot->Bits();
253	uint32 count = screenshot->BitsLength();
254	for (uint32 i = 0; i < count; i += 4) {
255		pixel[i + 3] = 255;
256	}
257
258	// Then make the space around the tab transparent
259	if (!frame.Contains(tabFrame))
260		return;
261
262	float tabHeight = tabFrame.bottom - tabFrame.top;
263
264	BRegion tabSpace(frame);
265	frame.OffsetBy(0, tabHeight);
266	tabSpace.Exclude(frame);
267	tabSpace.Exclude(tabFrame);
268	frame.OffsetBy(0, -tabHeight);
269	tabSpace.OffsetBy(-frame.left, -frame.top);
270	BScreen screen;
271	BRect screenFrame = screen.Frame();
272	tabSpace.OffsetBy(-screenFrame.left, -screenFrame.top);
273
274	BView view(screenshot->Bounds(), "bitmap", B_FOLLOW_ALL_SIDES, 0);
275	screenshot->AddChild(&view);
276	if (view.Looper() && view.Looper()->Lock()) {
277		view.SetDrawingMode(B_OP_COPY);
278		view.SetHighColor(B_TRANSPARENT_32_BIT);
279
280		for (int i = 0; i < tabSpace.CountRects(); i++)
281			view.FillRect(tabSpace.RectAt(i));
282
283		view.Sync();
284		view.Looper()->Unlock();
285	}
286	screenshot->RemoveChild(&view);
287}
288