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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
|
#define USE_GDI
//#define USE_FIB
//#define USE_DIB
using cYo.Projects.ComicRack.Engine;
using cYo.Projects.ComicRack.Engine.IO.Provider;
using cYo.Projects.ComicRack.Viewer;
using FreeImageAPI;
using Nancy;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace BCR
{
public static class BCR
{
private static System.Object lockThis = new System.Object();
public static IEnumerable<Comic> GetComicsForList(BCRUser user, Guid id)
{
var list = Program.Database.ComicLists.FindItem(id);
if (list == null)
{
return Enumerable.Empty<Comic>();
}
return list.GetBooks().Select(x => x.ToComic(user));
}
public static IEnumerable<Series> GetSeriesAndVolumes()
{
return ComicRackWebViewer.Plugin.Application.GetLibraryBooks().AsSeries();
}
public static IEnumerable<Series> GetSeries()
{
return ComicRackWebViewer.Plugin.Application.GetLibraryBooks().AsSeries();
}
/*
*
*/
public static IEnumerable<Comic> GetSeries(BCRUser user, Guid id, NancyContext context)
{
var books = ComicRackWebViewer.Plugin.Application.GetLibraryBooks();
var book = books.Where(x => x.Id == id).First();
var series = books.Where(x => x.ShadowSeries == book.ShadowSeries)
.Where(x => x.ShadowVolume == book.ShadowVolume)
.Select(x => x.ToComic(user))
.OrderBy(x => x.ShadowNumber).ToList();
int totalCount = 0;
return context.ApplyODataUriFilter(series, ref totalCount).Cast<Comic>();
}
// Get all comics from a specific series
public static IEnumerable<Comic> GetComicsFromSeries(BCRUser user, Guid id)
{
var books = ComicRackWebViewer.Plugin.Application.GetLibraryBooks();
var book = books.Where(x => x.Id == id).First();
var series = books.Where(x => x.ShadowSeries == book.ShadowSeries)
.Where(x => x.ShadowVolume == book.ShadowVolume)
.Select(x => x.ToComic(user))
.OrderBy(x => x.ShadowVolume)
.ThenBy(x => x.ShadowNumber).ToList();
return series;
}
// Get all volumes from a specific series
public static IEnumerable<int> GetVolumesFromSeries(Guid id)
{
var books = ComicRackWebViewer.Plugin.Application.GetLibraryBooks();
var book = books.Where(x => x.Id == id).First();
var volumes = books.Where(x => x.ShadowSeries == book.ShadowSeries).Select(x => x.ShadowVolume).Distinct();
return volumes;
}
// Get all comics from a specific series and volume
public static IEnumerable<Comic> GetComicsFromSeriesVolume(BCRUser user, Guid id, int volume)
{
var books = ComicRackWebViewer.Plugin.Application.GetLibraryBooks();
var book = books.Where(x => x.Id == id).First();
var series = books.Where(x => x.ShadowSeries == book.ShadowSeries)
.Where(x => x.ShadowVolume == volume)
.Select(x => x.ToComic(user))
.OrderBy(x => x.ShadowNumber).ToList();
return series;
}
public static MemoryStream GetBytesFromImage(Image image/*, bool progressive, int qualitylevel*/)
{
var bitmap = new Bitmap(image);
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
return stream;
}
private static Bitmap GetPageBitmap(Guid id, int page)
{
try
{
ComicBook comic = GetComics().First(x => x.Id == id);
var index = comic.TranslatePageToImageIndex(page);
var provider = GetProvider(comic);
if (provider == null)
{
return null;
}
return provider.GetImage(index); // ComicRack returns the page converted to a jpeg image.....
}
catch //(Exception e)
{
//MessageBox.Show(e.ToString());
return null;
}
}
private static byte[] GetPageImageBytes(Guid id, int page)
{
try
{
ComicBook comic = GetComics().First(x => x.Id == id);
// Webcomics are not (yet) supported. If I don't filter them here, ComicRack hangs.
if (comic.IsDynamicSource)
return null;
var index = comic.TranslatePageToImageIndex(page);
var provider = GetProvider(comic);
if (provider == null)
{
return null;
}
return provider.GetByteImage(index); // ComicRack returns the page converted to a jpeg image.....
}
catch //(Exception e)
{
//MessageBox.Show(e.ToString());
return null;
}
}
// Uses GDI+ for resizing.
public static Bitmap Resize(Image img, int width, int height)
{
//create a new Bitmap the size of the new image
Bitmap bmp = new Bitmap(width, height);
//create a new graphic from the Bitmap
Graphics graphic = Graphics.FromImage((Image)bmp);
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.CompositingQuality = CompositingQuality.HighQuality;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
//draw the newly resized image
graphic.DrawImage(img, 0, 0, width, height);
//dispose and free up the resources
graphic.Dispose();
//return the image
return bmp;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static bool GetPageImageSize(Guid id, int page, ref int width, ref int height)
{
// Check if original image is in the cache.
string filename = string.Format("{0}-p{1}.jpg", id, page);
MemoryStream stream = ImageCache.Instance.LoadFromCache(filename, false);
if (stream == null)
{
// Image is not in the cache, get it via ComicRack.
var bytes = GetPageImageBytes(id, page);
if (bytes == null)
{
return false;
}
stream = new MemoryStream(bytes);
// Always save the original page to the cache
ImageCache.Instance.SaveToCache(filename, stream, false);
}
stream.Seek(0, SeekOrigin.Begin);
using (Bitmap bitmap = new Bitmap(stream, false))
{
width = (int)bitmap.Width;
height = (int)bitmap.Height;
}
stream.Dispose();
return true;
}
public static Response GetPageImage(Guid id, int page, int width, int height, IResponseFormatter response)
{
// Restrict access to the FreeImage library to one thread at a time.
lock(lockThis)
{
int max_width = 0;
int max_height = 0;
bool thumbnail = !(width == -1 && height == -1);
bool processed = false;
string filename = string.Format("{0}-p{1}-w{2}-h{3}.jpg", id, page, width, height);
if (thumbnail)
{
MemoryStream cachestream = ImageCache.Instance.LoadFromCache(filename, true);
// Cached thumbnails are assumed to be in the correct format and adhere to the size/format restrictions of the ipad.
if (cachestream != null)
return response.FromStream(cachestream, MimeTypes.GetMimeType(".jpg"));
}
else
{
// Check if a processed (rescaled and/or progressive) image is cached.
string processed_filename = string.Format("{0}-p{1}-processed.jpg", id, page);
MemoryStream cachestream = ImageCache.Instance.LoadFromCache(processed_filename, false);
if (cachestream != null)
return response.FromStream(cachestream, MimeTypes.GetMimeType(".jpg"));
}
MemoryStream stream = null;
// Check if original image is in the cache.
string org_filename = string.Format("{0}-p{1}.jpg", id, page);
stream = ImageCache.Instance.LoadFromCache(org_filename, false);
if (stream == null)
{
// Image is not in the cache, get it via ComicRack.
var bytes = GetPageImageBytes(id, page);
if (bytes == null)
{
return HttpStatusCode.NotFound;
}
stream = new MemoryStream(bytes);
// Always save the original page to the cache
ImageCache.Instance.SaveToCache(org_filename, stream, false);
}
stream.Seek(0, SeekOrigin.Begin);
#if USE_GDI
Bitmap bitmap = new Bitmap(stream, false);
int bitmap_width = (int)bitmap.Width;
int bitmap_height = (int)bitmap.Height;
#elif USE_DIB
FIBITMAP dib = FreeImage.LoadFromStream(stream);
if (dib == null)
{
Console.WriteLine("Loading bitmap failed. Aborting.");
// Check whether there was an error message.
return HttpStatusCode.InternalServerError;
}
int bitmap_width = (int)FreeImage.GetWidth(dib);
int bitmap_height = (int)FreeImage.GetHeight(dib);
#elif USE_FIB
FreeImageBitmap fib = FreeImageBitmap.FromStream(stream, false);
if (fib == null)
{
Console.WriteLine("Loading bitmap failed. Aborting.");
// Check whether there was an error message.
return HttpStatusCode.InternalServerError;
}
int bitmap_width = (int)fib.Width;
int bitmap_height = (int)fib.Height;
#endif
if (ImageCache.Instance.use_max_dimension)
{
int mw, mh;
if (bitmap_width >= bitmap_height)
{
mw = ImageCache.Instance.max_dimension_long;
mh = ImageCache.Instance.max_dimension_short;
}
else
{
mw = ImageCache.Instance.max_dimension_short;
mh = ImageCache.Instance.max_dimension_long;
}
if (bitmap_width > mw || bitmap_height > mh)
{
double scaleW = (double)mw / (double)bitmap_width;
double scaleH = (double)mh / (double)bitmap_height;
double scale = Math.Min(scaleW, scaleH);
max_width = (int)Math.Floor(scale * bitmap_width);
max_height = (int)Math.Floor(scale * bitmap_height);
}
else
{
max_width = bitmap_width;
max_height = bitmap_height;
}
}
else
// Check if the image dimensions exceeds the maximum image dimensions
if ((bitmap_width * bitmap_height) > ImageCache.Instance.maximum_imagesize)
{
max_width = (int)Math.Floor(Math.Sqrt((double)bitmap_width / (double)bitmap_height * (double)ImageCache.Instance.maximum_imagesize));
max_height = (int)Math.Floor((double)max_width * (double)bitmap_height / (double)bitmap_width);
}
else
{
max_width = bitmap_width;
max_height = bitmap_height;
}
// Calculate the dimensions of the returned image.
int result_width = width;
int result_height = height;
if (result_width == -1 && result_height == -1)
{
result_width = max_width;
result_height = max_height;
}
else
{
if (result_width == -1)
{
result_height = Math.Min(max_height, result_height);
double ratio = (double)result_height / (double)max_height;
result_width = (int)Math.Floor(((double)max_width * ratio));
}
else
if (result_height == -1)
{
result_width = Math.Min(max_width, result_width);
double ratio = (double)result_width / (double)max_width;
result_height = (int)Math.Floor(((double)max_height * ratio));
}
}
// TODO: do this per requesting target device instead of using one global setting.
// Resize ?
if (result_width != bitmap_width || result_height != bitmap_height)
{
processed = true;
#if USE_DIB || USE_FIB
//FREE_IMAGE_FILTER resizer = FREE_IMAGE_FILTER.FILTER_BICUBIC;
FREE_IMAGE_FILTER resizer = FREE_IMAGE_FILTER.FILTER_LANCZOS3;
#if USE_FIB
fib.Rescale(result_width, result_height, resizer);
#else
FIBITMAP newdib = FreeImage.Rescale(dib, result_width, result_height, resizer);
if (!newdib.IsNull)
{
FreeImage.Unload(dib);
dib.SetNull();
dib = newdib;
}
#endif
#elif USE_GDI
Bitmap resizedBitmap = Resize(bitmap, result_width, result_height);
bitmap.Dispose();
bitmap = resizedBitmap;
resizedBitmap = null;
#endif
}
// Check if the image must be converted to progressive jpeg
if (ImageCache.Instance.use_progressive_jpeg && (result_width * result_height) >= ImageCache.Instance.progressive_jpeg_size_threshold)
{
processed = true;
// Convert image to progressive jpeg
// FreeImage source code reveals that lower 7 bits of the FREE_IMAGE_SAVE_FLAGS enum are used for low-level quality control.
FREE_IMAGE_SAVE_FLAGS quality = (FREE_IMAGE_SAVE_FLAGS)ImageCache.Instance.progressive_jpeg_quality;
FREE_IMAGE_SAVE_FLAGS flags = FREE_IMAGE_SAVE_FLAGS.JPEG_SUBSAMPLING_444 | FREE_IMAGE_SAVE_FLAGS.JPEG_PROGRESSIVE | quality;
#if USE_DIB || USE_FIB
stream.Dispose();
stream = new MemoryStream();
#if USE_FIB
fib.Save(stream, FREE_IMAGE_FORMAT.FIF_JPEG, flags);
fib.Dispose();
#else
FreeImage.SaveToStream(dib, stream, FREE_IMAGE_FORMAT.FIF_JPEG, flags);
FreeImage.Unload(dib);
dib.SetNull();
#endif
#else
FIBITMAP dib = FreeImage.CreateFromBitmap(bitmap);
bitmap.Dispose();
bitmap = null;
stream.Dispose();
stream = new MemoryStream();
FreeImage.SaveToStream(dib, stream, FREE_IMAGE_FORMAT.FIF_JPEG, flags);
FreeImage.Unload(dib);
dib.SetNull();
#endif
}
else
if (processed)
{
// image was rescaled, make new stream with rescaled bitmap
#if USE_DIB || USE_FIB
FREE_IMAGE_SAVE_FLAGS flags = FREE_IMAGE_SAVE_FLAGS.JPEG_SUBSAMPLING_444 | FREE_IMAGE_SAVE_FLAGS.JPEG_OPTIMIZE | FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYNORMAL;
stream.Dispose();
stream = new MemoryStream();
#if USE_FIB
fib.Save(stream, FREE_IMAGE_FORMAT.FIF_JPEG, flags);
fib.Dispose();
#else
FreeImage.SaveToStream(dib, stream, FREE_IMAGE_FORMAT.FIF_JPEG, flags);
FreeImage.Unload(dib);
dib.SetNull();
#endif
#else
stream = GetBytesFromImage(bitmap);
#endif
// For now, images that were resized because they exceeded the maximum dimensions are not saved to the cache.
}
#if USE_DIB
FreeImage.Unload(dib);
dib.SetNull();
#elif USE_FIB
fib.Dispose();
#elif USE_GDI
if (bitmap != null)
{
bitmap.Dispose();
bitmap = null;
}
#endif
// Always save thumbnails to the cache
if (thumbnail)
{
ImageCache.Instance.SaveToCache(filename, stream, true);
}
else
if (processed)
{
// Store rescaled and/or progressive jpegs in the cache for now.
string processed_filename = string.Format("{0}-p{1}-processed.jpg", id, page);
ImageCache.Instance.SaveToCache(processed_filename, stream, false);
}
stream.Seek(0, SeekOrigin.Begin);
return response.FromStream(stream, MimeTypes.GetMimeType(".jpg"));
}
}
private static ImageProvider GetProvider(ComicBook comic)
{
var provider = comic.CreateImageProvider();
if (provider == null)
{
return null;
}
if (provider.Status != ImageProviderStatus.Completed)
{
provider.Open(false);
}
return provider;
}
public static Comic GetComic(BCRUser user, Guid id)
{
try
{
var comic = GetComics().First(x => x.Id == id);
return comic.ToComic(user);
}
catch//(Exception e)
{
//MessageBox.Show(e.ToString());
return null;
}
}
public static ComicBook GetComicBook(Guid id)
{
try
{
var comic = GetComics().First(x => x.Id == id);
return comic;
}
catch//(Exception e)
{
//MessageBox.Show(e.ToString());
return null;
}
}
public static IQueryable<ComicBook> GetComics()
{
return ComicRackWebViewer.Plugin.Application.GetLibraryBooks().AsQueryable();
}
public static Response GetIcon(string key, IResponseFormatter response)
{
var image = ComicBook.PublisherIcons.GetImage(key);
if (image == null)
{
return response.AsRedirect("/original/Views/spacer.png");
}
return response.FromStream(GetBytesFromImage(image), MimeTypes.GetMimeType(".jpg"));
}
public static IEnumerable<Publisher> GetPublishers()
{
return ComicRackWebViewer.Plugin.Application.GetLibraryBooks().GroupBy(x => x.Publisher).Select(x =>
{
return x.GroupBy(y => y.Imprint).Select(y => new Publisher { Name = x.Key, Imprint = y.Key });
}).SelectMany(x => x).OrderBy(x => x.Imprint).OrderBy(x => x.Name);
}
public static IEnumerable<Series> GetSeries(string publisher, string imprint)
{
IEnumerable<ComicBook> comics;
if (string.Compare(publisher, "no publisher", true) == 0)
{
comics = ComicRackWebViewer.Plugin.Application.GetLibraryBooks().Where(x => string.IsNullOrEmpty(x.Publisher));
}
else
{
comics = ComicRackWebViewer.Plugin.Application.GetLibraryBooks().Where(x => string.Compare(publisher, x.Publisher, true) == 0);
if (string.IsNullOrEmpty(imprint))
{
comics = comics.Where(x => string.IsNullOrEmpty(x.Imprint));
}
comics = comics.Where(x => string.Compare(imprint, x.Imprint, true) == 0);
}
return comics.AsSeries();
}
}
}
|