/*******************************************************************************
* Copyright 2009 Robot Media SL
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package net.robotmedia.acv.comic;
import java.io.File;
import java.io.FileOutputStream;
import android.net.Uri;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.robotmedia.acv.Constants;
import net.robotmedia.acv.logic.TrackingManager;
import net.robotmedia.acv.utils.FileUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;
public abstract class Comic {
protected enum ImageState {
MODIFIED, UNKNOWN, ORIGINAL
}
private static final HashMap<String, Comic> comics = new HashMap<String, Comic>();
// TODO: Remove comics from collection
private static int MAX_HEIGHT = 1200;
private static int MAX_WIDTH = 1200;
public static void setMaxWidth(int value) {
MAX_WIDTH = value;
}
public static void setMaxHeight(int value) {
MAX_HEIGHT = value;
}
private static Comic instance = null;
protected static final Pattern numberPattern = Pattern.compile("(?<!\\d)0*(\\d{1,3})(?!\\d)");
private static String TAG = "Comic";
protected void setId(String id) {
this.id = id;
}
public static Comic createComic(String path) {
File file = new File(path);
Comic comic = null;
String type;
if (file.isDirectory()) {
comic = new FolderComic(path);
type = Constants.EVENT_VALUE_FOLDER;
} else {
type = FileUtils.getFileExtension(path);
if (Constants.ZIP_EXTENSION.equals(type) || Constants.CBZ_EXTENSION.equals(type)) {
try {
comic = new ZipComic(path);
} catch (Exception e) {
Log.w(TAG, "Failed to load zip comic", e);
}
if (comic == null || comic.isError()) {
try {
comic = new RarComic(path);
} catch (Exception e) {
Log.w(TAG, "Failed to load zip comic as rar comic", e);
}
if (comic != null && !comic.isError()) {
Log.i(TAG, "Loaded rar comic with wrong extension");
comic.type = Constants.RAR_EXTENSION;
}
}
} else if (Constants.RAR_EXTENSION.equals(type) || Constants.CBR_EXTENSION.equals(type)) {
try {
comic = new RarComic(path);
} catch (Exception e) {
Log.w(TAG, "Failed to load rar comic", e);
}
if (comic == null || comic.isError()) {
try {
comic = new ZipComic(path);
} catch (Exception e) {
Log.w(TAG, "Failed to load rar comic as zip comic", e);
}
if (comic != null && !comic.isError()) {
Log.i(TAG, "Loaded rar comic with wrong extension");
comic.type = Constants.ZIP_EXTENSION;
}
}
} else if (FileUtils.isImage(type)) {
comic = new FileComic(path);
} else if (Constants.ACV_EXTENSION.equals(type)) {
comic = new ACVComic(path);
}
}
if (comic != null) {
comic.type = type;
comics.put(comic.getID(), comic);
}
return comic;
}
public static Comic getComic(String id) {
return comics.get(id);
}
@Deprecated
public static Comic getInstance() {
return instance;
}
protected BitmapFactory.Options bounds;
protected String description;
private boolean error = false;
protected String id;
protected String name;
protected String path;
protected HashMap<String, ImageState> imageState;
private String type;
protected Comic(String path) {
instance = this;
name = FileUtils.getFileName(path);
this.path = path;
this.imageState = new HashMap<String, ImageState>();
bounds = new BitmapFactory.Options();
this.bounds.inJustDecodeBounds = true;
}
/**
* Returns a new string by adding leading zeroes to all numbers of the given string. Used to sort strings alphabetically.
* @param value
* @return
*/
protected String addLeadingZeroes(String s) {
Matcher m = numberPattern.matcher(s);
int lastMatch = 0;
ArrayList<String> splitted = new ArrayList<String>();
while (m.find()) {
splitted.add(s.substring(lastMatch,m.start()));
String numberAsString = m.group(1);
int number = Integer.parseInt(numberAsString);
String formattedNumber = String.format("%04d", number);
splitted.add(formattedNumber);
lastMatch = m.end(1);
}
splitted.add(s.substring(lastMatch));
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < splitted.size(); i++) {
buffer.append(splitted.get(i));
}
return buffer.toString();
}
protected int calculateSampleSize(int width, int height) {
boolean landscape = width > height;
int maxHeight = getMaxHeight(landscape);
int maxWidth = getMaxWidth(landscape);
int newWidth;
if (landscape) {
float ratio = (float) height / (float) width;
float containerRatio = (float) maxHeight / (float) maxWidth;
if (ratio < containerRatio) {
newWidth = maxWidth;
} else {
newWidth = width * maxHeight / height;
}
} else {
float ratio = (float) width / (float) height;
float containerRatio = (float) maxWidth / (float) maxHeight;
if (ratio < containerRatio) {
newWidth = width * maxHeight / height;
} else {
newWidth = maxWidth;
}
}
float sampleSizeF = (float) width / (float) newWidth;
int sampleSize = (int) Math.round(Math.ceil(sampleSizeF));
return sampleSize;
}
protected String getRelativePath() {
return Constants.TEMP_PATH;
}
protected File createTempFile(String name) {
File dir = new File(Environment.getExternalStorageDirectory(), this.getRelativePath());
if (id != null) {
dir = new File(dir, this.getID());
}
dir.mkdirs();
if (name.contains(File.separator)) {
String nameDir = name.substring(0, name.lastIndexOf(File.separator));
String nameFile = name.substring(name.lastIndexOf(File.separator)+1);
File f = new File(dir, nameDir);
f.mkdirs();
return new File(f, nameFile);
} else {
return new File(dir, name);
}
}
/**
* Releases all resources.
*/
public abstract void destroy();
protected void error() {
error = true;
}
public Integer getBackgroundColor(int index) {
return Color.BLACK;
}
public String getDescription() {
return description;
}
public int getFramesSize(int index) {
return 0;
}
public String getID() {
return id;
}
/**
* Returns the screen length of the comic.
* @return the screen length of the comic.
*/
public abstract int getLength();
protected int getMaxHeight(boolean landscape) {
return MAX_HEIGHT;
}
protected int getMaxWidth(boolean landscape) {
return MAX_WIDTH;
}
public String getName() {
return name;
}
public String getPath() {
return path;
}
public String getScaleMode() {
return null;
}
/**
* Returns the screen.
* @param position Position of the screen.
* @return Drawable with the screen of the given position, or null if the screen can not be generated or if the position is invalid.
*/
public abstract Drawable getScreen(int position);
protected String getDefaultFileName(int index) {
return index + "." + Constants.JPG_EXTENSION;
}
protected String getTempFilePath(String name) {
File file = createTempFile(name);
if (file.exists()) {
return file.getPath();
} else {
return null;
}
}
/**
* Returns a thumbnail of the screen.
* @param position Position of the screen whose thumbnail is requested.
* @return Drawable with a thumbnail of the screen of the given position, or null if the thumbnail can not be generated or if the position is invalid.
*/
public abstract Drawable getThumbnail(int position);
public String getType() {
return type;
}
/**
* Returns the Uri of the screen, if available.
* @param position Position of the screen.
* @return Uri of the screen or null if not available.
*/
public abstract Uri getUri(int position);
public boolean hasFrames(int index) {
return getFramesSize(index) > 0;
}
/**
* Returns true if the comic is compatible with the given version or if compatibility can not be checked, false otherwise. The default implementation returns true.
* @param version Viewer version code.
* @return True if the comic is compatible with the given version or if compatibility can not be checked, false otherwise.
*/
public boolean isCompatible(int version) {
return true;
}
public boolean isError() {
return error;
}
/**
* Ensures the screen is prepared for a getScreen call. A prepareScreen call is highly likely to be followed by a getScreen call for the same screen.
* @param position Position of the screen that will be prepared. Invalid positions will be ignored.
*/
public abstract void prepareScreen(int position);
protected String saveBitmap(String name, Bitmap bitmap) {
FileOutputStream out = null;
try {
File file = createTempFile(name);
out = new FileOutputStream(file);
boolean success = bitmap.compress(CompressFormat.JPEG, Constants.COMPRESSION_QUALITY, out);
out.close();
if (success) {
return file.getPath();
}
} catch (Exception e) {
e.printStackTrace();
TrackingManager.trackError("Comic.saveBitmap", e);
if (out != null) {
try { out.close(); } catch (Exception e1) {}
}
} finally {
bitmap = null;
}
return null;
}
public void setDescription(String description) {
this.description = description;
}
}