1 /***************************************************************************
2                 ComicBook.cpp - ComicBook class and its children
3                              -------------------
4     copyright            : (C) 2003-2006 by James Athey
5     email                : jathey@comcast.net
6  ***************************************************************************/
7 
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  *   In addition, as a special exception, the author gives permission to   *
16  *   link the code of his release of Comical with Rarlabs' "unrar"         *
17  *   library (or with modified versions of it that use the same license    *
18  *   as the "unrar" library), and distribute the linked executables. You   *
19  *   must obey the GNU General Public License in all respects for all of   *
20  *   the code used other than "unrar". If you modify this file, you may    *
21  *   extend this exception to your version of the file, but you are not    *
22  *   obligated to do so. If you do not wish to do so, delete this          *
23  *   exception statement from your version.                                *
24  *                                                                         *
25  ***************************************************************************/
26 
27 #include "ComicBook.h"
28 #include "Exceptions.h"
29 #include <cstring>
30 #include <wx/datetime.h>
31 #include <wx/mstream.h>
32 #include <wx/utils.h>
33 #include <wx/textdlg.h>
34 
35 DEFINE_EVENT_TYPE(EVT_PAGE_SCALED)
DEFINE_EVENT_TYPE(EVT_PAGE_THUMBNAILED)36 DEFINE_EVENT_TYPE(EVT_PAGE_THUMBNAILED)
37 DEFINE_EVENT_TYPE(EVT_CURRENT_PAGE_CHANGED)
38 DEFINE_EVENT_TYPE(EVT_PAGE_ERROR)
39 
40 ComicBook::ComicBook(wxString _filename, wxUint32 _cacheLen, COMICAL_ZOOM _zoom, long _zoomLevel, bool _fitOnlyOversize, COMICAL_MODE _mode, FREE_IMAGE_FILTER _filter, COMICAL_DIRECTION _direction, wxInt32 _scrollbarThickness) : wxThread(wxTHREAD_JOINABLE), filename(_filename), cacheLen(_cacheLen), zoom(_zoom), zoomLevel(_zoomLevel), fitOnlyOversize(_fitOnlyOversize), mode(_mode), filter(_filter), direction(_direction), scrollbarThickness(_scrollbarThickness)
41 {
42 	pageCount = 0;
43 	currentPage = 0;
44 	evtHandler = new wxEvtHandler();
45 	// Each of the long values is followed by the letter L not the number one
46 	Filenames = new wxArrayString();
47 	originals = NULL;
48 	resamples = NULL;
49 	thumbnails = NULL;
50 	Orientations = NULL;
51 	originalLockers = NULL;
52 	resampleLockers = NULL;
53 	thumbnailLockers = NULL;
54 	password = NULL;
55 }
56 
~ComicBook()57 ComicBook::~ComicBook()
58 {
59 	for(wxUint32 i = 0; i < pageCount; i++) {
60 		if (originals[i].Ok())
61 			originals[i].Destroy();
62 		if (resamples[i].Ok())
63 			resamples[i].Destroy();
64 		if (thumbnails[i].Ok())
65 			thumbnails[i].Destroy();
66 	}
67 	if (originals)
68 		delete[] originals;
69 	if (resamples)
70 		delete[] resamples;
71 	if (thumbnails)
72 		delete[] thumbnails;
73 	if (Orientations)
74 		delete[] Orientations;
75 	if (originalLockers)
76 		delete[] originalLockers;
77 	if (resampleLockers)
78 		delete[] resampleLockers;
79 	if (thumbnailLockers)
80 		delete[] thumbnailLockers;
81 	if (Filenames)
82 		delete Filenames;
83 	if (password)
84 		delete[] password;
85 	if (evtHandler)
86 		delete evtHandler;
87 }
88 
RotatePage(wxUint32 pagenumber,COMICAL_ROTATE direction)89 void ComicBook::RotatePage(wxUint32 pagenumber, COMICAL_ROTATE direction)
90 {
91 	if (Orientations[pagenumber] != direction) {
92 		wxMutexLocker rlock(resampleLockers[pagenumber]);
93 		wxMutexLocker tlock(thumbnailLockers[pagenumber]);
94 		Orientations[pagenumber] = direction;
95 		resamples[pagenumber].Destroy();
96 		thumbnails[pagenumber].Destroy();
97 	}
98 }
99 
100 /* returns TRUE if newMode is different, FALSE if the same and no changes were made */
SetMode(COMICAL_MODE newMode)101 bool ComicBook::SetMode(COMICAL_MODE newMode)
102 {
103 	if(mode != newMode) {
104 		wxUint32 i;
105 		for (i = 0; i < pageCount; i++) {
106 			resampleLockers[i].Lock();
107 			if (resamples[i].Ok())
108 				resamples[i].Destroy();
109 		}
110 		mode = newMode;
111 		for (i = 0; i < pageCount; i++)
112 			resampleLockers[i].Unlock();
113 		return TRUE;
114 	} else
115 		return FALSE;
116 }
117 
118 /* returns TRUE if newFilter is different, FALSE if the same and no changes were made */
SetFilter(FREE_IMAGE_FILTER newFilter)119 bool ComicBook::SetFilter(FREE_IMAGE_FILTER newFilter)
120 {
121 	if(filter != newFilter) {
122 		wxUint32 i;
123 		for (i = 0; i < pageCount; i++) {
124 			resampleLockers[i].Lock();
125 			if (resamples[i].Ok())
126 				resamples[i].Destroy();
127 		}
128 		filter = newFilter;
129 		for (i = 0; i < pageCount; i++)
130 			resampleLockers[i].Unlock();
131 		return TRUE;
132 	} else
133 		return FALSE;
134 }
135 
136 /* Makes pages zoom to newZoom level, and will zoom pages that are smaller than the canvas only if newFitOnlyIfOversize is FALSE.
137  * returns TRUE if parameters were different, FALSE if parameters were the same and no changes were made */
SetZoom(COMICAL_ZOOM newZoom)138 bool ComicBook::SetZoom(COMICAL_ZOOM newZoom)
139 {
140 	if(zoom != newZoom) {
141 		wxUint32 i;
142 		for (i = 0; i < pageCount; i++) {
143 			resampleLockers[i].Lock();
144 			if (resamples[i].Ok())
145 				resamples[i].Destroy();
146 		}
147 
148 		zoom = newZoom;
149 		for (i = 0; i < pageCount; i++)
150 			resampleLockers[i].Unlock();
151 		return TRUE;
152 	} else
153 		return FALSE;
154 }
155 
156 /* Makes pages zoom to newZoomLevel%.
157  * returns TRUE if parameters were different, FALSE if parameters were the same and no changes were made */
SetZoomLevel(long newZoomLevel)158 bool ComicBook::SetZoomLevel(long newZoomLevel)
159 {
160 	if(zoomLevel != newZoomLevel) {
161 		wxUint32 i;
162 		if (zoom == ZOOM_CUSTOM) {
163 			for (i = 0; i < pageCount; i++) {
164 				resampleLockers[i].Lock();
165 				if (resamples[i].Ok())
166 					resamples[i].Destroy();
167 			}
168 		}
169 		zoomLevel = newZoomLevel;
170 		if (zoom == ZOOM_CUSTOM) {
171 			for (i = 0; i < pageCount; i++)
172 				resampleLockers[i].Unlock();
173 		}
174 		return TRUE;
175 	} else
176 		return FALSE;
177 }
178 
SetFitOnlyOversize(bool newFitOnlyOversize)179 bool ComicBook::SetFitOnlyOversize(bool newFitOnlyOversize)
180 {
181 	if (fitOnlyOversize != newFitOnlyOversize) {
182 		wxUint32 i;
183 		for (i = 0; i < pageCount; i++) {
184 			resampleLockers[i].Lock();
185 			if (resamples[i].Ok())
186 				resamples[i].Destroy();
187 		}
188 
189 		fitOnlyOversize = newFitOnlyOversize;
190 		for (i = 0; i < pageCount; i++)
191 			resampleLockers[i].Unlock();
192 		return TRUE;
193 	} else
194 		return FALSE;
195 }
196 
197 /* returns TRUE if direction is different, FALSE if same and no changes were made */
SetDirection(COMICAL_DIRECTION newDirection)198 bool ComicBook::SetDirection(COMICAL_DIRECTION newDirection)
199 {
200 	if(direction != newDirection) {
201 		direction = newDirection;
202 		return TRUE;
203 	} else
204 		return FALSE;
205 }
206 
207 /* returns TRUE if parameters were different, FALSE if parameters were the same and no changes were made */
SetCanvasSize(wxSize canvasSize)208 bool ComicBook::SetCanvasSize(wxSize canvasSize)
209 {
210 	if(canvasWidth != canvasSize.x || canvasHeight != canvasSize.y) {
211 		wxUint32 i;
212 		for (i = 0; i < pageCount; i++) {
213 			resampleLockers[i].Lock();
214 			if (resamples[i].Ok())
215 				resamples[i].Destroy();
216 		}
217 		canvasWidth = canvasSize.x;
218 		canvasHeight = canvasSize.y;
219 		for (i = 0; i < pageCount; i++)
220 			resampleLockers[i].Unlock();
221 		return TRUE;
222 	} else
223 		return FALSE;
224 }
225 
SetScrollbarThickness(wxInt32 newScrollbarThickness)226 bool ComicBook::SetScrollbarThickness(wxInt32 newScrollbarThickness)
227 {
228 	if(scrollbarThickness != newScrollbarThickness) {
229 		wxUint32 i;
230 		for (i = 0; i < pageCount; i++) {
231 			resampleLockers[i].Lock();
232 			if (resamples[i].Ok())
233 				resamples[i].Destroy();
234 		}
235 		scrollbarThickness = newScrollbarThickness;
236 		for (i = 0; i < pageCount; i++)
237 			resampleLockers[i].Unlock();
238 		return TRUE;
239 	} else
240 		return FALSE;
241 }
242 
GetPage(wxUint32 pagenumber)243 wxBitmap * ComicBook::GetPage(wxUint32 pagenumber)
244 {
245 	if (pagenumber > pageCount)
246 		throw new PageOutOfRangeException(pagenumber, pageCount);
247 	wxMutexLocker lock(resampleLockers[pagenumber]);
248 	if (!resamples[pagenumber].Ok())
249 		return NULL;
250 	return new wxBitmap(resamples[pagenumber]);
251 }
252 
GetPageLeftHalf(wxUint32 pagenumber)253 wxBitmap * ComicBook::GetPageLeftHalf(wxUint32 pagenumber)
254 {
255 	if (pagenumber > pageCount)
256 		throw new PageOutOfRangeException(pagenumber, pageCount);
257 	wxMutexLocker lock(resampleLockers[pagenumber]);
258 	if (!resamples[pagenumber].Ok())
259 		return NULL;
260 	wxInt32 rWidth = resamples[pagenumber].GetWidth();
261 	wxInt32 rHeight = resamples[pagenumber].GetHeight();
262 	if (direction == COMICAL_RTL) {
263 		wxInt32 offset = rWidth / 2;
264 		wxInt32 remainder = rWidth % 2;
265 		return new wxBitmap(resamples[pagenumber].GetSubImage(wxRect(offset, 0, offset + remainder, rHeight)));
266 	} else
267 		return new wxBitmap(resamples[pagenumber].GetSubImage(wxRect(0, 0, rWidth / 2, rHeight)));
268 }
269 
GetPageRightHalf(wxUint32 pagenumber)270 wxBitmap * ComicBook::GetPageRightHalf(wxUint32 pagenumber)
271 {
272 	if (pagenumber > pageCount)
273 		throw new PageOutOfRangeException(pagenumber, pageCount);
274 	wxMutexLocker lock(resampleLockers[pagenumber]);
275 	if (!resamples[pagenumber].Ok())
276 		return NULL;
277 	wxInt32 rWidth = resamples[pagenumber].GetWidth();
278 	wxInt32 rHeight = resamples[pagenumber].GetHeight();
279 	if (direction == COMICAL_LTR) {
280 		wxInt32 offset = rWidth / 2;
281 		wxInt32 remainder = rWidth % 2;
282 		return new wxBitmap(resamples[pagenumber].GetSubImage(wxRect(offset, 0, offset + remainder, rHeight)));
283 	} else
284 		return new wxBitmap(resamples[pagenumber].GetSubImage(wxRect(0, 0, rWidth / 2, rHeight)));
285 }
286 
GetThumbnail(wxUint32 pagenumber)287 wxBitmap * ComicBook::GetThumbnail(wxUint32 pagenumber)
288 {
289 	if (pagenumber > pageCount)
290 		throw new PageOutOfRangeException(pagenumber, pageCount);
291 	wxMutexLocker lock(thumbnailLockers[pagenumber]);
292 	if (!thumbnails[pagenumber].Ok())
293 		return NULL;
294 	return new wxBitmap(thumbnails[pagenumber]);
295 }
296 
IsPageLandscape(wxUint32 pagenumber)297 bool ComicBook::IsPageLandscape(wxUint32 pagenumber)
298 {
299 	wxInt32 rWidth, rHeight;
300 	if (pagenumber > pageCount)
301 		throw new PageOutOfRangeException(pagenumber, pageCount);
302 
303 	resampleLockers[pagenumber].Lock();
304 	if (resamples[pagenumber].Ok()) {
305 		rWidth = resamples[pagenumber].GetWidth();
306 		rHeight = resamples[pagenumber].GetHeight();
307 		resampleLockers[pagenumber].Unlock();
308 		goto IsPageLandscapeRatioCalculate;
309 	}
310 	resampleLockers[pagenumber].Unlock();
311 
312 	thumbnailLockers[pagenumber].Lock();
313 	if (thumbnails[pagenumber].Ok()) {
314 		rWidth = thumbnails[pagenumber].GetWidth();
315 		rHeight = thumbnails[pagenumber].GetHeight();
316 		thumbnailLockers[pagenumber].Unlock();
317 		goto IsPageLandscapeRatioCalculate;
318 	}
319 	thumbnailLockers[pagenumber].Unlock();
320 
321 	wxBeginBusyCursor();
322 
323 	// If the page has never been loaded, even in thumbnail form, the display
324 	// will really screw up if the page IS landscape and this function says
325 	// it's not.  Therefore, we wait until it is ready.
326 	while (!originals[pagenumber].Ok()) {
327 		wxMilliSleep(10);
328 	}
329 
330 	originalLockers[pagenumber].Lock();
331 	if (Orientations[pagenumber] == NORTH ||
332 			Orientations[pagenumber] == SOUTH) {
333 		rWidth = originals[pagenumber].GetWidth();
334 		rHeight = originals[pagenumber].GetHeight();
335 	} else {
336 		rHeight = originals[pagenumber].GetWidth();
337 		rWidth = originals[pagenumber].GetHeight();
338 	}
339 	originalLockers[pagenumber].Unlock();
340 
341 	wxEndBusyCursor();
342 
343 	IsPageLandscapeRatioCalculate:
344 	if ((float(rWidth)/float(rHeight)) > 1.0f)
345 		return true;
346 	else
347 		return false;
348 }
349 
IsPageReady(wxUint32 pagenumber)350 bool ComicBook::IsPageReady(wxUint32 pagenumber)
351 {
352 	if (pagenumber > pageCount)
353 		throw new PageOutOfRangeException(pagenumber, pageCount);
354 	return resamples[pagenumber].Ok();
355 }
356 
IsThumbReady(wxUint32 pagenumber)357 bool ComicBook::IsThumbReady(wxUint32 pagenumber)
358 {
359 	if (pagenumber > pageCount)
360 		throw new PageOutOfRangeException(pagenumber, pageCount);
361 	return thumbnails[pagenumber].Ok();
362 }
363 
FitWithoutScrollbars(wxUint32 pagenumber,float * scalingFactor)364 bool ComicBook::FitWithoutScrollbars(wxUint32 pagenumber, float *scalingFactor)
365 {
366 	wxSize size;
367 	wxInt32 usableWidth, usableWidthScrollbar, usableHeightScrollbar, withoutScrollBarHeight, withoutScrollBarWidth;
368 
369 	if (pagenumber > pageCount)
370 		throw new PageOutOfRangeException(pagenumber, pageCount);
371 
372 	if (Orientations[pagenumber] == NORTH || Orientations[pagenumber] == SOUTH)
373 		size = wxSize(originals[pagenumber].GetWidth(), originals[pagenumber].GetHeight());
374 	else
375 		size = wxSize(originals[pagenumber].GetHeight(), originals[pagenumber].GetWidth());
376 
377 	float ratio = float (size.x) / float(size.y);
378 
379 	switch(zoom) {
380 	case ZOOM_WIDTH:
381 		if (ratio >= 1.0f || mode == ONEPAGE) {
382 			usableWidth = canvasWidth;
383 			usableWidthScrollbar = canvasWidth - scrollbarThickness;
384 		} else {
385 			usableWidth = canvasWidth / 2;
386 			usableWidthScrollbar = (canvasWidth - scrollbarThickness) / 2;
387 		}
388 		*scalingFactor = float(usableWidth) / float(size.x);
389 		withoutScrollBarHeight = wxInt32(float(size.y) * *scalingFactor);
390 		if (withoutScrollBarHeight > canvasHeight) {
391 			// it's possible the page will fit without scrollbars at some
392 			// width in between canvasWidth - scrollbarThickness and
393 			// canvasWidth.
394 			// scaling factor if height is maximized
395 			*scalingFactor = float(canvasHeight) / float(size.y);
396 			// which will fit best?
397 			if (wxInt32(*scalingFactor * float(size.x)) > usableWidthScrollbar)
398 				return true;
399 			else
400 				return false;
401 		} else
402 			return true;
403 	case ZOOM_HEIGHT:
404 		if (ratio >= 1.0f || mode == ONEPAGE) {
405 			usableWidth = canvasWidth;
406 		} else {
407 			usableWidth = canvasWidth / 2;
408 		}
409 		*scalingFactor = float(canvasHeight) / float(size.y);
410 		withoutScrollBarWidth = wxInt32(float(size.x) * *scalingFactor);
411 		if (withoutScrollBarWidth > usableWidth) {
412 			// it's possible the page will fit without scrollbars at some
413 			// width in between canvasHeight - scrollbarThickness and
414 			// canvasHeight.
415 			// scaling factor if width is maximized
416 			*scalingFactor = float(usableWidth) / float(size.x);
417 			// which will fit best?
418 			usableHeightScrollbar = canvasHeight - scrollbarThickness;
419 			if ((*scalingFactor * size.y) > usableHeightScrollbar)
420 				return true;
421 			else
422 				return false;
423 		} else
424 			return true;
425 	case ZOOM_FIT:
426 		return true;
427 	default:
428 		// this function should not be called for other zooms
429 		break;
430 	}
431 	return false;
432 }
433 
FitWithoutScrollbars(wxUint32 pagenumber)434 bool ComicBook::FitWithoutScrollbars(wxUint32 pagenumber)
435 {
436 	float throwaway = 0.0f;
437 	return FitWithoutScrollbars(pagenumber, &throwaway);
438 }
439 
IsOversize(wxUint32 pagenumber)440 bool ComicBook::IsOversize(wxUint32 pagenumber)
441 {
442 	wxSize size;
443 	wxInt32 usableWidth, usableHeight;
444 
445 	if (pagenumber > pageCount)
446 		throw new PageOutOfRangeException(pagenumber, pageCount);
447 
448 	if (Orientations[pagenumber] == NORTH || Orientations[pagenumber] == SOUTH)
449 		size = wxSize(originals[pagenumber].GetWidth(), originals[pagenumber].GetHeight());
450 	else
451 		size = wxSize(originals[pagenumber].GetHeight(), originals[pagenumber].GetWidth());
452 
453 	float ratio = float (size.x) / float(size.y);
454 
455 	switch(zoom) {
456 
457 	case ZOOM_FIT:
458 		usableHeight = canvasHeight;
459 		if (ratio > 1.0f || mode == ONEPAGE)
460 			usableWidth = canvasWidth;
461 		else
462 			usableWidth = canvasWidth / 2;
463 		break;
464 
465 	case ZOOM_WIDTH:
466 		usableHeight = canvasHeight;
467 		// For now, to be safe, assume vertical scrollbar is present thanks to neighboring page.
468 		if (ratio > 1.0f || mode == ONEPAGE)
469 			usableWidth = canvasWidth - scrollbarThickness;
470 		else
471 			usableWidth = (canvasWidth - scrollbarThickness) / 2;
472 		break;
473 
474 	case ZOOM_HEIGHT:
475 		usableWidth = canvasWidth;
476 		// For now, to be safe, assume horizontal scrollbar is present thanks to neighboring page.
477 		if (ratio > 1.0f || mode == ONEPAGE)
478 			usableHeight = canvasHeight - scrollbarThickness;
479 		else
480 			usableHeight = (canvasHeight - scrollbarThickness) / 2;
481 		break;
482 
483 	default:
484 		return true;
485 
486 	}
487 
488 	if (size.x > usableWidth || size.y > usableHeight)
489 		return true;
490 	else
491 		return false;
492 }
493 
Entry()494 void * ComicBook::Entry()
495 {
496 	wxUint32 i, target, high, curr;
497 	wxInt32 low;
498 	bool scalingHappened;
499 	wxInputStream *stream;
500 	wxMemoryInputStream *mstream;
501 
502 	while (!TestDestroy()) {
503 		scalingHappened = false;
504 		curr = currentPage; // in case this value changes midloop
505 
506 		// The caching algorithm.  First calculates next highest
507 		// priority page, then checks to see if that page needs
508 		// updating.  If no pages need updating, yield the timeslice.
509 
510 		if (cacheLen >= pageCount) {
511 			low = 0;
512 			high = pageCount - 1;
513 		} else {
514 			/* A moving window of size cacheLen.  2/3 of the pages in the
515 			 * cache are after the Current page, the other 1/3 are before
516 			 * it. */
517 			high = 2 * cacheLen / 3;
518 			low = cacheLen - high;
519 
520 			high = curr + high - 1;
521 			low = curr - low;
522 
523 			/* Keep the window within 0 and pageCount. */
524 			if (high >= pageCount) {
525 				low -= (high - pageCount) + 1;
526 				high = pageCount - 1;
527 			} else if (low < 0) {
528 				high -= low; // low is negative, this is an addition
529 				low = 0;
530 			}
531 		}
532 
533 		for (i = 0; i < cacheLen && i < pageCount; i++) {
534 			if (TestDestroy())
535 				goto thread_end;
536 			target = curr + i;
537 			if (curr > 0) {	// ftw and fth scaling requires previous page to
538 				target--;	// do FitWithoutScrollbars.
539 				if (target > high)
540 					target = curr - (++target - high);
541 			} else if (target > high)
542 				target = curr - (target - high);
543 			try {
544 				originalLockers[target].Lock();
545 				if (originals[target].Ok()) {
546 					originalLockers[target].Unlock();
547 					continue;
548 				}
549 				stream = ExtractStream(target);
550 				if (stream->IsOk() && stream->GetSize() > 0) {
551 					originals[target].LoadFile(*stream);
552 					// Memory Input Streams don't take ownership of the buffer
553 					mstream = dynamic_cast<wxMemoryInputStream*>(stream);
554 					if (mstream)
555 						delete[] (wxUint8 *) mstream->GetInputStreamBuffer()->GetBufferStart();
556 				} else {
557 					SendPageErrorEvent(target, wxString::Format(wxT("Failed to extract page %d."), target));
558 					originals[target] = wxImage(1, 1);
559 				}
560 				if (!originals[target].Ok()) {
561 					SendPageErrorEvent(target, wxString::Format(wxT("Failed to extract page %d."), target));
562 					originals[target] = wxImage(1, 1);
563 				}
564 				wxDELETE(stream);
565 			} catch (ArchiveException *ae) {
566 				SendPageErrorEvent(target, ae->Message);
567 			}
568 
569 			thumbnailLockers[target].Lock();
570 			if (!thumbnails[target].Ok())
571 				ScaleThumbnail(target);
572 			if (!thumbnails[target].Ok()) // let's only see one error if things go wrong
573 				SendPageErrorEvent(target, wxString::Format(wxT("Could not create thumbnail for page %d."), target));
574 
575 			thumbnailLockers[target].Unlock();
576 			originalLockers[target].Unlock();
577 			break; // extraction happened, we should try a rescale now
578 		}
579 
580 		for (i = 0; i < cacheLen && i < pageCount; i++) {
581 			if (TestDestroy())
582 				goto thread_end;
583 			target = curr + i;
584 			if (target > high)
585 				target = curr - (target - high);
586 
587 			resampleLockers[target].Lock();
588 
589 			if (resamples[target].Ok() || !originals[target].Ok() ||
590 					// Only try scaling if the neighbors are extracted.  Otherwise,
591 					// we can't test whether the neighbors will fit without scrollbars.
592 					(target > 0 && !originals[target - 1].Ok()) ||
593 					(target < (pageCount - 1) && !originals[target + 1].Ok())) {
594 				resampleLockers[target].Unlock();
595 				continue;
596 			}
597 
598 			ScaleImage(target);
599 			scalingHappened = true;
600 			resampleLockers[target].Unlock();
601 			break;
602 		}
603 
604 		if (!scalingHappened) {
605 			// if the cache is full, then use this iteration to fetch a
606 			// thumbnail, if needed.
607 			for (wxUint32 j = 0; j < pageCount; j++) {
608 				thumbnailLockers[j].Lock();
609 
610 				if (thumbnails[j].Ok()) {
611 					thumbnailLockers[j].Unlock();
612 					continue;
613 				}
614 				try {
615 					originalLockers[j].Lock();
616 					if (!originals[j].Ok()) {
617 						stream = ExtractStream(j);
618 						if (stream->IsOk() && stream->GetSize() > 0) {
619 							originals[j].LoadFile(*stream);
620 							// Memory Input Streams don't take ownership of the buffer
621 							mstream = dynamic_cast<wxMemoryInputStream*>(stream);
622 							if (mstream)
623 								delete[] (wxUint8 *) mstream->GetInputStreamBuffer()->GetBufferStart();
624 						} else {
625 							SendPageErrorEvent(j, wxString::Format(wxT("Failed to extract page %d."), j));
626 							originals[j] = wxImage(1, 1);
627 						}
628 						if (!originals[j].Ok()) {
629 							SendPageErrorEvent(j, wxString::Format(wxT("Failed to extract page %d."), j));
630 							originals[j] = wxImage(1, 1);
631 						}
632 						wxDELETE(stream);
633 					}
634 				} catch (ArchiveException *ae) {
635 					SendPageErrorEvent(j, wxString::Format(wxT("Failed to extract page %d."), j));
636 				}
637 				ScaleThumbnail(j);
638 				originalLockers[j].Unlock();
639 				thumbnailLockers[j].Unlock();
640 				break;
641 			}
642 		}
643 
644 		if (i < cacheLen && i < pageCount) {
645 			if (cacheLen < pageCount) {
646 				// Delete pages outside of the cache's range.
647 				for (i = 0; wxInt32(i) < low; i++) {
648 
649 					resampleLockers[i].Lock();
650 					if(resamples[i].Ok())
651 						resamples[i].Destroy();
652 					resampleLockers[i].Unlock();
653 
654 					originalLockers[i].Lock();
655 					if(originals[i].Ok())
656 						originals[i].Destroy();
657 					originalLockers[i].Unlock();
658 				}
659 
660 				for (i = pageCount - 1; i > high; i--) {
661 
662 					resampleLockers[i].Lock();
663 					if(resamples[i].Ok())
664 						resamples[i].Destroy();
665 					resampleLockers[i].Unlock();
666 
667 					originalLockers[i].Lock();
668 					if(originals[i].Ok())
669 						originals[i].Destroy();
670 					originalLockers[i].Unlock();
671 				}
672 			}
673 		}
674 
675 		Yield();
676 		Sleep(20);
677 	}
678 	thread_end:
679 	return 0;
680 }
681 
682 /* Resizes an image to fit. */
ScaleImage(wxUint32 pagenumber)683 void ComicBook::ScaleImage(wxUint32 pagenumber)
684 {
685 	wxInt32 xImage, yImage;
686 	float rCanvas, rImage;  // width/height ratios
687 	float scalingFactor;
688 
689 	wxImage &orig = originals[pagenumber];
690 
691 	if (Orientations[pagenumber] == NORTH || Orientations[pagenumber] == SOUTH) {
692 		xImage = orig.GetWidth();
693 		yImage = orig.GetHeight();
694 	} else {// EAST || WEST
695 		yImage = orig.GetWidth();
696 		xImage = orig.GetHeight();
697 	}
698 
699 	switch(zoom) {
700 
701 	case ZOOM_CUSTOM:
702 		scalingFactor = zoomLevel / 100.0f;
703 		break;
704 
705 	case ZOOM_FIT:
706 		if (fitOnlyOversize && !IsOversize(pagenumber))
707 			goto zoom_original;
708 		rImage = float(xImage) / float(yImage);
709 		if (rImage >= 1.0f || mode == ONEPAGE) {
710 			rCanvas = float(canvasWidth) / float(canvasHeight);
711 			if (rCanvas > rImage)
712 				scalingFactor = float(canvasHeight) / float(yImage);
713 			else
714 				scalingFactor = float(canvasWidth) / float(xImage);
715 		} else {
716 			rCanvas = (float(canvasWidth)/2.0f) / float(canvasHeight);
717 			if (rCanvas > rImage)
718 				scalingFactor = float(canvasHeight) / float(yImage);
719 			else
720 				scalingFactor = (float(canvasWidth)/2.0f) / float(xImage);
721 		}
722 		break;
723 
724 	case ZOOM_WIDTH:
725 		if (fitOnlyOversize && !IsOversize(pagenumber))
726 			goto zoom_original;
727 		if (FitWithoutScrollbars(pagenumber, &scalingFactor)) {
728 			if (mode == TWOPAGE) {
729 				// check neighbor pages to see if they fit too
730 				if (pagenumber > 0) {
731 					if (!FitWithoutScrollbars(pagenumber - 1))
732 						goto fith_nofit;
733 				}
734 				if (pagenumber < (pageCount - 1)) {
735 					if (!FitWithoutScrollbars(pagenumber + 1))
736 						goto fith_nofit;
737 				}
738 			}
739 		} else {
740 			fith_nofit:
741 			rImage = float(xImage) / float(yImage);
742 			// fit with scrollbars
743 			if (rImage >= 1.0f || mode == ONEPAGE) {
744 				scalingFactor = float(canvasWidth - scrollbarThickness) / float(xImage);
745 			} else {
746 				scalingFactor = float((canvasWidth - scrollbarThickness) / 2) / float(xImage);
747 			}
748 		}
749 		break;
750 
751 	case ZOOM_HEIGHT: // fit to height
752 		if (fitOnlyOversize && !IsOversize(pagenumber))
753 			goto zoom_original;
754 		if (FitWithoutScrollbars(pagenumber, &scalingFactor)) {
755 			if (mode == TWOPAGE) {
756 				// check neighbor pages to see if they fit too
757 				if (pagenumber > 0) {
758 					if (!FitWithoutScrollbars(pagenumber - 1))
759 						goto fitv_nofit;
760 				}
761 				if (pagenumber < pageCount - 1) {
762 					if (!FitWithoutScrollbars(pagenumber + 1))
763 						goto fitv_nofit;
764 				}
765 			}
766 		} else {
767 			fitv_nofit:
768 			// fit with scrollbars
769 			scalingFactor = float(canvasHeight - scrollbarThickness) / float(yImage);
770 		}
771 		break;
772 
773 	case ZOOM_FULL: // no resize
774 	default:
775 	zoom_original:
776 		switch (Orientations[pagenumber]) {
777 		case NORTH:
778 			resamples[pagenumber] = wxImage(orig);
779 			break;
780 		case EAST:
781 			resamples[pagenumber] = wxImage(orig).Rotate90(true);
782 			break;
783 		case SOUTH:
784 			resamples[pagenumber] = wxImage(orig).Rotate90().Rotate90();
785 			break;
786 		case WEST:
787 			resamples[pagenumber] = wxImage(orig).Rotate90(false);
788 			break;
789 		default:
790 			break;
791 		}
792 		SendScaledEvent(pagenumber);
793 		return;
794 	}
795 
796 	switch (Orientations[pagenumber]) {
797 	case NORTH:
798 		resamples[pagenumber] = FreeImage_Rescale(orig, wxInt32(xImage * scalingFactor), wxInt32(yImage * scalingFactor), filter);
799 		break;
800 	case EAST:
801 		resamples[pagenumber] = FreeImage_Rescale(orig, wxInt32(yImage * scalingFactor), wxInt32(xImage * scalingFactor), filter).Rotate90(true);
802 		break;
803 	case SOUTH:
804 		resamples[pagenumber] = FreeImage_Rescale(orig, wxInt32(xImage * scalingFactor), wxInt32(yImage * scalingFactor), filter).Rotate90().Rotate90();
805 		break;
806 	case WEST:
807 		resamples[pagenumber] = FreeImage_Rescale(orig, wxInt32(yImage * scalingFactor), wxInt32(xImage * scalingFactor), filter).Rotate90(false);
808 		break;
809 	}
810 	SendScaledEvent(pagenumber);
811 }
812 
ScaleThumbnail(wxUint32 pagenumber)813 void ComicBook::ScaleThumbnail(wxUint32 pagenumber)
814 {
815 	wxImage &orig = originals[pagenumber];
816 	wxInt32 xImage, yImage, xScaled, yScaled;
817 	float scalingFactor;
818 
819 	if (Orientations[pagenumber] == NORTH || Orientations[pagenumber] == SOUTH) {
820 		xImage = orig.GetWidth();
821 		yImage = orig.GetHeight();
822 	} else {// EAST || WEST
823 		yImage = orig.GetWidth();
824 		xImage = orig.GetHeight();
825 	}
826 
827 	if(float(yImage) / float(xImage) > 0.6f) {
828 		yScaled = 60;
829 		scalingFactor = 60.0f / float(yImage);
830 		xScaled = wxInt32(float(xImage) * scalingFactor);
831 	} else {
832 		xScaled = 100;
833 		scalingFactor = 100.0f / float(xImage);
834 		yScaled = wxInt32(float(yImage) * scalingFactor);
835 	}
836 
837 	switch (Orientations[pagenumber]) {
838 	case NORTH:
839 		thumbnails[pagenumber] = FreeImage_Rescale(orig, xScaled, yScaled, filter);
840 		break;
841 	case EAST:
842 		thumbnails[pagenumber] = FreeImage_Rescale(orig, yScaled, xScaled, filter).Rotate90(true);
843 		break;
844 	case SOUTH:
845 		thumbnails[pagenumber] = FreeImage_Rescale(orig, xScaled, yScaled, filter).Rotate90().Rotate90();
846 		break;
847 	case WEST:
848 		thumbnails[pagenumber] = FreeImage_Rescale(orig, yScaled, xScaled, filter).Rotate90(false);
849 		break;
850 	}
851 	SendThumbnailedEvent(pagenumber);
852 }
853 
SetCurrentPage(wxUint32 pagenumber)854 void ComicBook::SetCurrentPage(wxUint32 pagenumber)
855 {
856 	currentPage = pagenumber;
857 	SendCurrentPageChangedEvent();
858 }
859 
SendScaledEvent(wxUint32 pagenumber)860 void ComicBook::SendScaledEvent(wxUint32 pagenumber)
861 {
862 	wxCommandEvent event(EVT_PAGE_SCALED, -1);
863 	event.SetInt(pagenumber);
864 	GetEventHandler()->AddPendingEvent(event);
865 }
866 
SendThumbnailedEvent(wxUint32 pagenumber)867 void ComicBook::SendThumbnailedEvent(wxUint32 pagenumber)
868 {
869 	wxCommandEvent event(EVT_PAGE_THUMBNAILED, -1);
870 	event.SetInt(pagenumber);
871 	GetEventHandler()->AddPendingEvent(event);
872 }
873 
SendCurrentPageChangedEvent()874 void ComicBook::SendCurrentPageChangedEvent()
875 {
876 	wxCommandEvent event(EVT_CURRENT_PAGE_CHANGED, -1);
877 	event.SetInt(this->GetCurrentPage());
878 	GetEventHandler()->AddPendingEvent(event);
879 }
880 
SendPageErrorEvent(wxUint32 pagenumber,wxString message)881 void ComicBook::SendPageErrorEvent(wxUint32 pagenumber, wxString message)
882 {
883 	wxCommandEvent event(EVT_PAGE_ERROR, -1);
884 	event.SetInt(pagenumber);
885 	event.SetString(message);
886 	GetEventHandler()->AddPendingEvent(event);
887 }
888 
SetPassword(const char * new_password)889 void ComicBook::SetPassword(const char* new_password)
890 {
891 	if (!new_password)
892 		return;
893 	if (password)
894 		delete[] password;
895 	password = new char[strlen(new_password) + 1];
896 	strcpy(password, new_password);
897 }
898 
postCtor()899 void ComicBook::postCtor()
900 {
901 	Filenames->Sort();
902 	Filenames->Shrink();
903 
904 	pageCount = Filenames->GetCount();
905 
906 	originals = new wxImage[pageCount];
907 	resamples = new wxImage[pageCount];
908 	thumbnails = new wxImage[pageCount];
909 	originalLockers = new wxMutex[pageCount];
910 	resampleLockers = new wxMutex[pageCount];
911 	thumbnailLockers = new wxMutex[pageCount];
912 	Orientations = new COMICAL_ROTATE[pageCount]; // NORTH == 0
913 	for (wxUint32 i = 0; i < pageCount; i++)
914 		Orientations[i] = NORTH;
915 
916 	if (!password) { // the password may already have been set if this is a RAR with encrypted headers
917 		wxString new_password;
918 		while (!TestPassword()) { // if the password needs to be set
919 			new_password = wxGetPasswordFromUser(
920 					wxT("This archive is password-protected.  Please enter the password."),
921 					wxT("Enter Password"));
922 			if (new_password.IsEmpty()) // the dialog was cancelled, and the archive cannot be opened
923 				throw ArchiveException(filename, wxT("Comical could not open this file because it is password-protected."));
924 			SetPassword(new_password.ToAscii());
925 		}
926 	}
927 }
928