Commit 18a9f25a authored by Jim Nelson's avatar Jim Nelson

libexif bindings built and now in use throughout app. Need to clean up...

libexif bindings built and now in use throughout app.  Need to clean up display before claiming tickets.  Also, 
first stab at adding a full-page view of photos.
parent 20a84f46
public class AppWindow : Gtk.Window {
public static const string TITLE = "Photo Organizer";
public static const string TITLE = "Shotwell";
public static const string VERSION = "0.0.1";
public static const string DATA_DIR = ".photo";
......@@ -81,9 +81,15 @@ public class AppWindow : Gtk.Window {
return subdir;
}
private Gtk.Box layout = null;
private Gtk.TreeStore pageTreeStore = null;
private Gtk.TreeView pageTreeView = null;
private CollectionPage collectionPage = null;
private PhotoTable photoTable = null;
private PhotoPage photoPage = null;
private PhotoTable photoTable = new PhotoTable();
construct {
// set up display
......@@ -92,8 +98,8 @@ public class AppWindow : Gtk.Window {
destroy += Gtk.main_quit;
Gtk.TreeStore pageTreeStore = new Gtk.TreeStore(1, typeof(string));
Gtk.TreeView pageTreeView = new Gtk.TreeView.with_model(pageTreeStore);
pageTreeStore = new Gtk.TreeStore(1, typeof(string));
pageTreeView = new Gtk.TreeView.with_model(pageTreeStore);
pageTreeView.modify_bg(Gtk.StateType.NORMAL, parse_color(CollectionPage.BG_COLOR));
var text = new Gtk.CellRendererText();
......@@ -142,25 +148,10 @@ public class AppWindow : Gtk.Window {
mainWindow = this;
}
photoTable = new PhotoTable();
collectionPage = new CollectionPage();
// layout the growable collection page with the toolbar beneath
Gtk.VBox pageBox = new Gtk.VBox(false, 0);
pageBox.pack_start(collectionPage, true, true, 0);
pageBox.pack_end(collectionPage.get_toolbar(), false, false, 0);
// layout the selection tree to the left of the collection/toolbar box
Gtk.HBox clientBox = new Gtk.HBox(false, 0);
clientBox.pack_start(pageTreeView, false, false, 0);
clientBox.pack_end(pageBox, true, true, 0);
photoPage = new PhotoPage();
// layout client beneath menu
Gtk.VBox mainBox = new Gtk.VBox(false, 0);
mainBox.pack_start(collectionPage.get_menubar(), false, false, 0);
mainBox.pack_end(clientBox, true, true, 0);
add(mainBox);
switch_to_collection_page();
}
public void about_box() {
......@@ -271,5 +262,53 @@ public class AppWindow : Gtk.Window {
}
collectionPage.end_adding();
}
public void switch_to_collection_page() {
switch_to_page(collectionPage, collectionPage.get_action_group(),
collectionPage.get_menubar(), collectionPage.get_toolbar());
}
public void switch_to_photo_page(PhotoID photoID) {
//photoPage.display_photo(photoID);
switch_to_page(photoPage, photoPage.get_action_group(),
photoPage.get_menubar(), photoPage.get_toolbar());
}
private Gtk.ActionGroup oldActionGroup = null;
private void switch_to_page(Gtk.Widget page, Gtk.ActionGroup actionGroup, Gtk.MenuBar menubar,
Gtk.Toolbar toolbar) {
if (layout != null) {
remove(layout);
layout = null;
}
if (oldActionGroup != null) {
remove_accel_group(uiManager.get_accel_group());
uiManager.remove_action_group(oldActionGroup);
oldActionGroup = null;
}
uiManager.insert_action_group(actionGroup, 0);
add_accel_group(uiManager.get_accel_group());
oldActionGroup = actionGroup;
// layout the growable collection page with the toolbar beneath
Gtk.VBox pageBox = new Gtk.VBox(false, 0);
pageBox.pack_start(page, true, true, 0);
pageBox.pack_end(toolbar, false, false, 0);
// layout the selection tree to the left of the collection/toolbar box
Gtk.HBox clientBox = new Gtk.HBox(false, 0);
clientBox.pack_start(pageTreeView, false, false, 0);
clientBox.pack_end(pageBox, true, true, 0);
// layout client beneath menu
layout = new Gtk.VBox(false, 0);
layout.pack_start(menubar, false, false, 0);
layout.pack_end(clientBox, true, true, 0);
add(layout);
}
}
......@@ -169,7 +169,7 @@ public class CollectionLayout : Gtk.Layout {
assert(ypadding >= 0);
// if thumbnail was recently appended, it needs to be put() rather than move()'d
if (thumbnail.parent == this) {
if (thumbnail.parent == (Gtk.Widget) this) {
move(thumbnail, x + xpadding, y + ypadding);
} else {
put(thumbnail, x + xpadding, y + ypadding);
......
......@@ -13,6 +13,7 @@ public class CollectionPage : Gtk.ScrolledWindow {
private PhotoTable photoTable = new PhotoTable();
private CollectionLayout layout = new CollectionLayout();
private Gtk.ActionGroup actionGroup = new Gtk.ActionGroup("CollectionActionGroup");
private Gtk.MenuBar menubar = null;
private Gtk.Toolbar toolbar = new Gtk.Toolbar();
private Gtk.HScale slider = null;
......@@ -30,7 +31,7 @@ public class CollectionPage : Gtk.ScrolledWindow {
{ "SelectAll", Gtk.STOCK_SELECT_ALL, "Select _All", "<Ctrl>A", "Select all the photos in the library", on_select_all },
{ "Remove", Gtk.STOCK_DELETE, "_Remove", "Delete", "Remove the selected photos from the library", on_remove },
{ "Photos", null, "_Photos", null, null, null },
{ "Photos", null, "_Photos", null, null, on_photos_menu },
{ "IncreaseSize", Gtk.STOCK_ZOOM_IN, "Zoom _in", "KP_Add", "Increase the magnification of the thumbnails", on_increase_size },
{ "DecreaseSize", Gtk.STOCK_ZOOM_OUT, "Zoom _out", "KP_Subtract", "Decrease the magnification of the thumbnails", on_decrease_size },
......@@ -44,17 +45,12 @@ public class CollectionPage : Gtk.ScrolledWindow {
};
construct {
Gtk.ActionGroup mainActionGroup = new Gtk.ActionGroup("CollectionActionGroup");
mainActionGroup.add_actions(ACTIONS, this);
AppWindow.get_ui_manager().insert_action_group(mainActionGroup, 0);
Gtk.ActionGroup contextActionGroup = new Gtk.ActionGroup("CollectionContextActionGroup");
contextActionGroup.add_actions(RIGHT_CLICK_ACTIONS, this);
AppWindow.get_ui_manager().insert_action_group(contextActionGroup, 0);
// set up action group
actionGroup.add_actions(ACTIONS, this);
actionGroup.add_actions(RIGHT_CLICK_ACTIONS, this);
// this page's menu bar
menubar = (Gtk.MenuBar) AppWindow.get_ui_manager().get_widget("/CollectionMenuBar");
AppWindow.get_main_window().add_accel_group(AppWindow.get_ui_manager().get_accel_group());
// set up page's toolbar (used by AppWindow for layout)
//
......@@ -108,6 +104,10 @@ public class CollectionPage : Gtk.ScrolledWindow {
return menubar;
}
public Gtk.ActionGroup get_action_group() {
return actionGroup;
}
public void begin_adding() {
}
......@@ -271,6 +271,11 @@ public class CollectionPage : Gtk.ScrolledWindow {
private void on_select_all() {
select_all();
}
private void on_photos_menu() {
set_item_sensitive("/CollectionMenuBar/PhotosMenu/PhotosIncreaseSize", scale < Thumbnail.MAX_SCALE);
set_item_sensitive("/CollectionMenuBar/PhotosMenu/PhotosDecreaseSize", scale > Thumbnail.MIN_SCALE);
}
private void on_increase_size() {
increase_thumb_size();
......@@ -297,7 +302,8 @@ public class CollectionPage : Gtk.ScrolledWindow {
private bool on_left_click(Gdk.EventButton event) {
// only interested in single-clicks presses for now
if (event.type != Gdk.EventType.BUTTON_PRESS) {
if ((event.type != Gdk.EventType.BUTTON_PRESS)
&& (event.type != Gdk.EventType.2BUTTON_PRESS)) {
return false;
}
......@@ -324,9 +330,18 @@ public class CollectionPage : Gtk.ScrolledWindow {
} break;
default: {
// a "raw" click deselects all thumbnails and selects the single chosen
unselect_all();
select(thumbnail);
if (event.type == Gdk.EventType.2BUTTON_PRESS) {
/*
// switch to full-page view
debug("switching to %s [%d]", thumbnail.get_file().get_path(),
thumbnail.get_photo_id().id);
AppWindow.get_main_window().switch_to_photo_page(thumbnail.get_photo_id());
*/
} else {
// a "raw" single-click deselects all thumbnails and selects the single chosen
unselect_all();
select(thumbnail);
}
} break;
}
} else {
......
......@@ -78,6 +78,21 @@ public class PhotoTable : DatabaseTable {
return true;
}
public File? get_file(PhotoID photoID) {
Sqlite.Statement stmt;
int res = db.prepare_v2("SELECT filename FROM PhotoTable WHERE id=?", -1, out stmt);
assert(res == Sqlite.OK);
res = stmt.bind_int(1, photoID.id);
assert(res == Sqlite.OK);
res = stmt.step();
if (res == Sqlite.ROW)
return File.new_for_path(stmt.column_text(0));
return null;
}
public bool remove_by_file(File file) {
Sqlite.Statement stmt;
int res = db.prepare_v2("DELETE FROM PhotoTable WHERE filename=?", -1, out stmt);
......
namespace Exif {
namespace Orientation {
public static const int TOP_LEFT = 1;
public static const int TOP_RIGHT = 2;
public static const int BOTTOM_RIGHT = 3;
public static const int BOTTOM_LEFT = 4;
public static const int LEFT_TOP = 5;
public static const int RIGHT_TOP = 6;
public static const int RIGHT_BOTTOM = 7;
public static const int LEFT_BOTTOM = 8;
}
}
public class PhotoExif {
private File file;
private Exif.Data exifData = null;
public PhotoExif(File file) {
this.file = file;
}
public int get_orientation() {
update();
Exif.Entry orientation = find_entry(Exif.Ifd.ZERO, Exif.Tag.ORIENTATION, Exif.Format.SHORT);
if (orientation == null)
return Exif.Orientation.TOP_LEFT;
return Exif.Convert.get_short(orientation.data, exifData.get_byte_order());
}
public bool get_dimensions(out Dimensions dim) {
update();
Exif.Entry width = find_entry(Exif.Ifd.EXIF, Exif.Tag.PIXEL_X_DIMENSION, Exif.Format.SHORT);
Exif.Entry height = find_entry(Exif.Ifd.EXIF, Exif.Tag.PIXEL_Y_DIMENSION, Exif.Format.SHORT);
if ((width == null) || (height == null))
return false;
dim.width = Exif.Convert.get_short(width.data, exifData.get_byte_order());
dim.height = Exif.Convert.get_short(height.data, exifData.get_byte_order());
return true;
}
public string? get_datetime() {
update();
Exif.Entry datetime = find_entry(Exif.Ifd.EXIF, Exif.Tag.DATE_TIME_ORIGINAL, Exif.Format.ASCII);
if (datetime == null)
return null;
return datetime.get_value();
}
private void update() {
// TODO: Update internal data structures if file changes
if (exifData != null)
return;
debug("Loading EXIF from %s", file.get_path());
exifData = Exif.Data.new_from_file(file.get_path());
// TODO: Better error handling
assert(exifData != null);
// fix now, all at once
exifData.fix();
}
private Exif.Entry? find_entry(Exif.Ifd ifd, Exif.Tag tag, Exif.Format format) {
assert(exifData != null);
Exif.Content content = exifData.ifd[ifd];
assert(content != null);
Exif.Entry entry = content.get_entry(tag);
if (entry == null)
return null;
assert(entry.format == format);
if ((format != Exif.Format.ASCII) && (format != Exif.Format.UNDEFINED))
assert(entry.size == format.get_size());
return entry;
}
}
TARGET = photo
TARGET = shotwell
# This takes care of a warning message generated by the use of Math.round in image_util.vala
VALAC_OPTS =--Xcc=-std=c99
VALAC_OPTS =--Xcc=-std=c99 --save-temps
SRC_FILES = \
main.vala \
......@@ -12,18 +12,34 @@ SRC_FILES = \
DatabaseTables.vala \
ThumbnailCache.vala \
image_util.vala \
CollectionLayout.vala
CollectionLayout.vala \
PhotoPage.vala \
Exif.vala
VAPI_FILES = \
libexif.vapi
VAPI_DIRS = \
.
HEADER_DIRS =
PKGS = \
gtk+-2.0 \
sqlite3 \
vala-1.0
vala-1.0 \
libexif
all: $(TARGET)
clean:
rm -f $(TARGET)
$(TARGET): $(SRC_FILES) Makefile
valac $(VALAC_OPTS) $(foreach pkg,$(PKGS),--pkg $(pkg)) $(SRC_FILES) -o $(TARGET)
$(TARGET): $(SRC_FILES) $(VAPI_FILES) Makefile
valac $(VALAC_OPTS) \
$(foreach pkg,$(PKGS),--pkg=$(pkg)) \
$(foreach vapidir,$(VAPI_DIRS), --vapidir=$(vapidir)) \
$(foreach hdir,$(HEADER_DIRS),-X -I$(hdir)) \
$(SRC_FILES) \
-o $(TARGET)
public class PhotoPage : Gtk.ScrolledWindow {
private PhotoTable photoTable = new PhotoTable();
private Gtk.ActionGroup actionGroup = new Gtk.ActionGroup("PhotoActionGroup");
private Gtk.MenuBar menubar = null;
private Gtk.Toolbar toolbar = new Gtk.Toolbar();
private PhotoID currentPhotoID;
private Gtk.Image image = new Gtk.Image();
private Gtk.Label title = new Gtk.Label(null);
// TODO: Mark fields for translation
private const Gtk.ActionEntry[] ACTIONS = {
{ "File", null, "_File", null, null, null },
{ "Quit", Gtk.STOCK_QUIT, "_Quit", null, "Quit the program", Gtk.main_quit },
{ "Help", null, "_Help", null, null, null },
{ "About", Gtk.STOCK_ABOUT, "_About", null, "About this application", null }
};
construct {
// set up action group
actionGroup.add_actions(ACTIONS, this);
// set up menu bar
menubar = (Gtk.MenuBar) AppWindow.get_ui_manager().get_widget("/CollectionMenuBar");
// scrollbar policy
set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
title.set_use_underline(false);
title.set_justify(Gtk.Justification.LEFT);
title.set_alignment(0, 0);
Gtk.VBox vbox = new Gtk.VBox(false, 0);
vbox.pack_start(image, false, false, 0);
vbox.pack_end(title, false, false, 0);
add_with_viewport(vbox);
}
public Gtk.Toolbar get_toolbar() {
return toolbar;
}
public Gtk.MenuBar get_menubar() {
return menubar;
}
public Gtk.ActionGroup get_action_group() {
return actionGroup;
}
public void display_photo(PhotoID photoID) {
currentPhotoID = photoID;
File file = photoTable.get_file(photoID);
if (file == null)
return;
debug("Loading %s", file.get_path());
Gdk.Pixbuf pixbuf = null;
try {
pixbuf = new Gdk.Pixbuf.from_file(file.get_path());
} catch (Error err) {
// TODO: Better error handling
error("%s", err.message);
}
image.set_from_pixbuf(pixbuf);
title.set_text(file.get_basename());
}
}
......@@ -25,13 +25,16 @@ public class Thumbnail : Gtk.Alignment {
private Dimensions scaledDim;
private Gdk.Pixbuf cached = null;
private Gdk.InterpType scaledInterp = LOW_QUALITY_INTERP;
private PhotoExif exif;
public Thumbnail(PhotoID photoID, File file, int scale = DEFAULT_SCALE) {
this.photoID = photoID;
this.file = file;
this.scale = scale;
this.exif = new PhotoExif(file);
this.originalDim = new PhotoTable().get_dimensions(photoID);
this.scaledDim = get_scaled_dimensions(originalDim, scale);
this.scaledDim = get_rotated_dimensions(scaledDim, exif.get_orientation());
// bottom-align everything
set(0, 1, 0, 0);
......@@ -42,7 +45,7 @@ public class Thumbnail : Gtk.Alignment {
// requisition size, even when it contains no pixbuf
image.set_size_request(scaledDim.width, scaledDim.height);
title = new Gtk.Label(file.get_basename());
title = new Gtk.Label(build_unexposed_title());
title.set_use_underline(false);
title.set_justify(Gtk.Justification.LEFT);
title.set_alignment(0, 0);
......@@ -69,6 +72,32 @@ public class Thumbnail : Gtk.Alignment {
return photoID;
}
private string build_exposed_title() {
int64 fileSize = 0;
try {
FileInfo info = file.query_info(FILE_ATTRIBUTE_STANDARD_SIZE,
FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
fileSize = info.get_size();
} catch(Error err) {
error("%s", err.message);
}
Dimensions dim;
bool dimFound = exif.get_dimensions(out dim);
string datetime = exif.get_datetime();
return "%s\n%s\n%s\n%lld bytes".printf(
file.get_basename(),
(datetime != null) ? datetime : "",
(dimFound) ? "%d x %d".printf(dim.width, dim.height) : "",
fileSize);
}
private string build_unexposed_title() {
return "%s\n\n\n".printf(file.get_basename());
}
public void select() {
selected = true;
......@@ -109,11 +138,13 @@ public class Thumbnail : Gtk.Alignment {
int oldScale = scale;
scale = newScale;
scaledDim = get_scaled_dimensions(originalDim, scale);
scaledDim = get_rotated_dimensions(scaledDim, exif.get_orientation());
// only fetch and scale if exposed
if (cached != null) {
if (ThumbnailCache.refresh_pixbuf(oldScale, newScale)) {
cached = ThumbnailCache.fetch(photoID, newScale);
cached = rotate_to_exif(cached, exif.get_orientation());
}
Gdk.Pixbuf scaled = cached.scale_simple(scaledDim.width, scaledDim.height, LOW_QUALITY_INTERP);
......@@ -148,7 +179,9 @@ public class Thumbnail : Gtk.Alignment {
if (cached != null)
return;
title.set_text(build_exposed_title());
cached = ThumbnailCache.fetch(photoID, scale);
cached = rotate_to_exif(cached, exif.get_orientation());
Gdk.Pixbuf scaled = cached.scale_simple(scaledDim.width, scaledDim.height, LOW_QUALITY_INTERP);
scaledInterp = LOW_QUALITY_INTERP;
image.set_from_pixbuf(scaled);
......@@ -159,6 +192,7 @@ public class Thumbnail : Gtk.Alignment {
if (cached == null)
return;
title.set_text(build_unexposed_title());
cached = null;
image.clear();
image.set_size_request(scaledDim.width, scaledDim.height);
......
......@@ -57,6 +57,35 @@ Dimensions get_scaled_dimensions(Dimensions original, int scale) {
return scaled;
}
Dimensions get_rotated_dimensions(Dimensions dim, int orientation) {
int width = dim.width;
int height = dim.height;
switch(orientation) {
case Exif.Orientation.TOP_LEFT:
case Exif.Orientation.TOP_RIGHT:
case Exif.Orientation.BOTTOM_RIGHT:
case Exif.Orientation.BOTTOM_LEFT: {
// fine just as it is
} break;
case Exif.Orientation.LEFT_TOP:
case Exif.Orientation.RIGHT_TOP:
case Exif.Orientation.RIGHT_BOTTOM:
case Exif.Orientation.LEFT_BOTTOM: {
int swap = width;
width = height;
height = swap;
} break;
default: {
error("Unknown orientation: %d", orientation);
} break;
}
return Dimensions(width, height);
}
Gdk.Pixbuf scale_pixbuf(Gdk.Pixbuf pixbuf, int scale, Gdk.InterpType interp) {
Dimensions original = Dimensions(pixbuf.get_width(), pixbuf.get_height());
Dimensions scaled = get_scaled_dimensions(original, scale);
......@@ -66,3 +95,47 @@ Gdk.Pixbuf scale_pixbuf(Gdk.Pixbuf pixbuf, int scale, Gdk.InterpType interp) {
return pixbuf.scale_simple(scaled.width, scaled.height, interp);
}
Gdk.Pixbuf rotate_to_exif(Gdk.Pixbuf pixbuf, int orientation) {
switch(orientation) {
case Exif.Orientation.TOP_LEFT: {
// fine just as it is
} break;
case Exif.Orientation.TOP_RIGHT: {
pixbuf = pixbuf.flip(true);
} break;
case Exif.Orientation.BOTTOM_RIGHT: {
pixbuf = pixbuf.flip(true);
pixbuf = pixbuf.flip(false);
} break;
case Exif.Orientation.BOTTOM_LEFT: {
pixbuf = pixbuf.flip(false);
} break;
case Exif.Orientation.LEFT_TOP: {
pixbuf = pixbuf.rotate_simple(Gdk.PixbufRotation.CLOCKWISE);
pixbuf = pixbuf.flip(true);
} break;
case Exif.Orientation.RIGHT_TOP: {
pixbuf = pixbuf.rotate_simple(Gdk.PixbufRotation.CLOCKWISE);
} break;
case Exif.Orientation.RIGHT_BOTTOM: {
pixbuf = pixbuf.rotate_simple(Gdk.PixbufRotation.COUNTERCLOCKWISE);
pixbuf = pixbuf.flip(true);
} break;
case Exif.Orientation.LEFT_BOTTOM: {
pixbuf = pixbuf.rotate_simple(Gdk.PixbufRotation.COUNTERCLOCKWISE);
} break;
default: {
error("Unknown orientation: %d", orientation);
} break;
}
return pixbuf;
}
[CCode (
cprefix = "Exif",
lower_case_cprefix="exif_"
)]
namespace Exif {
[CCode (
cname="ExifByteOrder",
cheader_filename="libexif/exif-byte-order.h",
cprefix="EXIF_BYTE_ORDER_"
)]
public enum ByteOrder {
INTEL,
MOTOROLA;
public weak string get_name();
}
[Compact]
[CCode (
cname="ExifContent",
cheader_filename="libexif/exif-content.h",
ref_function="exif_content_ref",
ref_function_void=true,
unref_function="exif_content_unref",
free_function="exif_content_free"
)]
public class Content {
[CCode (cname="exif_content_new")]
public Content();
public void add_entry(Entry entry);
public void remove_entry(Entry entry);
public void dump(uint indent = 4);
public void foreach_entry(ForeachEntryFunc cb, void *user);
public weak Entry get_entry(Tag tag);
public void fix();
public Ifd get_ifd();
public Entry **entries;
public int count;
public Data parent;
}
[CCode (
cheader_filename="libexif/exif-utils.h",
cprefix="exif_",
lower_case_cprefix="exif_"
)]
namespace Convert {
public static uint16 get_short(uchar *buffer, ByteOrder byteOrder);
public static int16 get_sshort(uchar *buffer, ByteOrder byteOrder);
public static uint32 get_long(uchar *buffer, ByteOrder byteOrder);
public static int32 get_slong(uchar *buffer, ByteOrder byteOrder);
}
[CCode (cheader_filename="libexif/exif-content.h")]
public static delegate void ForeachEntryFunc(Entry e, void *user);
[Compact]
[CCode (
cname="ExifData",
cheader_filename="libexif/exif-data.h",
ref_function="exif_data_ref",
ref_function_void=true,
unref_function="exif_data_unref",
free_function="exif_data_free"
)]
public class Data {
[CCode (cname="exif_data_new")]
public Data();
public static Data new_from_file(string path);
public void dump();
public void fix();
public void foreach_content(ForeachContentFunc cb, void *user = null);
public ByteOrder get_byte_order();
public void set_option(DataOption option);
public void unset_option(DataOption option);
public Content[Ifd.COUNT] ifd;
public uchar *data;
public uint size;
}
[CCode (cheader_filename="libexif/exif-data.h")]
public static delegate void ForeachContentFunc(Content c, void *user);
[CCode (
cname="ExifDataOption",
cheader_filename="libexif/exif-data.h",
cprefix="EXIF_DATA_OPTION_"
)]
public enum DataOption {
IGNORE_UNKNOWN_TAGS,
FOLLOW_SPECIFICATION,
DONT_CHANGE_MAKER_NOTE;
public weak string get_name();
public weak string get_description();
}
[Compact]
[CCode (
cname="ExifEntry",
cheader_filename="libexif/exif-entry.h",
ref_function="exif_entry_ref",
ref_function_void=true,
unref_function="exif_entry_unref"
)]
public class Entry {
[CCode (cname="exif_entry_new")]
public Entry();
public void dump(uint indent = 4);
public void initialize(Tag tag);
public void fix();
public weak string get_value(char *val = new char[256], uint maxlen = 256);
public Tag tag;
public Format format;
public ulong components;
public uchar *data;
public uint size;
public Content *parent;
}
[CCode (
cname="ExifFormat",