author | alwin <alwin> | 2004-11-01 19:52:25 (UTC) |
---|---|---|
committer | alwin <alwin> | 2004-11-01 19:52:25 (UTC) |
commit | 2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984 (patch) (unidiff) | |
tree | b1d5661ea64931b6eeb2030ceb22532cef699a75 | |
parent | 5a41dcd5901badbd2e258b0a916fb012b6351eeb (diff) | |
download | opie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.zip opie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.tar.gz opie-2dc92f4bfe9e81edc2b0b24ecacf3bc44b344984.tar.bz2 |
exif class added (taken from opie-eye_slave and more c++ like reworked)
it is used in oimageview while loading jpegs checking BEFORE loading if the
image should be scaled loaded.
ToDo: documentation of the interface, remove it from opie-eye_slave (it should
use of course the library version)
-rw-r--r-- | libopie2/opiemm/oimagescrollview.cpp | 14 | ||||
-rw-r--r-- | libopie2/opiemm/opieexif.cpp | 1195 | ||||
-rw-r--r-- | libopie2/opiemm/opieexif.h | 139 | ||||
-rw-r--r-- | libopie2/opiemm/opiemm.pro | 4 |
4 files changed, 1346 insertions, 6 deletions
diff --git a/libopie2/opiemm/oimagescrollview.cpp b/libopie2/opiemm/oimagescrollview.cpp index 56be10b..37a1ad5 100644 --- a/libopie2/opiemm/oimagescrollview.cpp +++ b/libopie2/opiemm/oimagescrollview.cpp | |||
@@ -1,357 +1,363 @@ | |||
1 | #include "oimagescrollview.h" | 1 | #include "oimagescrollview.h" |
2 | 2 | ||
3 | #include <opie2/oimagezoomer.h> | 3 | #include <opie2/oimagezoomer.h> |
4 | #include <opie2/odebug.h> | 4 | #include <opie2/odebug.h> |
5 | #include <opie2/oapplication.h> | 5 | #include <opie2/oapplication.h> |
6 | #include <opie2/owait.h> | 6 | #include <opie2/owait.h> |
7 | #include <opie2/opieexif.h> | ||
7 | 8 | ||
8 | #include <qimage.h> | 9 | #include <qimage.h> |
9 | #include <qlayout.h> | 10 | #include <qlayout.h> |
10 | 11 | ||
11 | /* for usage with the bitset */ | 12 | /* for usage with the bitset */ |
12 | #define AUTO_SCALE 0 | 13 | #define AUTO_SCALE 0 |
13 | #define AUTO_ROTATE 1 | 14 | #define AUTO_ROTATE 1 |
14 | #define SHOW_ZOOMER 2 | 15 | #define SHOW_ZOOMER 2 |
15 | #define FIRST_RESIZE_DONE 3 | 16 | #define FIRST_RESIZE_DONE 3 |
16 | #define IMAGE_IS_JPEG 4 | 17 | #define IMAGE_IS_JPEG 4 |
17 | #define IMAGE_SCALED_LOADED 5 | 18 | #define IMAGE_SCALED_LOADED 5 |
18 | 19 | ||
19 | #define SCROLLVIEW_BITSET_SIZE 6 | 20 | #define SCROLLVIEW_BITSET_SIZE 6 |
20 | 21 | ||
21 | namespace Opie { | 22 | namespace Opie { |
22 | namespace MM { | 23 | namespace MM { |
23 | OImageScrollView::OImageScrollView( QWidget* parent, const char* name, WFlags f ) | 24 | OImageScrollView::OImageScrollView( QWidget* parent, const char* name, WFlags f ) |
24 | :QScrollView(parent,name,f|Qt::WRepaintNoErase ),_image_data(),_original_data(), | 25 | :QScrollView(parent,name,f|Qt::WRepaintNoErase ),_image_data(),_original_data(), |
25 | m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") | 26 | m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") |
26 | { | 27 | { |
27 | _zoomer = 0; | 28 | _zoomer = 0; |
28 | m_states[AUTO_SCALE]=true; | 29 | m_states[AUTO_SCALE]=true; |
29 | m_states[AUTO_ROTATE]=true; | 30 | m_states[AUTO_ROTATE]=true; |
30 | m_states[FIRST_RESIZE_DONE]=false; | 31 | m_states[FIRST_RESIZE_DONE]=false; |
31 | m_states[IMAGE_IS_JPEG]=false; | 32 | m_states[IMAGE_IS_JPEG]=false; |
32 | m_states[IMAGE_SCALED_LOADED]=false; | 33 | m_states[IMAGE_SCALED_LOADED]=false; |
33 | m_states[SHOW_ZOOMER]=true; | 34 | m_states[SHOW_ZOOMER]=true; |
34 | init(); | 35 | init(); |
35 | } | 36 | } |
36 | 37 | ||
37 | OImageScrollView::OImageScrollView (const QImage&img, QWidget * parent, const char * name, WFlags f,bool always_scale,bool rfit) | 38 | OImageScrollView::OImageScrollView (const QImage&img, QWidget * parent, const char * name, WFlags f,bool always_scale,bool rfit) |
38 | :QScrollView(parent,name,f|Qt::WRepaintNoErase),_image_data(),_original_data(img), | 39 | :QScrollView(parent,name,f|Qt::WRepaintNoErase),_image_data(),_original_data(img), |
39 | m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") | 40 | m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") |
40 | { | 41 | { |
41 | _zoomer = 0; | 42 | _zoomer = 0; |
42 | m_states[AUTO_SCALE]=always_scale; | 43 | m_states[AUTO_SCALE]=always_scale; |
43 | m_states[AUTO_ROTATE]=rfit; | 44 | m_states[AUTO_ROTATE]=rfit; |
44 | m_states[FIRST_RESIZE_DONE]=false; | 45 | m_states[FIRST_RESIZE_DONE]=false; |
45 | m_states[IMAGE_IS_JPEG]=false; | 46 | m_states[IMAGE_IS_JPEG]=false; |
46 | m_states[IMAGE_SCALED_LOADED]=false; | 47 | m_states[IMAGE_SCALED_LOADED]=false; |
47 | m_states[SHOW_ZOOMER]=true; | 48 | m_states[SHOW_ZOOMER]=true; |
48 | _original_data.convertDepth(QPixmap::defaultDepth()); | 49 | _original_data.convertDepth(QPixmap::defaultDepth()); |
49 | _original_data.setAlphaBuffer(false); | 50 | _original_data.setAlphaBuffer(false); |
50 | init(); | 51 | init(); |
51 | } | 52 | } |
52 | 53 | ||
53 | OImageScrollView::OImageScrollView (const QString&img, QWidget * parent, const char * name, WFlags f,bool always_scale,bool rfit) | 54 | OImageScrollView::OImageScrollView (const QString&img, QWidget * parent, const char * name, WFlags f,bool always_scale,bool rfit) |
54 | :QScrollView(parent,name,f|Qt::WRepaintNoErase),_image_data(),_original_data(),m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") | 55 | :QScrollView(parent,name,f|Qt::WRepaintNoErase),_image_data(),_original_data(),m_states(SCROLLVIEW_BITSET_SIZE),m_lastName("") |
55 | { | 56 | { |
56 | _zoomer = 0; | 57 | _zoomer = 0; |
57 | m_states.resize(SCROLLVIEW_BITSET_SIZE); | 58 | m_states.resize(SCROLLVIEW_BITSET_SIZE); |
58 | m_states[AUTO_SCALE]=always_scale; | 59 | m_states[AUTO_SCALE]=always_scale; |
59 | m_states[AUTO_ROTATE]=rfit; | 60 | m_states[AUTO_ROTATE]=rfit; |
60 | m_states[FIRST_RESIZE_DONE]=false; | 61 | m_states[FIRST_RESIZE_DONE]=false; |
61 | m_states[IMAGE_IS_JPEG]=false; | 62 | m_states[IMAGE_IS_JPEG]=false; |
62 | m_states[IMAGE_SCALED_LOADED]=false; | 63 | m_states[IMAGE_SCALED_LOADED]=false; |
63 | m_states[SHOW_ZOOMER]=true; | 64 | m_states[SHOW_ZOOMER]=true; |
64 | init(); | 65 | init(); |
65 | setImage(img); | 66 | setImage(img); |
66 | } | 67 | } |
67 | 68 | ||
68 | void OImageScrollView::setImage(const QImage&img) | 69 | void OImageScrollView::setImage(const QImage&img) |
69 | { | 70 | { |
70 | _image_data = QImage(); | 71 | _image_data = QImage(); |
71 | _original_data=img; | 72 | _original_data=img; |
72 | _original_data.convertDepth(QPixmap::defaultDepth()); | 73 | _original_data.convertDepth(QPixmap::defaultDepth()); |
73 | _original_data.setAlphaBuffer(false); | 74 | _original_data.setAlphaBuffer(false); |
74 | m_lastName = ""; | 75 | m_lastName = ""; |
75 | setImageIsJpeg(false); | 76 | setImageIsJpeg(false); |
76 | setImageScaledLoaded(false); | 77 | setImageScaledLoaded(false); |
77 | if (FirstResizeDone()) { | 78 | if (FirstResizeDone()) { |
78 | generateImage(); | 79 | generateImage(); |
79 | } | 80 | } |
80 | } | 81 | } |
81 | 82 | ||
82 | void OImageScrollView::loadJpeg(bool interncall) | 83 | void OImageScrollView::loadJpeg(bool interncall) |
83 | { | 84 | { |
84 | if (m_lastName.isEmpty()) return; | 85 | if (m_lastName.isEmpty()) return; |
85 | QImageIO iio( m_lastName, 0l ); | 86 | QImageIO iio( m_lastName, 0l ); |
86 | QString param; | 87 | QString param; |
87 | bool real_load = false; | 88 | bool real_load = false; |
88 | if (AutoScale()) { | 89 | if (AutoScale()) { |
89 | if (!interncall) { | 90 | if (!interncall) { |
91 | ExifData xf; | ||
92 | bool scanned = xf.scan(m_lastName); | ||
90 | int wid, hei; | 93 | int wid, hei; |
91 | wid = QApplication::desktop()->width(); | 94 | wid = QApplication::desktop()->width(); |
92 | hei = QApplication::desktop()->height(); | 95 | hei = QApplication::desktop()->height(); |
93 | if (hei>wid) { | 96 | if (hei>wid) { |
94 | wid = hei; | 97 | wid = hei; |
95 | } else { | 98 | } else { |
96 | hei = wid; | 99 | hei = wid; |
97 | } | 100 | } |
98 | param = QString( "Fast Shrink( 3 ) Scale( %1, %2, ScaleMin)" ).arg( wid ).arg( hei ); | 101 | if ( (scanned && (wid<xf.getWidth()||hei<xf.getHeight()))||!scanned ) { |
99 | odebug << "Load jpeg scaled \"" << param << "\"" << oendl; | 102 | param = QString( "Fast Shrink( 3 ) Scale( %1, %2, ScaleMin)" ).arg( wid ).arg( hei ); |
100 | iio.setParameters(param.latin1()); | 103 | odebug << "Load jpeg scaled \"" << param << "\"" << oendl; |
101 | setImageScaledLoaded(true); | 104 | iio.setParameters(param.latin1()); |
105 | setImageScaledLoaded(true); | ||
106 | } | ||
107 | |||
102 | real_load = true; | 108 | real_load = true; |
103 | } | 109 | } |
104 | } else { | 110 | } else { |
105 | if (ImageScaledLoaded()||!interncall) { | 111 | if (ImageScaledLoaded()||!interncall) { |
106 | odebug << "Load jpeg unscaled" << oendl; | 112 | odebug << "Load jpeg unscaled" << oendl; |
107 | real_load = true; | 113 | real_load = true; |
108 | } | 114 | } |
109 | setImageScaledLoaded(false); | 115 | setImageScaledLoaded(false); |
110 | } | 116 | } |
111 | if (real_load) { | 117 | if (real_load) { |
112 | _original_data = iio.read() ? iio.image() : QImage(); | 118 | _original_data = iio.read() ? iio.image() : QImage(); |
113 | } | 119 | } |
114 | } | 120 | } |
115 | 121 | ||
116 | void OImageScrollView::setImage( const QString& path ) { | 122 | void OImageScrollView::setImage( const QString& path ) { |
117 | odebug << "load new image " << oendl; | 123 | odebug << "load new image " << oendl; |
118 | if (m_lastName == path) return; | 124 | if (m_lastName == path) return; |
119 | m_lastName = path; | 125 | m_lastName = path; |
120 | _original_data = QImage(); | 126 | _original_data = QImage(); |
121 | QString itype = QImage::imageFormat(m_lastName); | 127 | QString itype = QImage::imageFormat(m_lastName); |
122 | odebug << "Image type = " << itype << oendl; | 128 | odebug << "Image type = " << itype << oendl; |
123 | if (itype == "JPEG") { | 129 | if (itype == "JPEG") { |
124 | setImageIsJpeg(true); | 130 | setImageIsJpeg(true); |
125 | loadJpeg(); | 131 | loadJpeg(); |
126 | } else { | 132 | } else { |
127 | setImageIsJpeg(false); | 133 | setImageIsJpeg(false); |
128 | _original_data.load(path); | 134 | _original_data.load(path); |
129 | _original_data.convertDepth(QPixmap::defaultDepth()); | 135 | _original_data.convertDepth(QPixmap::defaultDepth()); |
130 | _original_data.setAlphaBuffer(false); | 136 | _original_data.setAlphaBuffer(false); |
131 | } | 137 | } |
132 | _image_data = QImage(); | 138 | _image_data = QImage(); |
133 | if (FirstResizeDone()) { | 139 | if (FirstResizeDone()) { |
134 | generateImage(); | 140 | generateImage(); |
135 | if (isVisible()) viewport()->repaint(true); | 141 | if (isVisible()) viewport()->repaint(true); |
136 | } | 142 | } |
137 | } | 143 | } |
138 | 144 | ||
139 | /* should be called every time the QImage changed it content */ | 145 | /* should be called every time the QImage changed it content */ |
140 | void OImageScrollView::init() | 146 | void OImageScrollView::init() |
141 | { | 147 | { |
142 | odebug << "init " << oendl; | 148 | odebug << "init " << oendl; |
143 | 149 | ||
144 | /* | 150 | /* |
145 | * create the zoomer | 151 | * create the zoomer |
146 | * and connect ther various signals | 152 | * and connect ther various signals |
147 | */ | 153 | */ |
148 | _zoomer = new Opie::MM::OImageZoomer( this, "The Zoomer" ); | 154 | _zoomer = new Opie::MM::OImageZoomer( this, "The Zoomer" ); |
149 | connect(_zoomer, SIGNAL( zoomAreaRel(int,int)), | 155 | connect(_zoomer, SIGNAL( zoomAreaRel(int,int)), |
150 | this, SLOT(scrollBy(int,int)) ); | 156 | this, SLOT(scrollBy(int,int)) ); |
151 | connect(_zoomer, SIGNAL( zoomArea(int,int)), | 157 | connect(_zoomer, SIGNAL( zoomArea(int,int)), |
152 | this, SLOT(center(int,int)) ); | 158 | this, SLOT(center(int,int)) ); |
153 | connect(this,SIGNAL(contentsMoving(int,int)), | 159 | connect(this,SIGNAL(contentsMoving(int,int)), |
154 | _zoomer, (SLOT(setVisiblePoint(int,int))) ); | 160 | _zoomer, (SLOT(setVisiblePoint(int,int))) ); |
155 | connect(this,SIGNAL(imageSizeChanged(const QSize&)), | 161 | connect(this,SIGNAL(imageSizeChanged(const QSize&)), |
156 | _zoomer, SLOT(setImageSize(const QSize&)) ); | 162 | _zoomer, SLOT(setImageSize(const QSize&)) ); |
157 | connect(this,SIGNAL(viewportSizeChanged(const QSize&)), | 163 | connect(this,SIGNAL(viewportSizeChanged(const QSize&)), |
158 | _zoomer, SLOT(setViewPortSize(const QSize&)) ); | 164 | _zoomer, SLOT(setViewPortSize(const QSize&)) ); |
159 | 165 | ||
160 | setBackgroundColor(white); | 166 | setBackgroundColor(white); |
161 | setFocusPolicy(QWidget::StrongFocus); | 167 | setFocusPolicy(QWidget::StrongFocus); |
162 | setImageScaledLoaded(false); | 168 | setImageScaledLoaded(false); |
163 | setImageIsJpeg(false); | 169 | setImageIsJpeg(false); |
164 | if (FirstResizeDone()) { | 170 | if (FirstResizeDone()) { |
165 | m_last_rot = Rotate0; | 171 | m_last_rot = Rotate0; |
166 | generateImage(); | 172 | generateImage(); |
167 | } else if (_original_data.size().isValid()) { | 173 | } else if (_original_data.size().isValid()) { |
168 | if (image_fit_into(_original_data.size()) || !ShowZoomer()) _zoomer->hide(); | 174 | if (image_fit_into(_original_data.size()) || !ShowZoomer()) _zoomer->hide(); |
169 | resizeContents(_original_data.width(),_original_data.height()); | 175 | resizeContents(_original_data.width(),_original_data.height()); |
170 | } | 176 | } |
171 | } | 177 | } |
172 | 178 | ||
173 | void OImageScrollView::setAutoRotate(bool how) | 179 | void OImageScrollView::setAutoRotate(bool how) |
174 | { | 180 | { |
175 | /* to avoid double repaints */ | 181 | /* to avoid double repaints */ |
176 | if (AutoRotate() != how) { | 182 | if (AutoRotate() != how) { |
177 | m_states.setBit(AUTO_ROTATE,how); | 183 | m_states.setBit(AUTO_ROTATE,how); |
178 | _image_data = QImage(); | 184 | _image_data = QImage(); |
179 | generateImage(); | 185 | generateImage(); |
180 | } | 186 | } |
181 | } | 187 | } |
182 | 188 | ||
183 | bool OImageScrollView::AutoRotate()const | 189 | bool OImageScrollView::AutoRotate()const |
184 | { | 190 | { |
185 | return m_states.testBit(AUTO_ROTATE); | 191 | return m_states.testBit(AUTO_ROTATE); |
186 | } | 192 | } |
187 | 193 | ||
188 | void OImageScrollView::setAutoScaleRotate(bool scale, bool rotate) | 194 | void OImageScrollView::setAutoScaleRotate(bool scale, bool rotate) |
189 | { | 195 | { |
190 | m_states.setBit(AUTO_ROTATE,rotate); | 196 | m_states.setBit(AUTO_ROTATE,rotate); |
191 | setAutoScale(scale); | 197 | setAutoScale(scale); |
192 | } | 198 | } |
193 | 199 | ||
194 | void OImageScrollView::setAutoScale(bool how) | 200 | void OImageScrollView::setAutoScale(bool how) |
195 | { | 201 | { |
196 | m_states.setBit(AUTO_SCALE,how); | 202 | m_states.setBit(AUTO_SCALE,how); |
197 | _image_data = QImage(); | 203 | _image_data = QImage(); |
198 | if (ImageIsJpeg() && how == false && ImageScaledLoaded()==true) { | 204 | if (ImageIsJpeg() && how == false && ImageScaledLoaded()==true) { |
199 | loadJpeg(true); | 205 | loadJpeg(true); |
200 | } | 206 | } |
201 | generateImage(); | 207 | generateImage(); |
202 | } | 208 | } |
203 | 209 | ||
204 | bool OImageScrollView::AutoScale()const | 210 | bool OImageScrollView::AutoScale()const |
205 | { | 211 | { |
206 | return m_states.testBit(AUTO_SCALE); | 212 | return m_states.testBit(AUTO_SCALE); |
207 | } | 213 | } |
208 | 214 | ||
209 | OImageScrollView::~OImageScrollView() | 215 | OImageScrollView::~OImageScrollView() |
210 | { | 216 | { |
211 | } | 217 | } |
212 | 218 | ||
213 | void OImageScrollView::rescaleImage(int w, int h) | 219 | void OImageScrollView::rescaleImage(int w, int h) |
214 | { | 220 | { |
215 | if (_image_data.width()==w && _image_data.height()==h) { | 221 | if (_image_data.width()==w && _image_data.height()==h) { |
216 | return; | 222 | return; |
217 | } | 223 | } |
218 | double hs = (double)h / (double)_image_data.height() ; | 224 | double hs = (double)h / (double)_image_data.height() ; |
219 | double ws = (double)w / (double)_image_data.width() ; | 225 | double ws = (double)w / (double)_image_data.width() ; |
220 | double scaleFactor = (hs > ws) ? ws : hs; | 226 | double scaleFactor = (hs > ws) ? ws : hs; |
221 | int smoothW = (int)(scaleFactor * _image_data.width()); | 227 | int smoothW = (int)(scaleFactor * _image_data.width()); |
222 | int smoothH = (int)(scaleFactor * _image_data.height()); | 228 | int smoothH = (int)(scaleFactor * _image_data.height()); |
223 | _image_data = _image_data.smoothScale(smoothW,smoothH); | 229 | _image_data = _image_data.smoothScale(smoothW,smoothH); |
224 | } | 230 | } |
225 | 231 | ||
226 | void OImageScrollView::rotate_into_data(Rotation r) | 232 | void OImageScrollView::rotate_into_data(Rotation r) |
227 | { | 233 | { |
228 | /* realy - we must do this that way, 'cause when acting direct on _image_data the app will | 234 | /* realy - we must do this that way, 'cause when acting direct on _image_data the app will |
229 | segfault :( */ | 235 | segfault :( */ |
230 | QImage dest; | 236 | QImage dest; |
231 | int x, y; | 237 | int x, y; |
232 | if ( _original_data.depth() > 8 ) | 238 | if ( _original_data.depth() > 8 ) |
233 | { | 239 | { |
234 | unsigned int *srcData, *destData; | 240 | unsigned int *srcData, *destData; |
235 | switch ( r ) | 241 | switch ( r ) |
236 | { | 242 | { |
237 | case Rotate90: | 243 | case Rotate90: |
238 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); | 244 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); |
239 | for ( y=0; y < _original_data.height(); ++y ) | 245 | for ( y=0; y < _original_data.height(); ++y ) |
240 | { | 246 | { |
241 | srcData = (unsigned int *)_original_data.scanLine(y); | 247 | srcData = (unsigned int *)_original_data.scanLine(y); |
242 | for ( x=0; x < _original_data.width(); ++x ) | 248 | for ( x=0; x < _original_data.width(); ++x ) |
243 | { | 249 | { |
244 | destData = (unsigned int *)dest.scanLine(x); | 250 | destData = (unsigned int *)dest.scanLine(x); |
245 | destData[_original_data.height()-y-1] = srcData[x]; | 251 | destData[_original_data.height()-y-1] = srcData[x]; |
246 | } | 252 | } |
247 | } | 253 | } |
248 | break; | 254 | break; |
249 | case Rotate180: | 255 | case Rotate180: |
250 | dest.create(_original_data.width(), _original_data.height(), _original_data.depth()); | 256 | dest.create(_original_data.width(), _original_data.height(), _original_data.depth()); |
251 | for ( y=0; y < _original_data.height(); ++y ) | 257 | for ( y=0; y < _original_data.height(); ++y ) |
252 | { | 258 | { |
253 | srcData = (unsigned int *)_original_data.scanLine(y); | 259 | srcData = (unsigned int *)_original_data.scanLine(y); |
254 | destData = (unsigned int *)dest.scanLine(_original_data.height()-y-1); | 260 | destData = (unsigned int *)dest.scanLine(_original_data.height()-y-1); |
255 | for ( x=0; x < _original_data.width(); ++x ) | 261 | for ( x=0; x < _original_data.width(); ++x ) |
256 | destData[_original_data.width()-x-1] = srcData[x]; | 262 | destData[_original_data.width()-x-1] = srcData[x]; |
257 | } | 263 | } |
258 | break; | 264 | break; |
259 | case Rotate270: | 265 | case Rotate270: |
260 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); | 266 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); |
261 | for ( y=0; y < _original_data.height(); ++y ) | 267 | for ( y=0; y < _original_data.height(); ++y ) |
262 | { | 268 | { |
263 | srcData = (unsigned int *)_original_data.scanLine(y); | 269 | srcData = (unsigned int *)_original_data.scanLine(y); |
264 | for ( x=0; x < _original_data.width(); ++x ) | 270 | for ( x=0; x < _original_data.width(); ++x ) |
265 | { | 271 | { |
266 | destData = (unsigned int *)dest.scanLine(_original_data.width()-x-1); | 272 | destData = (unsigned int *)dest.scanLine(_original_data.width()-x-1); |
267 | destData[y] = srcData[x]; | 273 | destData[y] = srcData[x]; |
268 | } | 274 | } |
269 | } | 275 | } |
270 | break; | 276 | break; |
271 | default: | 277 | default: |
272 | dest = _original_data; | 278 | dest = _original_data; |
273 | break; | 279 | break; |
274 | } | 280 | } |
275 | } | 281 | } |
276 | else | 282 | else |
277 | { | 283 | { |
278 | unsigned char *srcData, *destData; | 284 | unsigned char *srcData, *destData; |
279 | unsigned int *srcTable, *destTable; | 285 | unsigned int *srcTable, *destTable; |
280 | switch ( r ) | 286 | switch ( r ) |
281 | { | 287 | { |
282 | case Rotate90: | 288 | case Rotate90: |
283 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); | 289 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); |
284 | dest.setNumColors(_original_data.numColors()); | 290 | dest.setNumColors(_original_data.numColors()); |
285 | srcTable = (unsigned int *)_original_data.colorTable(); | 291 | srcTable = (unsigned int *)_original_data.colorTable(); |
286 | destTable = (unsigned int *)dest.colorTable(); | 292 | destTable = (unsigned int *)dest.colorTable(); |
287 | for ( x=0; x < _original_data.numColors(); ++x ) | 293 | for ( x=0; x < _original_data.numColors(); ++x ) |
288 | destTable[x] = srcTable[x]; | 294 | destTable[x] = srcTable[x]; |
289 | for ( y=0; y < _original_data.height(); ++y ) | 295 | for ( y=0; y < _original_data.height(); ++y ) |
290 | { | 296 | { |
291 | srcData = (unsigned char *)_original_data.scanLine(y); | 297 | srcData = (unsigned char *)_original_data.scanLine(y); |
292 | for ( x=0; x < _original_data.width(); ++x ) | 298 | for ( x=0; x < _original_data.width(); ++x ) |
293 | { | 299 | { |
294 | destData = (unsigned char *)dest.scanLine(x); | 300 | destData = (unsigned char *)dest.scanLine(x); |
295 | destData[_original_data.height()-y-1] = srcData[x]; | 301 | destData[_original_data.height()-y-1] = srcData[x]; |
296 | } | 302 | } |
297 | } | 303 | } |
298 | break; | 304 | break; |
299 | case Rotate180: | 305 | case Rotate180: |
300 | dest.create(_original_data.width(), _original_data.height(), _original_data.depth()); | 306 | dest.create(_original_data.width(), _original_data.height(), _original_data.depth()); |
301 | dest.setNumColors(_original_data.numColors()); | 307 | dest.setNumColors(_original_data.numColors()); |
302 | srcTable = (unsigned int *)_original_data.colorTable(); | 308 | srcTable = (unsigned int *)_original_data.colorTable(); |
303 | destTable = (unsigned int *)dest.colorTable(); | 309 | destTable = (unsigned int *)dest.colorTable(); |
304 | for ( x=0; x < _original_data.numColors(); ++x ) | 310 | for ( x=0; x < _original_data.numColors(); ++x ) |
305 | destTable[x] = srcTable[x]; | 311 | destTable[x] = srcTable[x]; |
306 | for ( y=0; y < _original_data.height(); ++y ) | 312 | for ( y=0; y < _original_data.height(); ++y ) |
307 | { | 313 | { |
308 | srcData = (unsigned char *)_original_data.scanLine(y); | 314 | srcData = (unsigned char *)_original_data.scanLine(y); |
309 | destData = (unsigned char *)dest.scanLine(_original_data.height()-y-1); | 315 | destData = (unsigned char *)dest.scanLine(_original_data.height()-y-1); |
310 | for ( x=0; x < _original_data.width(); ++x ) | 316 | for ( x=0; x < _original_data.width(); ++x ) |
311 | destData[_original_data.width()-x-1] = srcData[x]; | 317 | destData[_original_data.width()-x-1] = srcData[x]; |
312 | } | 318 | } |
313 | break; | 319 | break; |
314 | case Rotate270: | 320 | case Rotate270: |
315 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); | 321 | dest.create(_original_data.height(), _original_data.width(), _original_data.depth()); |
316 | dest.setNumColors(_original_data.numColors()); | 322 | dest.setNumColors(_original_data.numColors()); |
317 | srcTable = (unsigned int *)_original_data.colorTable(); | 323 | srcTable = (unsigned int *)_original_data.colorTable(); |
318 | destTable = (unsigned int *)dest.colorTable(); | 324 | destTable = (unsigned int *)dest.colorTable(); |
319 | for ( x=0; x < _original_data.numColors(); ++x ) | 325 | for ( x=0; x < _original_data.numColors(); ++x ) |
320 | destTable[x] = srcTable[x]; | 326 | destTable[x] = srcTable[x]; |
321 | for ( y=0; y < _original_data.height(); ++y ) | 327 | for ( y=0; y < _original_data.height(); ++y ) |
322 | { | 328 | { |
323 | srcData = (unsigned char *)_original_data.scanLine(y); | 329 | srcData = (unsigned char *)_original_data.scanLine(y); |
324 | for ( x=0; x < _original_data.width(); ++x ) | 330 | for ( x=0; x < _original_data.width(); ++x ) |
325 | { | 331 | { |
326 | destData = (unsigned char *)dest.scanLine(_original_data.width()-x-1); | 332 | destData = (unsigned char *)dest.scanLine(_original_data.width()-x-1); |
327 | destData[y] = srcData[x]; | 333 | destData[y] = srcData[x]; |
328 | } | 334 | } |
329 | } | 335 | } |
330 | break; | 336 | break; |
331 | default: | 337 | default: |
332 | dest = _original_data; | 338 | dest = _original_data; |
333 | break; | 339 | break; |
334 | } | 340 | } |
335 | 341 | ||
336 | } | 342 | } |
337 | _image_data = dest; | 343 | _image_data = dest; |
338 | } | 344 | } |
339 | 345 | ||
340 | void OImageScrollView::generateImage() | 346 | void OImageScrollView::generateImage() |
341 | { | 347 | { |
342 | Rotation r = Rotate0; | 348 | Rotation r = Rotate0; |
343 | _pdata = QPixmap(); | 349 | _pdata = QPixmap(); |
344 | if (_original_data.isNull()) { | 350 | if (_original_data.isNull()) { |
345 | emit imageSizeChanged( _image_data.size() ); | 351 | emit imageSizeChanged( _image_data.size() ); |
346 | if (_zoomer) _zoomer->setImage( _image_data ); | 352 | if (_zoomer) _zoomer->setImage( _image_data ); |
347 | return; | 353 | return; |
348 | } | 354 | } |
349 | if (width()>height()&&_original_data.width()<_original_data.height() || | 355 | if (width()>height()&&_original_data.width()<_original_data.height() || |
350 | width()<height()&&_original_data.width()>_original_data.height()) { | 356 | width()<height()&&_original_data.width()>_original_data.height()) { |
351 | if (AutoRotate()) r = Rotate90; | 357 | if (AutoRotate()) r = Rotate90; |
352 | } | 358 | } |
353 | 359 | ||
354 | int twidth,theight; | 360 | int twidth,theight; |
355 | odebug << " r = " << r << oendl; | 361 | odebug << " r = " << r << oendl; |
356 | if (AutoScale() && (_original_data.width()>width() || _original_data.height() > height()) ) { | 362 | if (AutoScale() && (_original_data.width()>width() || _original_data.height() > height()) ) { |
357 | if (!_image_data.size().isValid()||width()>_image_data.width()||height()>_image_data.height()) { | 363 | if (!_image_data.size().isValid()||width()>_image_data.width()||height()>_image_data.height()) { |
diff --git a/libopie2/opiemm/opieexif.cpp b/libopie2/opiemm/opieexif.cpp new file mode 100644 index 0000000..0860ea8 --- a/dev/null +++ b/libopie2/opiemm/opieexif.cpp | |||
@@ -0,0 +1,1195 @@ | |||
1 | #include "opieexif.h" | ||
2 | |||
3 | /* OPIE */ | ||
4 | #include <opie2/odebug.h> | ||
5 | #include <qpe/timestring.h> | ||
6 | /* QT */ | ||
7 | #include <qobject.h> | ||
8 | #include <qimage.h> | ||
9 | |||
10 | /** | ||
11 | exif.h | ||
12 | */ | ||
13 | |||
14 | #include <stdio.h> | ||
15 | #include <stdlib.h> | ||
16 | #include <math.h> | ||
17 | #include <time.h> | ||
18 | |||
19 | #include <qstring.h> | ||
20 | #include <qfile.h> | ||
21 | #include <qimage.h> | ||
22 | |||
23 | //static int HaveAll; | ||
24 | |||
25 | //-------------------------------------------------------------------------- | ||
26 | // Table of Jpeg encoding process names | ||
27 | |||
28 | #define M_SOF0 0xC0 // Start Of Frame N | ||
29 | #define M_SOF1 0xC1 // N indicates which compression process | ||
30 | #define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use | ||
31 | #define M_SOF3 0xC3 | ||
32 | #define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers | ||
33 | #define M_SOF6 0xC6 | ||
34 | #define M_SOF7 0xC7 | ||
35 | #define M_SOF9 0xC9 | ||
36 | #define M_SOF10 0xCA | ||
37 | #define M_SOF11 0xCB | ||
38 | #define M_SOF13 0xCD | ||
39 | #define M_SOF14 0xCE | ||
40 | #define M_SOF15 0xCF | ||
41 | #define M_SOI 0xD8 // Start Of Image (beginning of datastream) | ||
42 | #define M_EOI 0xD9 // End Of Image (end of datastream) | ||
43 | #define M_SOS 0xDA // Start Of Scan (begins compressed data) | ||
44 | #define M_JFIF 0xE0 // Jfif marker | ||
45 | #define M_EXIF 0xE1 // Exif marker | ||
46 | #define M_COM 0xFE // COMment | ||
47 | |||
48 | |||
49 | //-------------------------------------------------------------------------- | ||
50 | // Describes format descriptor | ||
51 | static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; | ||
52 | #define NUM_FORMATS 12 | ||
53 | |||
54 | #define FMT_BYTE 1 | ||
55 | #define FMT_STRING 2 | ||
56 | #define FMT_USHORT 3 | ||
57 | #define FMT_ULONG 4 | ||
58 | #define FMT_URATIONAL 5 | ||
59 | #define FMT_SBYTE 6 | ||
60 | #define FMT_UNDEFINED 7 | ||
61 | #define FMT_SSHORT 8 | ||
62 | #define FMT_SLONG 9 | ||
63 | #define FMT_SRATIONAL 10 | ||
64 | #define FMT_SINGLE 11 | ||
65 | #define FMT_DOUBLE 12 | ||
66 | |||
67 | //-------------------------------------------------------------------------- | ||
68 | // Describes tag values | ||
69 | |||
70 | #define TAG_EXIF_OFFSET 0x8769 | ||
71 | #define TAG_INTEROP_OFFSET 0xa005 | ||
72 | |||
73 | #define TAG_MAKE 0x010F | ||
74 | #define TAG_MODEL 0x0110 | ||
75 | #define TAG_ORIENTATION 0x0112 | ||
76 | |||
77 | #define TAG_EXPOSURETIME 0x829A | ||
78 | #define TAG_FNUMBER 0x829D | ||
79 | |||
80 | #define TAG_SHUTTERSPEED 0x9201 | ||
81 | #define TAG_APERTURE 0x9202 | ||
82 | #define TAG_MAXAPERTURE 0x9205 | ||
83 | #define TAG_FOCALLENGTH 0x920A | ||
84 | |||
85 | #define TAG_DATETIME_ORIGINAL 0x9003 | ||
86 | #define TAG_USERCOMMENT 0x9286 | ||
87 | |||
88 | #define TAG_SUBJECT_DISTANCE 0x9206 | ||
89 | #define TAG_FLASH 0x9209 | ||
90 | |||
91 | #define TAG_FOCALPLANEXRES 0xa20E | ||
92 | #define TAG_FOCALPLANEUNITS 0xa210 | ||
93 | #define TAG_EXIF_IMAGEWIDTH 0xA002 | ||
94 | #define TAG_EXIF_IMAGELENGTH 0xA003 | ||
95 | |||
96 | // the following is added 05-jan-2001 vcs | ||
97 | #define TAG_EXPOSURE_BIAS 0x9204 | ||
98 | #define TAG_WHITEBALANCE 0x9208 | ||
99 | #define TAG_METERING_MODE 0x9207 | ||
100 | #define TAG_EXPOSURE_PROGRAM 0x8822 | ||
101 | #define TAG_ISO_EQUIVALENT 0x8827 | ||
102 | #define TAG_COMPRESSION_LEVEL 0x9102 | ||
103 | |||
104 | #define TAG_THUMBNAIL_OFFSET 0x0201 | ||
105 | #define TAG_THUMBNAIL_LENGTH 0x0202 | ||
106 | |||
107 | |||
108 | |||
109 | namespace Opie { | ||
110 | |||
111 | namespace MM { | ||
112 | |||
113 | class FatalError { | ||
114 | const char* ex; | ||
115 | public: | ||
116 | FatalError(const char* s) { ex = s; } | ||
117 | void debug_print() const { owarn << "exception: " << ex << "" << oendl; } | ||
118 | }; | ||
119 | |||
120 | ExifData::TagTable_t ProcessTable[] = { | ||
121 | { M_SOF0, "Baseline"}, | ||
122 | { M_SOF1, "Extended sequential"}, | ||
123 | { M_SOF2, "Progressive"}, | ||
124 | { M_SOF3, "Lossless"}, | ||
125 | { M_SOF5, "Differential sequential"}, | ||
126 | { M_SOF6, "Differential progressive"}, | ||
127 | { M_SOF7, "Differential lossless"}, | ||
128 | { M_SOF9, "Extended sequential, arithmetic coding"}, | ||
129 | { M_SOF10, "Progressive, arithmetic coding"}, | ||
130 | { M_SOF11, "Lossless, arithmetic coding"}, | ||
131 | { M_SOF13, "Differential sequential, arithmetic coding"}, | ||
132 | { M_SOF14, "Differential progressive, arithmetic coding"}, | ||
133 | { M_SOF15, "Differential lossless, arithmetic coding"}, | ||
134 | { 0, "Unknown"} | ||
135 | }; | ||
136 | |||
137 | //-------------------------------------------------------------------------- | ||
138 | // Parse the marker stream until SOS or EOI is seen; | ||
139 | //-------------------------------------------------------------------------- | ||
140 | int ExifData::ReadJpegSections (QFile & infile, ReadMode_t ReadMode) | ||
141 | { | ||
142 | int a; | ||
143 | |||
144 | a = infile.getch(); | ||
145 | |||
146 | if (a != 0xff || infile.getch() != M_SOI) { | ||
147 | SectionsRead = 0; | ||
148 | return false; | ||
149 | } | ||
150 | for(SectionsRead = 0; SectionsRead < MAX_SECTIONS-1; ){ | ||
151 | int marker = 0; | ||
152 | int got; | ||
153 | unsigned int ll,lh; | ||
154 | unsigned int itemlen; | ||
155 | uchar * Data; | ||
156 | |||
157 | for (a=0;a<7;a++){ | ||
158 | marker = infile.getch(); | ||
159 | if (marker != 0xff) break; | ||
160 | |||
161 | if (a >= 6){ | ||
162 | |||
163 | owarn << "too many padding bytes" << oendl; | ||
164 | return false; | ||
165 | |||
166 | } | ||
167 | } | ||
168 | |||
169 | if (marker == 0xff){ | ||
170 | // 0xff is legal padding, but if we get that many, something's wrong. | ||
171 | return false; | ||
172 | } | ||
173 | |||
174 | Sections[SectionsRead].Type = marker; | ||
175 | |||
176 | // Read the length of the section. | ||
177 | lh = (uchar) infile.getch(); | ||
178 | ll = (uchar) infile.getch(); | ||
179 | |||
180 | itemlen = (lh << 8) | ll; | ||
181 | |||
182 | if (itemlen < 2) { | ||
183 | return false;; | ||
184 | } | ||
185 | |||
186 | Sections[SectionsRead].Size = itemlen; | ||
187 | |||
188 | Data = (uchar *)malloc(itemlen+1); // Add 1 to allow sticking a 0 at the end. | ||
189 | Sections[SectionsRead].Data = Data; | ||
190 | |||
191 | // Store first two pre-read bytes. | ||
192 | Data[0] = (uchar)lh; | ||
193 | Data[1] = (uchar)ll; | ||
194 | |||
195 | got = infile.readBlock((char*)Data+2, itemlen-2); // Read the whole section. | ||
196 | if (( unsigned ) got != itemlen-2){ | ||
197 | return false; | ||
198 | } | ||
199 | SectionsRead++; | ||
200 | |||
201 | switch(marker){ | ||
202 | |||
203 | case M_SOS: // stop before hitting compressed data | ||
204 | // If reading entire image is requested, read the rest of the data. | ||
205 | if (ReadMode & READ_IMAGE){ | ||
206 | unsigned long size; | ||
207 | |||
208 | size = QMAX( 0ul, infile.size()-infile.at() ); | ||
209 | Data = (uchar *)malloc(size); | ||
210 | if (Data == NULL){ | ||
211 | return false; | ||
212 | } | ||
213 | |||
214 | got = infile.readBlock((char*)Data, size); | ||
215 | if (( unsigned ) got != size){ | ||
216 | return false; | ||
217 | } | ||
218 | |||
219 | Sections[SectionsRead].Data = Data; | ||
220 | Sections[SectionsRead].Size = size; | ||
221 | Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER; | ||
222 | SectionsRead ++; | ||
223 | //HaveAll = 1; | ||
224 | } | ||
225 | return true; | ||
226 | |||
227 | case M_EOI: // in case it's a tables-only JPEG stream | ||
228 | owarn << "No image in jpeg!" << oendl; | ||
229 | return false; | ||
230 | |||
231 | case M_COM: // Comment section | ||
232 | // pieczy 2002-02-12 | ||
233 | // now the User comment goes to UserComment | ||
234 | // so we can store a Comment section also in READ_EXIF mode | ||
235 | process_COM(Data, itemlen); | ||
236 | break; | ||
237 | |||
238 | case M_JFIF: | ||
239 | // Regular jpegs always have this tag, exif images have the exif | ||
240 | // marker instead, althogh ACDsee will write images with both markers. | ||
241 | // this program will re-create this marker on absence of exif marker. | ||
242 | // hence no need to keep the copy from the file. | ||
243 | free(Sections[--SectionsRead].Data); | ||
244 | break; | ||
245 | |||
246 | case M_EXIF: | ||
247 | // Seen files from some 'U-lead' software with Vivitar scanner | ||
248 | // that uses marker 31 for non exif stuff. Thus make sure | ||
249 | // it says 'Exif' in the section before treating it as exif. | ||
250 | if ((ReadMode & READ_EXIF) && memcmp(Data+2, "Exif", 4) == 0){ | ||
251 | process_EXIF((uchar *)Data, itemlen); | ||
252 | }else{ | ||
253 | // Discard this section. | ||
254 | free(Sections[--SectionsRead].Data); | ||
255 | } | ||
256 | break; | ||
257 | |||
258 | case M_SOF0: | ||
259 | case M_SOF1: | ||
260 | case M_SOF2: | ||
261 | case M_SOF3: | ||
262 | case M_SOF5: | ||
263 | case M_SOF6: | ||
264 | case M_SOF7: | ||
265 | case M_SOF9: | ||
266 | case M_SOF10: | ||
267 | case M_SOF11: | ||
268 | case M_SOF13: | ||
269 | case M_SOF14: | ||
270 | case M_SOF15: | ||
271 | process_SOFn(Data, marker); | ||
272 | default: | ||
273 | break; | ||
274 | break; | ||
275 | } | ||
276 | } | ||
277 | return true; | ||
278 | } | ||
279 | |||
280 | |||
281 | //-------------------------------------------------------------------------- | ||
282 | // Discard read data. | ||
283 | //-------------------------------------------------------------------------- | ||
284 | void ExifData::DiscardData(void) | ||
285 | { | ||
286 | for (int a=0; a < SectionsRead; a++) | ||
287 | free(Sections[a].Data); | ||
288 | SectionsRead = 0; | ||
289 | } | ||
290 | |||
291 | //-------------------------------------------------------------------------- | ||
292 | // Convert a 16 bit unsigned value from file's native byte order | ||
293 | //-------------------------------------------------------------------------- | ||
294 | int ExifData::Get16u(void * Short) | ||
295 | { | ||
296 | if (MotorolaOrder){ | ||
297 | return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; | ||
298 | }else{ | ||
299 | return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0]; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | //-------------------------------------------------------------------------- | ||
304 | // Convert a 32 bit signed value from file's native byte order | ||
305 | //-------------------------------------------------------------------------- | ||
306 | int ExifData::Get32s(void * Long) | ||
307 | { | ||
308 | if (MotorolaOrder){ | ||
309 | return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) | ||
310 | | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 ); | ||
311 | }else{ | ||
312 | return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) | ||
313 | | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 ); | ||
314 | } | ||
315 | } | ||
316 | |||
317 | //-------------------------------------------------------------------------- | ||
318 | // Convert a 32 bit unsigned value from file's native byte order | ||
319 | //-------------------------------------------------------------------------- | ||
320 | unsigned ExifData::Get32u(void * Long) | ||
321 | { | ||
322 | return (unsigned)Get32s(Long) & 0xffffffff; | ||
323 | } | ||
324 | |||
325 | //-------------------------------------------------------------------------- | ||
326 | // Evaluate number, be it int, rational, or float from directory. | ||
327 | //-------------------------------------------------------------------------- | ||
328 | double ExifData::ConvertAnyFormat(void * ValuePtr, int Format) | ||
329 | { | ||
330 | double Value; | ||
331 | Value = 0; | ||
332 | |||
333 | switch(Format){ | ||
334 | case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; | ||
335 | case FMT_BYTE: Value = *(uchar *)ValuePtr; break; | ||
336 | |||
337 | case FMT_USHORT: Value = Get16u(ValuePtr); break; | ||
338 | |||
339 | case FMT_ULONG: Value = Get32u(ValuePtr); break; | ||
340 | |||
341 | case FMT_URATIONAL: | ||
342 | case FMT_SRATIONAL: | ||
343 | { | ||
344 | int Num,Den; | ||
345 | Num = Get32s(ValuePtr); | ||
346 | Den = Get32s(4+(char *)ValuePtr); | ||
347 | if (Den == 0){ | ||
348 | Value = 0; | ||
349 | }else{ | ||
350 | Value = (double)Num/Den; | ||
351 | } | ||
352 | break; | ||
353 | } | ||
354 | |||
355 | case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; | ||
356 | case FMT_SLONG: Value = Get32s(ValuePtr); break; | ||
357 | |||
358 | // Not sure if this is correct (never seen float used in Exif format) | ||
359 | case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; | ||
360 | case FMT_DOUBLE: Value = *(double *)ValuePtr; break; | ||
361 | } | ||
362 | return Value; | ||
363 | } | ||
364 | |||
365 | //-------------------------------------------------------------------------- | ||
366 | // Process one of the nested EXIF directories. | ||
367 | //-------------------------------------------------------------------------- | ||
368 | void ExifData::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength) | ||
369 | { | ||
370 | int de; | ||
371 | int a; | ||
372 | int NumDirEntries; | ||
373 | unsigned ThumbnailOffset = 0; | ||
374 | unsigned ThumbnailSize = 0; | ||
375 | |||
376 | NumDirEntries = Get16u(DirStart); | ||
377 | #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) | ||
378 | |||
379 | { | ||
380 | unsigned char * DirEnd; | ||
381 | DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); | ||
382 | if (DirEnd+4 > (OffsetBase+ExifLength)){ | ||
383 | if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength){ | ||
384 | // Version 1.3 of jhead would truncate a bit too much. | ||
385 | // This also caught later on as well. | ||
386 | }else{ | ||
387 | // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier | ||
388 | // might trigger this. | ||
389 | return; | ||
390 | } | ||
391 | } | ||
392 | if (DirEnd < LastExifRefd) LastExifRefd = DirEnd; | ||
393 | } | ||
394 | |||
395 | for (de=0;de<NumDirEntries;de++){ | ||
396 | int Tag, Format, Components; | ||
397 | unsigned char * ValuePtr; | ||
398 | int ByteCount; | ||
399 | char * DirEntry; | ||
400 | DirEntry = (char *)DIR_ENTRY_ADDR(DirStart, de); | ||
401 | |||
402 | Tag = Get16u(DirEntry); | ||
403 | Format = Get16u(DirEntry+2); | ||
404 | Components = Get32u(DirEntry+4); | ||
405 | |||
406 | if ((Format-1) >= NUM_FORMATS) { | ||
407 | // (-1) catches illegal zero case as unsigned underflows to positive large. | ||
408 | return; | ||
409 | } | ||
410 | |||
411 | ByteCount = Components * BytesPerFormat[Format]; | ||
412 | |||
413 | if (ByteCount > 4){ | ||
414 | unsigned OffsetVal; | ||
415 | OffsetVal = Get32u(DirEntry+8); | ||
416 | // If its bigger than 4 bytes, the dir entry contains an offset. | ||
417 | if (OffsetVal+ByteCount > ExifLength){ | ||
418 | // Bogus pointer offset and / or bytecount value | ||
419 | //printf("Offset %d bytes %d ExifLen %d\n",OffsetVal, ByteCount, ExifLength); | ||
420 | |||
421 | return; | ||
422 | } | ||
423 | ValuePtr = OffsetBase+OffsetVal; | ||
424 | }else{ | ||
425 | // 4 bytes or less and value is in the dir entry itself | ||
426 | ValuePtr = (unsigned char *)DirEntry+8; | ||
427 | } | ||
428 | |||
429 | if (LastExifRefd < ValuePtr+ByteCount){ | ||
430 | // Keep track of last byte in the exif header that was actually referenced. | ||
431 | // That way, we know where the discardable thumbnail data begins. | ||
432 | LastExifRefd = ValuePtr+ByteCount; | ||
433 | } | ||
434 | |||
435 | // Extract useful components of tag | ||
436 | switch(Tag){ | ||
437 | |||
438 | case TAG_MAKE: | ||
439 | ExifData::CameraMake = QString((char*)ValuePtr); | ||
440 | break; | ||
441 | |||
442 | case TAG_MODEL: | ||
443 | ExifData::CameraModel = QString((char*)ValuePtr); | ||
444 | break; | ||
445 | |||
446 | case TAG_ORIENTATION: | ||
447 | Orientation = (int)ConvertAnyFormat(ValuePtr, Format); | ||
448 | break; | ||
449 | |||
450 | case TAG_DATETIME_ORIGINAL: | ||
451 | DateTime = QString((char*)ValuePtr); | ||
452 | break; | ||
453 | |||
454 | case TAG_USERCOMMENT: | ||
455 | // Olympus has this padded with trailing spaces. Remove these first. | ||
456 | for (a=ByteCount;;){ | ||
457 | a--; | ||
458 | if ((ValuePtr)[a] == ' '){ | ||
459 | (ValuePtr)[a] = '\0'; | ||
460 | }else{ | ||
461 | break; | ||
462 | } | ||
463 | if (a == 0) break; | ||
464 | } | ||
465 | |||
466 | // Copy the comment | ||
467 | if (memcmp(ValuePtr, "ASCII",5) == 0){ | ||
468 | for (a=5;a<10;a++){ | ||
469 | int c; | ||
470 | c = (ValuePtr)[a]; | ||
471 | if (c != '\0' && c != ' '){ | ||
472 | //strncpy(ImageInfo.Comments, (const char*)(a+ValuePtr), 199); | ||
473 | UserComment.sprintf("%s", (const char*)(a+ValuePtr)); | ||
474 | break; | ||
475 | } | ||
476 | } | ||
477 | }else{ | ||
478 | //strncpy(ImageInfo.Comments, (const char*)ValuePtr, 199); | ||
479 | UserComment.sprintf("%s", (const char*)ValuePtr); | ||
480 | } | ||
481 | break; | ||
482 | |||
483 | case TAG_FNUMBER: | ||
484 | // Simplest way of expressing aperture, so I trust it the most. | ||
485 | // (overwrite previously computd value if there is one) | ||
486 | ExifData::ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); | ||
487 | break; | ||
488 | |||
489 | case TAG_APERTURE: | ||
490 | case TAG_MAXAPERTURE: | ||
491 | // More relevant info always comes earlier, so only use this field if we don't | ||
492 | // have appropriate aperture information yet. | ||
493 | if (ExifData::ApertureFNumber == 0){ | ||
494 | ExifData::ApertureFNumber | ||
495 | = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5); | ||
496 | } | ||
497 | break; | ||
498 | |||
499 | case TAG_FOCALLENGTH: | ||
500 | // Nice digital cameras actually save the focal length as a function | ||
501 | // of how farthey are zoomed in. | ||
502 | ExifData::FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); | ||
503 | break; | ||
504 | |||
505 | case TAG_SUBJECT_DISTANCE: | ||
506 | // Inidcates the distacne the autofocus camera is focused to. | ||
507 | // Tends to be less accurate as distance increases. | ||
508 | ExifData::Distance = (float)ConvertAnyFormat(ValuePtr, Format); | ||
509 | break; | ||
510 | |||
511 | case TAG_EXPOSURETIME: | ||
512 | // Simplest way of expressing exposure time, so I trust it most. | ||
513 | // (overwrite previously computd value if there is one) | ||
514 | ExifData::ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format); | ||
515 | break; | ||
516 | |||
517 | case TAG_SHUTTERSPEED: | ||
518 | // More complicated way of expressing exposure time, so only use | ||
519 | // this value if we don't already have it from somewhere else. | ||
520 | if (ExifData::ExposureTime == 0){ | ||
521 | ExifData::ExposureTime | ||
522 | = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2))); | ||
523 | } | ||
524 | break; | ||
525 | |||
526 | case TAG_FLASH: | ||
527 | if (ConvertAnyFormat(ValuePtr, Format)){ | ||
528 | ExifData::FlashUsed = 1; | ||
529 | } | ||
530 | break; | ||
531 | |||
532 | case TAG_EXIF_IMAGELENGTH: | ||
533 | ExifImageLength = (int)ConvertAnyFormat(ValuePtr, Format); | ||
534 | break; | ||
535 | |||
536 | case TAG_EXIF_IMAGEWIDTH: | ||
537 | ExifImageWidth = (int)ConvertAnyFormat(ValuePtr, Format); | ||
538 | break; | ||
539 | |||
540 | case TAG_FOCALPLANEXRES: | ||
541 | FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); | ||
542 | break; | ||
543 | |||
544 | case TAG_FOCALPLANEUNITS: | ||
545 | switch((int)ConvertAnyFormat(ValuePtr, Format)){ | ||
546 | case 1: FocalplaneUnits = 25.4; break; // inch | ||
547 | case 2: | ||
548 | // According to the information I was using, 2 means meters. | ||
549 | // But looking at the Cannon powershot's files, inches is the only | ||
550 | // sensible value. | ||
551 | FocalplaneUnits = 25.4; | ||
552 | break; | ||
553 | |||
554 | case 3: FocalplaneUnits = 10; break; // centimeter | ||
555 | case 4: FocalplaneUnits = 1; break; // milimeter | ||
556 | case 5: FocalplaneUnits = .001; break; // micrometer | ||
557 | } | ||
558 | break; | ||
559 | |||
560 | // Remaining cases contributed by: Volker C. Schoech (schoech@gmx.de) | ||
561 | |||
562 | case TAG_EXPOSURE_BIAS: | ||
563 | ExifData::ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); | ||
564 | break; | ||
565 | |||
566 | case TAG_WHITEBALANCE: | ||
567 | ExifData::Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); | ||
568 | break; | ||
569 | |||
570 | case TAG_METERING_MODE: | ||
571 | ExifData::MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); | ||
572 | break; | ||
573 | |||
574 | case TAG_EXPOSURE_PROGRAM: | ||
575 | ExifData::ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); | ||
576 | break; | ||
577 | |||
578 | case TAG_ISO_EQUIVALENT: | ||
579 | ExifData::ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); | ||
580 | if ( ExifData::ISOequivalent < 50 ) ExifData::ISOequivalent *= 200; | ||
581 | break; | ||
582 | |||
583 | case TAG_COMPRESSION_LEVEL: | ||
584 | ExifData::CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format); | ||
585 | break; | ||
586 | |||
587 | case TAG_THUMBNAIL_OFFSET: | ||
588 | ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); | ||
589 | break; | ||
590 | |||
591 | case TAG_THUMBNAIL_LENGTH: | ||
592 | ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); | ||
593 | break; | ||
594 | |||
595 | } | ||
596 | |||
597 | if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){ | ||
598 | unsigned char * SubdirStart; | ||
599 | SubdirStart = OffsetBase + Get32u(ValuePtr); | ||
600 | if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){ | ||
601 | return; | ||
602 | } | ||
603 | ProcessExifDir(SubdirStart, OffsetBase, ExifLength); | ||
604 | continue; | ||
605 | } | ||
606 | } | ||
607 | |||
608 | { | ||
609 | // In addition to linking to subdirectories via exif tags, | ||
610 | // there's also a potential link to another directory at the end of each | ||
611 | // directory. this has got to be the result of a comitee! | ||
612 | unsigned char * SubdirStart; | ||
613 | unsigned Offset; | ||
614 | |||
615 | if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){ | ||
616 | Offset = Get32u(DIR_ENTRY_ADDR(DirStart, NumDirEntries)); | ||
617 | // There is at least one jpeg from an HP camera having an Offset of almost MAXUINT. | ||
618 | // Adding OffsetBase to it produces an overflow, so compare with ExifLength here. | ||
619 | // See http://bugs.kde.org/show_bug.cgi?id=54542 | ||
620 | if (Offset && Offset < ExifLength){ | ||
621 | SubdirStart = OffsetBase + Offset; | ||
622 | if (SubdirStart > OffsetBase+ExifLength){ | ||
623 | if (SubdirStart < OffsetBase+ExifLength+20){ | ||
624 | // Jhead 1.3 or earlier would crop the whole directory! | ||
625 | // As Jhead produces this form of format incorrectness, | ||
626 | // I'll just let it pass silently | ||
627 | owarn << "Thumbnail removed with Jhead 1.3 or earlier" << oendl; | ||
628 | }else{ | ||
629 | return; | ||
630 | } | ||
631 | }else{ | ||
632 | if (SubdirStart <= OffsetBase+ExifLength){ | ||
633 | ProcessExifDir(SubdirStart, OffsetBase, ExifLength); | ||
634 | } | ||
635 | } | ||
636 | } | ||
637 | }else{ | ||
638 | // The exif header ends before the last next directory pointer. | ||
639 | } | ||
640 | } | ||
641 | |||
642 | if (ThumbnailSize && ThumbnailOffset){ | ||
643 | if (ThumbnailSize + ThumbnailOffset <= ExifLength){ | ||
644 | // The thumbnail pointer appears to be valid. Store it. | ||
645 | Thumbnail.loadFromData(OffsetBase + ThumbnailOffset, ThumbnailSize, "JPEG"); | ||
646 | } | ||
647 | } | ||
648 | } | ||
649 | |||
650 | //-------------------------------------------------------------------------- | ||
651 | // Process a COM marker. We want to leave the bytes unchanged. The | ||
652 | // progam that displays this text may decide to remove blanks, convert | ||
653 | // newlines, or otherwise modify the text. In particular we want to be | ||
654 | // safe for passing utf-8 text. | ||
655 | //-------------------------------------------------------------------------- | ||
656 | void ExifData::process_COM (const uchar * Data, int length) | ||
657 | { | ||
658 | QChar ch; | ||
659 | int a; | ||
660 | |||
661 | for (a=2;a<length;a++){ | ||
662 | ch = Data[a]; | ||
663 | if (ch == '\000') continue; // Remove nulls | ||
664 | Comment.append(ch); | ||
665 | } | ||
666 | } | ||
667 | |||
668 | |||
669 | //-------------------------------------------------------------------------- | ||
670 | // Process a SOFn marker. This is useful for the image dimensions | ||
671 | //-------------------------------------------------------------------------- | ||
672 | void ExifData::process_SOFn (const uchar * Data, int marker) | ||
673 | { | ||
674 | int data_precision, num_components; | ||
675 | |||
676 | data_precision = Data[2]; | ||
677 | ExifData::Height = Get16m(Data+3); | ||
678 | ExifData::Width = Get16m(Data+5); | ||
679 | num_components = Data[7]; | ||
680 | |||
681 | if (num_components == 3){ | ||
682 | ExifData::IsColor = 1; | ||
683 | }else{ | ||
684 | ExifData::IsColor = 0; | ||
685 | } | ||
686 | |||
687 | ExifData::Process = marker; | ||
688 | |||
689 | } | ||
690 | |||
691 | //-------------------------------------------------------------------------- | ||
692 | // Get 16 bits motorola order (always) for jpeg header stuff. | ||
693 | //-------------------------------------------------------------------------- | ||
694 | int ExifData::Get16m(const void * Short) | ||
695 | { | ||
696 | return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; | ||
697 | } | ||
698 | |||
699 | |||
700 | //-------------------------------------------------------------------------- | ||
701 | // Process a EXIF marker | ||
702 | // Describes all the drivel that most digital cameras include... | ||
703 | //-------------------------------------------------------------------------- | ||
704 | void ExifData::process_EXIF(unsigned char * CharBuf, unsigned int length) | ||
705 | { | ||
706 | ExifData::FlashUsed = 0; // If it s from a digicam, and it used flash, it says so. | ||
707 | |||
708 | FocalplaneXRes = 0; | ||
709 | FocalplaneUnits = 0; | ||
710 | ExifImageWidth = 0; | ||
711 | ExifImageLength = 0; | ||
712 | |||
713 | { // Check the EXIF header component | ||
714 | static const uchar ExifHeader[] = "Exif\0\0"; | ||
715 | if (memcmp(CharBuf+2, ExifHeader,6)){ | ||
716 | return; | ||
717 | } | ||
718 | } | ||
719 | |||
720 | if (memcmp(CharBuf+8,"II",2) == 0){ | ||
721 | // printf("Exif section in Intel order\n"); | ||
722 | MotorolaOrder = 0; | ||
723 | }else{ | ||
724 | if (memcmp(CharBuf+8,"MM",2) == 0){ | ||
725 | // printf("Exif section in Motorola order\n"); | ||
726 | MotorolaOrder = 1; | ||
727 | }else{ | ||
728 | return; | ||
729 | } | ||
730 | } | ||
731 | |||
732 | // Check the next two values for correctness. | ||
733 | if (Get16u(CharBuf+10) != 0x2a | ||
734 | || Get32u(CharBuf+12) != 0x08){ | ||
735 | return; | ||
736 | } | ||
737 | |||
738 | LastExifRefd = CharBuf; | ||
739 | |||
740 | // First directory starts 16 bytes in. Offsets start at 8 bytes in. | ||
741 | ProcessExifDir(CharBuf+16, CharBuf+8, length-6); | ||
742 | |||
743 | // This is how far the interesting (non thumbnail) part of the exif went. | ||
744 | ExifSettingsLength = LastExifRefd - CharBuf; | ||
745 | |||
746 | // Compute the CCD width, in milimeters. | ||
747 | if (FocalplaneXRes != 0){ | ||
748 | ExifData::CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); | ||
749 | } | ||
750 | } | ||
751 | |||
752 | //-------------------------------------------------------------------------- | ||
753 | // Convert exif time to Unix time structure | ||
754 | //-------------------------------------------------------------------------- | ||
755 | int ExifData::Exif2tm(struct ::tm * timeptr, char * ExifTime) | ||
756 | { | ||
757 | int a; | ||
758 | |||
759 | timeptr->tm_wday = -1; | ||
760 | |||
761 | // Check for format: YYYY:MM:DD HH:MM:SS format. | ||
762 | a = sscanf(ExifTime, "%d:%d:%d %d:%d:%d", | ||
763 | &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday, | ||
764 | &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec); | ||
765 | |||
766 | if (a == 6){ | ||
767 | timeptr->tm_isdst = -1; | ||
768 | timeptr->tm_mon -= 1; // Adjust for unix zero-based months | ||
769 | timeptr->tm_year -= 1900; // Adjust for year starting at 1900 | ||
770 | return true; // worked. | ||
771 | } | ||
772 | |||
773 | return false; // Wasn't in Exif date format. | ||
774 | } | ||
775 | |||
776 | //-------------------------------------------------------------------------- | ||
777 | // Contructor for initialising | ||
778 | //-------------------------------------------------------------------------- | ||
779 | ExifData::ExifData() | ||
780 | { | ||
781 | ExifData::Whitebalance = -1; | ||
782 | ExifData::MeteringMode = -1; | ||
783 | ExifData::FlashUsed = -1; | ||
784 | Orientation = 0; | ||
785 | Height = 0; | ||
786 | Width = 0; | ||
787 | IsColor = 0; | ||
788 | Process = 0; | ||
789 | FocalLength = 0; | ||
790 | ExposureTime = 0; | ||
791 | ApertureFNumber = 0; | ||
792 | Distance = 0; | ||
793 | CCDWidth = 0; | ||
794 | ExposureBias = 0; | ||
795 | ExposureProgram = 0; | ||
796 | ISOequivalent = 0; | ||
797 | CompressionLevel = 0; | ||
798 | MotorolaOrder = 0; | ||
799 | } | ||
800 | |||
801 | ExifData::~ExifData() | ||
802 | { | ||
803 | } | ||
804 | |||
805 | //-------------------------------------------------------------------------- | ||
806 | // process a EXIF jpeg file | ||
807 | //-------------------------------------------------------------------------- | ||
808 | bool ExifData::scan(const QString & path) | ||
809 | { | ||
810 | int ret; | ||
811 | |||
812 | QFile f(path); | ||
813 | f.open(IO_ReadOnly); | ||
814 | |||
815 | // Scan the JPEG headers. | ||
816 | ret = ReadJpegSections(f, READ_EXIF); | ||
817 | |||
818 | if (ret == false){ | ||
819 | owarn << "Not JPEG file!" << oendl; | ||
820 | DiscardData(); | ||
821 | f.close(); | ||
822 | return false; | ||
823 | } | ||
824 | f.close(); | ||
825 | DiscardData(); | ||
826 | |||
827 | //now make the strings clean, | ||
828 | // for exmaple my Casio is a "QV-4000 " | ||
829 | CameraMake = CameraMake.stripWhiteSpace(); | ||
830 | CameraModel = CameraModel.stripWhiteSpace(); | ||
831 | UserComment = UserComment.stripWhiteSpace(); | ||
832 | Comment = Comment.stripWhiteSpace(); | ||
833 | return true; | ||
834 | } | ||
835 | |||
836 | //-------------------------------------------------------------------------- | ||
837 | // Does the embedded thumbnail match the jpeg image? | ||
838 | //-------------------------------------------------------------------------- | ||
839 | #ifndef JPEG_TOL | ||
840 | #define JPEG_TOL 0.02 | ||
841 | #endif | ||
842 | bool ExifData::isThumbnailSane() { | ||
843 | if (Thumbnail.isNull()) return false; | ||
844 | |||
845 | // check whether thumbnail dimensions match the image | ||
846 | // not foolproof, but catches some altered images (jpegtran -rotate) | ||
847 | if (ExifImageLength != 0 && ExifImageLength != Height) return false; | ||
848 | if (ExifImageWidth != 0 && ExifImageWidth != Width) return false; | ||
849 | if (Thumbnail.width() == 0 || Thumbnail.height() == 0) return false; | ||
850 | if (Height == 0 || Width == 0) return false; | ||
851 | double d = (double)Height/Width*Thumbnail.width()/Thumbnail.height(); | ||
852 | return (1-JPEG_TOL < d) && (d < 1+JPEG_TOL); | ||
853 | } | ||
854 | |||
855 | |||
856 | |||
857 | static QImage flip_image( const QImage& img ); | ||
858 | static QImage rotate_90( const QImage& img ); | ||
859 | static QImage rotate_180( const QImage& ); | ||
860 | static QImage rotate_270( const QImage& ); | ||
861 | |||
862 | //-------------------------------------------------------------------------- | ||
863 | // return a thumbnail that respects the orientation flag | ||
864 | // only if it seems sane | ||
865 | //-------------------------------------------------------------------------- | ||
866 | QImage ExifData::getThumbnail() { | ||
867 | if (!isThumbnailSane()) return NULL; | ||
868 | if (!Orientation || Orientation == 1) return Thumbnail; | ||
869 | |||
870 | // now fix orientation | ||
871 | |||
872 | QImage dest = Thumbnail; | ||
873 | switch (Orientation) { // notice intentional fallthroughs | ||
874 | case 2: dest = flip_image( dest ); break; | ||
875 | case 4: dest = flip_image( dest ); | ||
876 | case 3: dest =rotate_180( dest ); break; | ||
877 | case 5: dest = flip_image( dest ); | ||
878 | case 6: dest = rotate_90( dest ); break; | ||
879 | case 7: dest = flip_image( dest ); | ||
880 | case 8: dest = rotate_270( dest ); break; | ||
881 | default: break; // should never happen | ||
882 | } | ||
883 | return dest; | ||
884 | } | ||
885 | |||
886 | |||
887 | /* | ||
888 | * | ||
889 | */ | ||
890 | static QImage flip_image( const QImage& img ) { | ||
891 | return img.mirror( TRUE, FALSE ); | ||
892 | } | ||
893 | |||
894 | |||
895 | static QImage dest; | ||
896 | static int x, y; | ||
897 | static unsigned int *srcData, *destData; // we're not threaded anyway | ||
898 | static unsigned char *srcData8, *destData8; // 8 bit is char | ||
899 | static unsigned int *srcTable, *destTable; // destination table | ||
900 | |||
901 | |||
902 | static QImage rotate_90_8( const QImage &img ) { | ||
903 | dest.create(img.height(), img.width(), img.depth()); | ||
904 | dest.setNumColors(img.numColors()); | ||
905 | srcTable = (unsigned int *)img.colorTable(); | ||
906 | destTable = (unsigned int *)dest.colorTable(); | ||
907 | for ( x=0; x < img.numColors(); ++x ) | ||
908 | destTable[x] = srcTable[x]; | ||
909 | for ( y=0; y < img.height(); ++y ){ | ||
910 | srcData8 = (unsigned char *)img.scanLine(y); | ||
911 | for ( x=0; x < img.width(); ++x ){ | ||
912 | destData8 = (unsigned char *)dest.scanLine(x); | ||
913 | destData8[img.height()-y-1] = srcData8[x]; | ||
914 | } | ||
915 | } | ||
916 | return dest; | ||
917 | } | ||
918 | |||
919 | static QImage rotate_90_all( const QImage& img ) { | ||
920 | dest.create(img.height(), img.width(), img.depth()); | ||
921 | for ( y=0; y < img.height(); ++y ) { | ||
922 | srcData = (unsigned int *)img.scanLine(y); | ||
923 | for ( x=0; x < img.width(); ++x ) { | ||
924 | destData = (unsigned int *)dest.scanLine(x); | ||
925 | destData[img.height()-y-1] = srcData[x]; | ||
926 | } | ||
927 | } | ||
928 | |||
929 | return dest; | ||
930 | } | ||
931 | |||
932 | |||
933 | static QImage rotate_90( const QImage & img ) { | ||
934 | if ( img.depth() > 8) | ||
935 | return rotate_90_all( img ); | ||
936 | else | ||
937 | return rotate_90_8( img ); | ||
938 | } | ||
939 | |||
940 | static QImage rotate_180_all( const QImage& img ) { | ||
941 | dest.create(img.width(), img.height(), img.depth()); | ||
942 | for ( y=0; y < img.height(); ++y ){ | ||
943 | srcData = (unsigned int *)img.scanLine(y); | ||
944 | destData = (unsigned int *)dest.scanLine(img.height()-y-1); | ||
945 | for ( x=0; x < img.width(); ++x ) | ||
946 | destData[img.width()-x-1] = srcData[x]; | ||
947 | } | ||
948 | return dest; | ||
949 | } | ||
950 | |||
951 | static QImage rotate_180_8( const QImage& img ) { | ||
952 | dest.create(img.width(), img.height(), img.depth()); | ||
953 | dest.setNumColors(img.numColors()); | ||
954 | srcTable = (unsigned int *)img.colorTable(); | ||
955 | destTable = (unsigned int *)dest.colorTable(); | ||
956 | for ( x=0; x < img.numColors(); ++x ) | ||
957 | destTable[x] = srcTable[x]; | ||
958 | for ( y=0; y < img.height(); ++y ){ | ||
959 | srcData8 = (unsigned char *)img.scanLine(y); | ||
960 | destData8 = (unsigned char *)dest.scanLine(img.height()-y-1); | ||
961 | for ( x=0; x < img.width(); ++x ) | ||
962 | destData8[img.width()-x-1] = srcData8[x]; | ||
963 | } | ||
964 | return dest; | ||
965 | } | ||
966 | |||
967 | static QImage rotate_180( const QImage& img ) { | ||
968 | if ( img.depth() > 8 ) | ||
969 | return rotate_180_all( img ); | ||
970 | else | ||
971 | return rotate_180_8( img ); | ||
972 | } | ||
973 | |||
974 | |||
975 | static QImage rotate_270_8( const QImage& img ) { | ||
976 | dest.create(img.height(), img.width(), img.depth()); | ||
977 | dest.setNumColors(img.numColors()); | ||
978 | srcTable = (unsigned int *)img.colorTable(); | ||
979 | destTable = (unsigned int *)dest.colorTable(); | ||
980 | for ( x=0; x < img.numColors(); ++x ) | ||
981 | destTable[x] = srcTable[x]; | ||
982 | for ( y=0; y < img.height(); ++y ){ | ||
983 | srcData8 = (unsigned char *)img.scanLine(y); | ||
984 | for ( x=0; x < img.width(); ++x ){ | ||
985 | destData8 = (unsigned char *)dest.scanLine(img.width()-x-1); | ||
986 | destData8[y] = srcData8[x]; | ||
987 | } | ||
988 | } | ||
989 | |||
990 | return dest; | ||
991 | } | ||
992 | |||
993 | static QImage rotate_270_all( const QImage& img ) { | ||
994 | dest.create(img.height(), img.width(), img.depth()); | ||
995 | for ( y=0; y < img.height(); ++y ){ | ||
996 | srcData = (unsigned int *)img.scanLine(y); | ||
997 | for ( x=0; x < img.width(); ++x ){ | ||
998 | destData = (unsigned int *)dest.scanLine(img.width()-x-1); | ||
999 | destData[y] = srcData[x]; | ||
1000 | } | ||
1001 | } | ||
1002 | return dest; | ||
1003 | } | ||
1004 | |||
1005 | static QImage rotate_270( const QImage& img ) { | ||
1006 | if ( img.depth() > 8 ) | ||
1007 | return rotate_270_all( img ); | ||
1008 | else | ||
1009 | return rotate_270_8( img ); | ||
1010 | } | ||
1011 | |||
1012 | |||
1013 | static QString color_mode_to_string( bool b ) { | ||
1014 | return b ? QObject::tr( "Colormode: Color\n" ) : QObject::tr( "Colormode: Black and white\n" ); | ||
1015 | } | ||
1016 | |||
1017 | static QString compression_to_string( int level ) { | ||
1018 | QString str; | ||
1019 | switch( level ) { | ||
1020 | case 1: | ||
1021 | str = QObject::tr( "Basic" ); | ||
1022 | break; | ||
1023 | case 2: | ||
1024 | str = QObject::tr( "Normal" ); | ||
1025 | break; | ||
1026 | case 4: | ||
1027 | str = QObject::tr( "Fine" ); | ||
1028 | break; | ||
1029 | default: | ||
1030 | str = QObject::tr( "Unknown" ); | ||
1031 | |||
1032 | } | ||
1033 | return QObject::tr("Quality: %1\n").arg(str); | ||
1034 | } | ||
1035 | |||
1036 | |||
1037 | static QDateTime parseDateTime( const QString& string ) | ||
1038 | { | ||
1039 | QDateTime dt; | ||
1040 | if ( string.length() != 19 ) | ||
1041 | return dt; | ||
1042 | |||
1043 | QString year = string.left( 4 ); | ||
1044 | QString month = string.mid( 5, 2 ); | ||
1045 | QString day = string.mid( 8, 2 ); | ||
1046 | QString hour = string.mid( 11, 2 ); | ||
1047 | QString minute = string.mid( 14, 2 ); | ||
1048 | QString seconds = string.mid( 18, 2 ); | ||
1049 | |||
1050 | bool ok; | ||
1051 | bool allOk = true; | ||
1052 | int y = year.toInt( &ok ); | ||
1053 | allOk &= ok; | ||
1054 | |||
1055 | int mo = month.toInt( &ok ); | ||
1056 | allOk &= ok; | ||
1057 | |||
1058 | int d = day.toInt( &ok ); | ||
1059 | allOk &= ok; | ||
1060 | |||
1061 | int h = hour.toInt( &ok ); | ||
1062 | allOk &= ok; | ||
1063 | |||
1064 | int mi = minute.toInt( &ok ); | ||
1065 | allOk &= ok; | ||
1066 | |||
1067 | int s = seconds.toInt( &ok ); | ||
1068 | allOk &= ok; | ||
1069 | |||
1070 | if ( allOk ) { | ||
1071 | dt.setDate( QDate( y, mo, d ) ); | ||
1072 | dt.setTime( QTime( h, mi, s ) ); | ||
1073 | } | ||
1074 | |||
1075 | return dt; | ||
1076 | } | ||
1077 | |||
1078 | static QString white_balance_string( int i ) { | ||
1079 | QString balance; | ||
1080 | switch ( i ) { | ||
1081 | case 0: | ||
1082 | balance = QObject::tr( "Unknown" ); | ||
1083 | break; | ||
1084 | case 1: | ||
1085 | balance = QObject::tr( "Daylight" ); | ||
1086 | break; | ||
1087 | case 2: | ||
1088 | balance = QObject::tr( "Fluorescent" ); | ||
1089 | break; | ||
1090 | case 3: | ||
1091 | balance = QObject::tr( "Tungsten" ); | ||
1092 | break; | ||
1093 | case 17: | ||
1094 | balance = QObject::tr( "Standard light A" ); | ||
1095 | break; | ||
1096 | case 18: | ||
1097 | balance = QObject::tr( "Standard light B" ); | ||
1098 | break; | ||
1099 | case 19: | ||
1100 | balance = QObject::tr( "Standard light C" ); | ||
1101 | break; | ||
1102 | case 20: | ||
1103 | balance = QObject::tr( "D55" ); | ||
1104 | break; | ||
1105 | case 21: | ||
1106 | balance = QObject::tr( "D65" ); | ||
1107 | break; | ||
1108 | case 22: | ||
1109 | balance = QObject::tr( "D75" ); | ||
1110 | break; | ||
1111 | case 255: | ||
1112 | balance = QObject::tr( "Other" ); | ||
1113 | break; | ||
1114 | default: | ||
1115 | balance = QObject::tr( "Unknown" ); | ||
1116 | } | ||
1117 | return QObject::tr( "White Balance: %1\n" ).arg( balance ); | ||
1118 | |||
1119 | } | ||
1120 | |||
1121 | |||
1122 | static QString metering_mode( int i) { | ||
1123 | QString meter; | ||
1124 | switch( i ) { | ||
1125 | case 0: | ||
1126 | meter = QObject::tr( "Unknown" ); | ||
1127 | break; | ||
1128 | case 1: | ||
1129 | meter = QObject::tr( "Average" ); | ||
1130 | break; | ||
1131 | case 2: | ||
1132 | meter = QObject::tr( "Center weighted average" ); | ||
1133 | break; | ||
1134 | case 3: | ||
1135 | meter = QObject::tr( "Spot" ); | ||
1136 | break; | ||
1137 | case 4: | ||
1138 | meter = QObject::tr( "MultiSpot" ); | ||
1139 | break; | ||
1140 | case 5: | ||
1141 | meter = QObject::tr( "Pattern" ); | ||
1142 | break; | ||
1143 | case 6: | ||
1144 | meter = QObject::tr( "Partial" ); | ||
1145 | break; | ||
1146 | case 255: | ||
1147 | meter = QObject::tr( "Other" ); | ||
1148 | break; | ||
1149 | default: | ||
1150 | meter = QObject::tr( "Unknown" ); | ||
1151 | } | ||
1152 | |||
1153 | return QObject::tr( "Metering Mode: %1\n" ).arg( meter ); | ||
1154 | } | ||
1155 | |||
1156 | |||
1157 | static QString exposure_program( int i ) { | ||
1158 | QString exp; | ||
1159 | switch( i ) { | ||
1160 | case 0: | ||
1161 | exp = QObject::tr( "Not defined" ); | ||
1162 | break; | ||
1163 | case 1: | ||
1164 | exp = QObject::tr( "Manual" ); | ||
1165 | break; | ||
1166 | case 2: | ||
1167 | exp = QObject::tr( "Normal progam" ); | ||
1168 | break; | ||
1169 | case 3: | ||
1170 | exp = QObject::tr( "Aperture priority" ); | ||
1171 | break; | ||
1172 | case 4: | ||
1173 | exp = QObject::tr( "Shutter priority" ); | ||
1174 | break; | ||
1175 | case 5: | ||
1176 | exp = QObject::tr( "Creative progam\n(biased toward fast shutter speed" ); | ||
1177 | break; | ||
1178 | case 6: | ||
1179 | exp = QObject::tr( "Action progam\n(biased toward fast shutter speed)" ); | ||
1180 | break; | ||
1181 | case 7: | ||
1182 | exp = QObject::tr( "Portrait mode\n(for closeup photos with the background out of focus)" ); | ||
1183 | break; | ||
1184 | case 8: | ||
1185 | exp = QObject::tr( "Landscape mode\n(for landscape photos with the background in focus)" ); | ||
1186 | break; | ||
1187 | default: | ||
1188 | exp = QObject::tr( "Unknown" ); | ||
1189 | } | ||
1190 | |||
1191 | return QObject::tr( "Exposure Program: %1\n" ).arg( exp ); | ||
1192 | } | ||
1193 | |||
1194 | } // namespace MM | ||
1195 | } // namespace OPIE | ||
diff --git a/libopie2/opiemm/opieexif.h b/libopie2/opiemm/opieexif.h new file mode 100644 index 0000000..efaed71 --- a/dev/null +++ b/libopie2/opiemm/opieexif.h | |||
@@ -0,0 +1,139 @@ | |||
1 | #ifndef _OPIE_EXIF_H | ||
2 | #define _OPIE_EXIF_H | ||
3 | |||
4 | #include <qt.h> | ||
5 | #include <qstring.h> | ||
6 | #include <qimage.h> | ||
7 | #include <qfile.h> | ||
8 | |||
9 | #include <time.h> | ||
10 | |||
11 | namespace Opie { namespace MM { | ||
12 | |||
13 | #ifndef uchar | ||
14 | typedef unsigned char uchar; | ||
15 | #endif | ||
16 | |||
17 | //#define MAX_SECTIONS 20 | ||
18 | //#define PSEUDO_IMAGE_MARKER 0x123; // Extra value. | ||
19 | |||
20 | //! Class for reading exif data from images | ||
21 | /*! | ||
22 | * This class is mostly used inside OImageScrollView for testing jpegs headers for a faster | ||
23 | * loading and scaling. It is taken from libexif and converted into an C++ structure. | ||
24 | * | ||
25 | * \see OImageScrollView | ||
26 | * \since 1.2 | ||
27 | */ | ||
28 | class ExifData { | ||
29 | public: | ||
30 | enum ReadMode_t { | ||
31 | READ_EXIF = 1, | ||
32 | READ_IMAGE = 2, | ||
33 | READ_ALL = 3 | ||
34 | }; | ||
35 | |||
36 | //-------------------------------------------------------------------------- | ||
37 | // This structure is used to store jpeg file sections in memory. | ||
38 | struct Section_t { | ||
39 | uchar * Data; | ||
40 | int Type; | ||
41 | unsigned Size; | ||
42 | }; | ||
43 | |||
44 | struct TagTable_t { | ||
45 | unsigned short Tag; | ||
46 | const char*const Desc; | ||
47 | }; | ||
48 | |||
49 | private: | ||
50 | static const int MAX_SECTIONS=20; | ||
51 | static const unsigned int PSEUDO_IMAGE_MARKER=0x123; | ||
52 | Section_t Sections[MAX_SECTIONS]; | ||
53 | |||
54 | QString CameraMake; | ||
55 | QString CameraModel; | ||
56 | QString DateTime; | ||
57 | int Orientation; | ||
58 | int Height, Width; | ||
59 | int ExifImageLength, ExifImageWidth; | ||
60 | int IsColor; | ||
61 | int Process; | ||
62 | int FlashUsed; | ||
63 | float FocalLength; | ||
64 | float ExposureTime; | ||
65 | float ApertureFNumber; | ||
66 | float Distance; | ||
67 | int Whitebalance; | ||
68 | int MeteringMode; | ||
69 | float CCDWidth; | ||
70 | float ExposureBias; | ||
71 | int ExposureProgram; | ||
72 | int ISOequivalent; | ||
73 | int CompressionLevel; | ||
74 | QString UserComment; | ||
75 | QString Comment; | ||
76 | QImage Thumbnail; | ||
77 | |||
78 | unsigned char * LastExifRefd; | ||
79 | int ExifSettingsLength; | ||
80 | double FocalplaneXRes; | ||
81 | double FocalplaneUnits; | ||
82 | int MotorolaOrder; | ||
83 | int SectionsRead; | ||
84 | |||
85 | int ReadJpegSections (QFile & infile, ReadMode_t ReadMode); | ||
86 | void DiscardData(void); | ||
87 | int Get16u(void * Short); | ||
88 | int Get32s(void * Long); | ||
89 | unsigned Get32u(void * Long); | ||
90 | double ConvertAnyFormat(void * ValuePtr, int Format); | ||
91 | void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength); | ||
92 | void process_COM (const uchar * Data, int length); | ||
93 | void process_SOFn (const uchar * Data, int marker); | ||
94 | int Get16m(const void * Short); | ||
95 | void process_EXIF(unsigned char * CharBuf, unsigned int length); | ||
96 | int Exif2tm(struct ::tm * timeptr, char * ExifTime); | ||
97 | |||
98 | public: | ||
99 | //! Contructor for initialising | ||
100 | ExifData(); | ||
101 | //! destructor | ||
102 | virtual ~ExifData(); | ||
103 | //! scan a given file | ||
104 | /*! | ||
105 | * try to scan the EXIF data of a image file | ||
106 | * \param aFile the file to scan | ||
107 | * \return true if success, otherwise false | ||
108 | */ | ||
109 | bool scan(const QString &aFile); | ||
110 | QString getCameraMake() { return CameraMake; } | ||
111 | QString getCameraModel() { return CameraModel; } | ||
112 | QString getDateTime() { return DateTime; } | ||
113 | int getOrientation() { return Orientation; } | ||
114 | int getHeight() { return Height; } | ||
115 | int getWidth() { return Width; } | ||
116 | int getIsColor() { return IsColor; } | ||
117 | int getProcess() { return Process; } | ||
118 | int getFlashUsed() { return FlashUsed; } | ||
119 | float getFocalLength() { return FocalLength; } | ||
120 | float getExposureTime() { return ExposureTime; } | ||
121 | float getApertureFNumber() { return ApertureFNumber; } | ||
122 | float getDistance() { return Distance; } | ||
123 | int getWhitebalance() { return Whitebalance; } | ||
124 | int getMeteringMode() { return MeteringMode; } | ||
125 | float getCCDWidth() { return CCDWidth; } | ||
126 | float getExposureBias() { return ExposureBias; } | ||
127 | int getExposureProgram() { return ExposureProgram; } | ||
128 | int getISOequivalent() { return ISOequivalent; } | ||
129 | int getCompressionLevel() { return CompressionLevel; } | ||
130 | QString getUserComment() { return UserComment; } | ||
131 | QString getComment() { return Comment; } | ||
132 | QImage getThumbnail(); | ||
133 | bool isThumbnailSane(); | ||
134 | bool isNullThumbnail() { return !isThumbnailSane(); } | ||
135 | }; | ||
136 | |||
137 | } | ||
138 | } | ||
139 | #endif | ||
diff --git a/libopie2/opiemm/opiemm.pro b/libopie2/opiemm/opiemm.pro index 09b5ed4..32580c8 100644 --- a/libopie2/opiemm/opiemm.pro +++ b/libopie2/opiemm/opiemm.pro | |||
@@ -1,18 +1,18 @@ | |||
1 | TEMPLATE = lib | 1 | TEMPLATE = lib |
2 | CONFIG += qt warn_on | 2 | CONFIG += qt warn_on |
3 | DESTDIR = $(OPIEDIR)/lib | 3 | DESTDIR = $(OPIEDIR)/lib |
4 | HEADERS = osoundsystem.h oimagezoomer.h oimagescrollview.h | 4 | HEADERS = osoundsystem.h oimagezoomer.h oimagescrollview.h opieexif.h |
5 | SOURCES = osoundsystem.cpp oimagezoomer.cpp oimagescrollview.cpp | 5 | SOURCES = osoundsystem.cpp oimagezoomer.cpp oimagescrollview.cpp opieexif.cpp |
6 | INTERFACES = | 6 | INTERFACES = |
7 | TARGET = opiemm2 | 7 | TARGET = opiemm2 |
8 | VERSION = 1.9.0 | 8 | VERSION = 1.9.0 |
9 | INCLUDEPATH += $(OPIEDIR)/include | 9 | INCLUDEPATH += $(OPIEDIR)/include |
10 | DEPENDPATH += $(OPIEDIR)/include | 10 | DEPENDPATH += $(OPIEDIR)/include |
11 | 11 | ||
12 | !contains( platform, x11 ) { | 12 | !contains( platform, x11 ) { |
13 | include ( $(OPIEDIR)/include.pro ) | 13 | include ( $(OPIEDIR)/include.pro ) |
14 | } | 14 | } |
15 | 15 | ||
16 | contains( platform, x11 ) { | 16 | contains( platform, x11 ) { |
17 | LIBS = -L$(OPIEDIR)/lib -Wl,-rpath,$(OPIEDIR)/lib | 17 | LIBS = -L$(OPIEDIR)/lib -Wl,-rpath,$(OPIEDIR)/lib |
18 | } | 18 | } |