Add facility to read frames in RGB & RGBA format
[imageviewer.git] / view.cpp
bloba7d88999a81372a73c40ed0522bc8812dae7ac6f
1 /* ***** BEGIN LICENSE BLOCK *****
3 * $Id$
5 * The MIT License
7 * Copyright (c) 2008 BBC Research
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
27 * ***** END LICENSE BLOCK ***** */
29 #include "view.h"
31 #include "chromaResample.h"
32 #include "colourSpace.h"
34 #include <QtGui>
36 #include <math.h>
38 View::View(QWidget *parent) :
39 QGraphicsView(parent)
41 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
42 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
43 setDragMode(QGraphicsView::ScrollHandDrag);
45 zoom=0.1f;
46 rotation=0;
47 setupMatrix();
49 //YUV image display defaults
50 yuvDisplayMode = YUVdisplayColour;
51 yuvImageNativeBitDepth = 10;
52 yuvImageBitDepth = 10;
53 yuvImageRGBScaling = true;
54 yuvImageColourMatrix = ColourSpace::HDTVtoRGB;
56 inChroma = RawFrame::Cr422;
57 inFrame = new RawFrame(0, 0, inChroma);
58 frame444 = new RawFrame(0, 0, RawFrame::Cr444);
59 frameRGB = new RawFrame(0, 0, RawFrame::CrRGB);
61 scene = new QGraphicsScene;
62 setScene(scene);
64 QBrush brush = QBrush(QColor(32,32,32), Qt::SolidPattern);
65 setBackgroundBrush(brush);
68 void View::setupMatrix()
70 QMatrix matrix;
71 matrix.scale(zoom, zoom);
72 matrix.rotate(rotation);
74 setMatrix(matrix);
77 void View::showFrame(const RawFrame *f)
79 inFrame = (RawFrame *)f;
80 processImage(processResample);
83 //erase the input and display data to ensure that the displayed image
84 //is removed when opening the next frame
85 void View::invalidateFrame()
87 if(inFrame->chroma == RawFrame::CrRGB) {
88 inFrame->r.assign(0);
89 inFrame->g.assign(0);
90 inFrame->b.assign(0);
91 } else {
92 //this will result in a illegal green frame - ie something is wrong
93 inFrame->luma.assign(0);
94 inFrame->cb.assign(0);
95 inFrame->cr.assign(0);
99 void View::setZoom(float value)
101 if(value !=zoom) {
102 zoom = value;
103 setupMatrix();
104 emit(zoomChanged(zoom));
108 float View::getZoom(void)
110 //this assume sthat the transformation matrix m11 and m22 are the same, so that there is
111 //the same zoom ratio in x/y
112 return matrix().m11();
115 int View::getRotation(void)
117 return(rotation);
120 //change the mode used to display the YCbCr image (colour / Yonly / planar)
121 void View::setYUVdisplayMode(YUVdisplayMode mode){
122 if(mode != yuvDisplayMode) {
123 yuvDisplayMode = mode;
124 processImage(processColour);
128 //set the scaling used when converting from YCbCr to RGB, and reprocess the input
129 void View::setYUVRGBScaling(bool scaled) {
130 if(scaled != yuvImageRGBScaling) {
131 yuvImageRGBScaling = scaled;
132 processImage(processColour);
136 //the GUI can request a particular bit depth to use for display
137 void View::setYUVbitDepth(int bits)
139 if (bits!= yuvImageBitDepth) {
140 yuvImageBitDepth = bits;
141 processImage(processColour);
145 //the original file can have a native bit depth which may be 0 if unknown
146 void View::setYUVnativeBitDepth(int bits)
148 yuvImageNativeBitDepth = bits;
151 //set the colour matrix type and reprocess the image
152 void View::setYUVColourMatrix(ColourSpace::MatrixType type)
154 if(type != yuvImageColourMatrix) {
155 yuvImageColourMatrix = type;
156 processImage(processColour);
160 void View::processImage(const ProcessType type)
162 QImage displayImage(inFrame->luma.width(), inFrame->luma.height(), QImage::Format_RGB32);
164 QImage *planarY = NULL;
165 QImage *planarCb = NULL;
166 QImage *planarCr = NULL;
168 //these are zero sized as the process_ext methods are used on external data
169 ChromaResample chromaResample(0, 0, RawFrame::Cr444);
170 ColourSpace colourSpace(0, 0, RawFrame::CrRGB);
172 //default to the native bit depth of the image
173 int depth = yuvImageNativeBitDepth;
175 //if the bit depth cannot be determined, take the one from the GUI
176 if(depth == 0) depth = yuvImageBitDepth;
178 switch(type) {
180 case processResample:
181 //check that the 444 frame is the right size
182 if(frame444->luma.width() != inFrame->luma.width() ||
183 frame444->luma.height() != inFrame->luma.height()) {
184 delete frame444;
185 frame444 = new RawFrame(inFrame->luma.width(), inFrame->luma.height(), RawFrame::Cr444);
188 if(inFrame->chroma != RawFrame::CrRGB) {
189 chromaResample.process_ext(*inFrame, *frame444);
191 else {
192 //copying - inefficient but useful for later
193 frame444->r = inFrame->r;
194 frame444->g = inFrame->g;
195 frame444->b = inFrame->b;
198 case processColour:
200 //check that the RGB frame is the right size
201 if(frameRGB->luma.width() != frame444->luma.width() ||
202 frameRGB->luma.height() != frame444->luma.height()) {
203 delete frameRGB;
204 frameRGB = new RawFrame(inFrame->luma.width(), inFrame->luma.height(), RawFrame::CrRGB);
207 if(inFrame->chroma != RawFrame::CrRGB) {
208 bool YOnly = false;
209 if(inFrame->chroma == RawFrame::YOnly || yuvDisplayMode == YUVdisplayYOnly || yuvDisplayMode == YUVdisplayPlanar) YOnly = true;
210 colourSpace.process_ext(*frame444, *frameRGB, yuvImageColourMatrix, yuvImageRGBScaling, YOnly, depth);
212 else {
213 //more inefficient copying
214 frameRGB->r = frame444->r;
215 frameRGB->g = frame444->g;
216 frameRGB->b = frame444->b;
220 case processDisplay:
222 //copy the data to a QImage
223 for(int y=0; y<frameRGB->r.height(); y++) {
224 for(int x=0; x<frameRGB->r.width(); x++) {
226 int R = frameRGB->r[y][x] >> (depth - 8);
227 int G = frameRGB->g[y][x] >> (depth - 8);
228 int B = frameRGB->b[y][x] >> (depth - 8);
230 int pixel = 0xff << 24 | R << 16 | G << 8 | B;
232 displayImage.setPixel(x, y, pixel);
236 //generate seperate QImages for the chrominance if we are doing planar display
237 if(yuvDisplayMode == YUVdisplayPlanar && inFrame->chroma != RawFrame::YOnly) {
238 planarY = new QImage(inFrame->luma.width(), inFrame->luma.height(), QImage::Format_RGB32);
239 planarCb = new QImage(inFrame->cb.width(), inFrame->cr.height(), QImage::Format_RGB32);
240 planarCr = new QImage(inFrame->cr.width(), inFrame->cr.height(), QImage::Format_RGB32);
243 for(int y=0; y<inFrame->luma.height(); y++) {
244 for(int x=0; x<inFrame->luma.width(); x++) {
246 int C = inFrame->luma[y][x] >> (depth - 8);
247 int pixel = C << 16 | C << 8 | C;
248 planarY->setPixel(x, y, pixel);
252 //Cb
253 for(int y=0; y<inFrame->cb.height(); y++) {
254 for(int x=0; x<inFrame->cb.width(); x++) {
256 int C = inFrame->cb[y][x] >> (depth - 8);
257 int pixel = C << 16 | C << 8 | C;
258 planarCb->setPixel(x, y, pixel);
262 //Cr
263 for(int y=0; y<inFrame->cr.height(); y++) {
264 for(int x=0; x<inFrame->cr.width(); x++) {
266 int C = inFrame->cr[y][x] >> (depth - 8);
267 int pixel = C << 16 | C << 8 | C;
268 planarCr->setPixel(x, y, pixel);
276 case processScene:
278 //Put the image on the graphics scene, and set the graphics view
280 if(scene == NULL)
281 scene = new QGraphicsScene;
283 //remove all items from the scene
284 foreach (QGraphicsItem *item, scene->items()) {
285 scene->removeItem(item);
286 delete item;
289 //add the planar format chroma if required
290 if(yuvDisplayMode == YUVdisplayPlanar && inFrame->chroma != RawFrame::YOnly) {
292 //Y at the top
293 QGraphicsPixmapItem* yItem = scene->addPixmap(QPixmap::fromImage(*planarY));
294 yItem->setCursor(Qt::ArrowCursor);
296 //Cb to the left
297 QGraphicsPixmapItem *cbItem = scene->addPixmap(QPixmap::fromImage(*planarCb));
298 cbItem->setPos(QPoint(0, inFrame->luma.height()));
299 cbItem->setCursor(Qt::ArrowCursor);
301 //Cr to the right or below
302 if(inFrame->chroma == RawFrame::Cr444 || inFrame->chroma == RawFrame::CrRGB) {
303 //the resulting image is three times the original height
304 QGraphicsPixmapItem *crItem = scene->addPixmap(QPixmap::fromImage(*planarCr));
305 crItem->setPos(QPoint(0, inFrame->luma.height()*2));
306 crItem->setCursor(Qt::ArrowCursor);
307 scene->setSceneRect(0, 0, inFrame->luma.width(), inFrame->luma.height() * 3);
309 else {
310 //we can fit the two pieces of chroma next to each other
311 QGraphicsPixmapItem *crItem = scene->addPixmap(QPixmap::fromImage(*planarCr));
312 crItem->setPos(QPoint(inFrame->luma.width()/2, inFrame->luma.height()));
313 crItem->setCursor(Qt::ArrowCursor);
314 scene->setSceneRect(0, 0, inFrame->luma.width(), inFrame->luma.height() + inFrame->cb.height());
318 else {
319 //the scene only encompasses one colour or YOnly pixmap
320 QGraphicsPixmapItem* pixmapItem = scene->addPixmap(QPixmap::fromImage(displayImage));
321 pixmapItem->setCursor(Qt::ArrowCursor);
322 scene->setSceneRect(0, 0, inFrame->luma.width(), inFrame->luma.height());
325 setScene(scene);
330 //these will only have been used if displaying planar format
331 delete planarY;
332 delete planarCb;
333 delete planarCr;
336 //make the mouse wheel zoom in and out over the pointer position
337 void View::wheelEvent(QWheelEvent * wheelEvent)
339 float newZoom;
341 if(wheelEvent->delta()> 0)
342 newZoom = zoom * 2;
343 else
344 newZoom = zoom / 2;
346 //prevent extreme zooming out
347 if(newZoom < 0.01)
348 return;
350 //resize about the mouse position
351 setTransformationAnchor(AnchorUnderMouse);
352 setZoom(newZoom);
353 setTransformationAnchor(AnchorViewCenter);
356 //double click shows pixel info
357 void View::mouseDoubleClickEvent(QMouseEvent *e)
359 int mousex=e->x();
360 int mousey=e->y();
362 //this is very neat. It returns the click position on the displayed pixmap
363 //accounting for the zoom/rotation of the scene
364 QPoint viewPoint(mousex, mousey);
365 QPointF scenePoint = mapToScene(viewPoint);
367 int pixelx = (int)scenePoint.x();
368 int pixely = (int)scenePoint.y();
370 //it is possible to zoom out then double click outside the scene.
371 //do not try to use these coordinates to index our arrays of pixels
372 if(pixelx < 0 || pixelx> sceneRect().width()) return;
373 if(pixely < 0 || pixely> sceneRect().height()) return;
375 QString pixelInfo;
377 if(yuvDisplayMode != YUVdisplayPlanar) {
379 if(inFrame->chroma != RawFrame::CrRGB) {
380 //YUV and RGB colour display
381 pixelInfo = QString("Pixel at (%1,%2) RGB=[%3,%4,%5] YCbCr=[%6,%7,%8]")
382 .arg(pixelx) .arg(pixely)
383 .arg(frameRGB->r[pixely][pixelx]) .arg(frameRGB->g[pixely][pixelx]) .arg(frameRGB->b[pixely][pixelx])
384 .arg(frame444->luma[pixely][pixelx]) .arg(frame444->cb[pixely][pixelx]) .arg(frame444->cr[pixely][pixelx]);
386 else
388 pixelInfo = QString("Pixel at (%1,%2) RGB=[%3,%4,%5]")
389 .arg(pixelx) .arg(pixely)
390 .arg(frameRGB->r[pixely][pixelx]) .arg(frameRGB->g[pixely][pixelx]) .arg(frameRGB->b[pixely][pixelx]);
393 else {
395 if(inFrame->chroma == RawFrame::Cr444 || inFrame->chroma == RawFrame::CrRGB) {
397 //chroma stacked one above the other
399 if(pixely < inFrame->luma.height()) {
400 //luminance
401 pixelInfo = QString("Pixel at (%1,%2) RGB=[%3,%4,%5] Y=[%6]")
402 .arg(pixelx) .arg(pixely)
403 .arg(frameRGB->r[pixely][pixelx]) .arg(frameRGB->g[pixely][pixelx]) .arg(frameRGB->b[pixely][pixelx])
404 .arg(inFrame->luma[pixely][pixelx]);
406 else if(pixely < inFrame->luma.height() * 2) {
407 //Cb
408 int cby = pixely - inFrame->luma.height();
409 pixelInfo = QString("Cb Pixel at (%1,%2) Cb=[%3]")
410 .arg(pixelx) .arg(cby)
411 .arg(inFrame->cb[cby][pixelx]);
413 else {
414 //Cr
415 int cry = pixely - (inFrame->luma.height() * 2);
416 pixelInfo = QString("Cr Pixel at (%1,%2) Cr=[%3]")
417 .arg(pixelx) .arg(cry)
418 .arg(inFrame->cr[cry][pixelx]);
421 else {
422 //chroma side by side
423 if(pixely < inFrame->luma.height()) {
424 //luminance
425 pixelInfo = QString("Pixel at (%1,%2) RGB=[%3,%4,%5] Y=[%6]")
426 .arg(pixelx) .arg(pixely)
427 .arg(frameRGB->r[pixely][pixelx]) .arg(frameRGB->g[pixely][pixelx]) .arg(frameRGB->b[pixely][pixelx])
428 .arg(inFrame->luma[pixely][pixelx]);
430 else if (pixelx < (inFrame->luma.width() / 2)) {
431 //Cb
432 if(pixelx> inFrame->cb.width()) return; //cannot look in 4:1:1 where there is no data
433 int cby = pixely - inFrame->luma.height();
434 pixelInfo = QString("Cb Pixel at (%1,%2) Cb=[%3]")
435 .arg(pixelx) .arg(cby)
436 .arg(inFrame->cb[cby][pixelx]);
438 else {
439 //Cr
440 int crx = pixelx - (inFrame->luma.width() / 2);
441 if(crx> inFrame->cr.width()) return; //cannot look in 4:1:1 where there is no data
442 int cry = pixely - inFrame->luma.height();
443 pixelInfo = QString("Cr Pixel at (%1,%2) Cr=[%3]")
444 .arg(crx) .arg(cry)
445 .arg(inFrame->cr[cry][crx]);
450 emit(pixelInfoMessage(pixelInfo, 0));