Merge branch 'opengl'
12
camera.css
|
@ -1,12 +0,0 @@
|
|||
.black {
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
.errorbox {
|
||||
background: #dd0000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.controlbox {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
592
camera.glade
|
@ -1,592 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.37.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkAdjustment" id="control_adj">
|
||||
<property name="upper">100</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">10</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="window">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Camera</property>
|
||||
<property name="default-width">360</property>
|
||||
<property name="default-height">640</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="main_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="page_main">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkDrawingArea" id="preview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="black"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="control_box">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="control_name">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="label" translatable="yes">ISO</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="control_slider">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="adjustment">control_adj</property>
|
||||
<property name="round-digits">1</property>
|
||||
<property name="draw-value">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="control_auto">
|
||||
<property name="label" translatable="yes">Auto</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="margin-start">10</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="controlbox"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="controls_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child type="center">
|
||||
<object class="GtkButton" id="shutter">
|
||||
<property name="width-request">48</property>
|
||||
<property name="height-request">48</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resource">/org/postmarketos/Megapixels/shutter-button.svg</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">8</property>
|
||||
<property name="margin-bottom">8</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="settings">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resource">/org/postmarketos/Megapixels/settings-symbolic.svg</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="switch_camera">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resource">/org/postmarketos/Megapixels/switch-camera.svg</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">10</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">8</property>
|
||||
<property name="margin-bottom">8</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="open_directory">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resource">/org/postmarketos/Megapixels/folder-symbolic.svg</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="open_last">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="open_last_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="thumb_last">
|
||||
<property name="width-request">24</property>
|
||||
<property name="height-request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="process_spinner">
|
||||
<property name="width-request">24</property>
|
||||
<property name="height-request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">10</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">10</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="error_box">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="error_message">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">No error</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="error_close">
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<property name="always-show-image">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="errorbox"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">main</property>
|
||||
<property name="title" translatable="yes">page0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="page_settings">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="shadow-type">none</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="settings_back">
|
||||
<property name="label" translatable="yes">Back</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Settings aren't functional yet</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Photos</property>
|
||||
<style>
|
||||
<class name="heading"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkAlignment">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="left-padding">12</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Resolution</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Storage mode</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="store_vng">
|
||||
<property name="label" translatable="yes">Debayer with VNG (slowest)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="store_simple">
|
||||
<property name="label" translatable="yes">Debayer with linear interpolation</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
<property name="group">store_vng</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="store_raw">
|
||||
<property name="label" translatable="yes">Raw</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
<property name="group">store_vng</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<style>
|
||||
<class name="view"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">settings</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -11,7 +11,7 @@ capture-rate=10
|
|||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=720
|
||||
preview-rate=20
|
||||
preview-rate=30
|
||||
preview-fmt=BGGR8
|
||||
rotate=270
|
||||
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
|
||||
|
@ -29,11 +29,11 @@ driver=gc2145
|
|||
media-driver=sun6i-csi
|
||||
capture-width=1280
|
||||
capture-height=960
|
||||
capture-rate=30
|
||||
capture-rate=60
|
||||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=960
|
||||
preview-rate=30
|
||||
preview-rate=60
|
||||
preview-fmt=BGGR8
|
||||
rotate=90
|
||||
mirrored=true
|
||||
|
|
|
@ -11,7 +11,7 @@ capture-rate=10
|
|||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=720
|
||||
preview-rate=20
|
||||
preview-rate=30
|
||||
preview-fmt=BGGR8
|
||||
rotate=270
|
||||
colormatrix=1.384,-0.3203,-0.0124,-0.2728,1.049,0.1556,-0.0506,0.2577,0.8050
|
||||
|
@ -29,11 +29,11 @@ driver=gc2145
|
|||
media-driver=sun6i-csi
|
||||
capture-width=1280
|
||||
capture-height=960
|
||||
capture-rate=30
|
||||
capture-rate=60
|
||||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=960
|
||||
preview-rate=30
|
||||
preview-rate=60
|
||||
preview-fmt=BGGR8
|
||||
rotate=90
|
||||
mirrored=true
|
||||
|
|
|
@ -11,7 +11,7 @@ capture-rate=10
|
|||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=720
|
||||
preview-rate=20
|
||||
preview-rate=30
|
||||
preview-fmt=BGGR8
|
||||
rotate=270
|
||||
mirrored=false
|
||||
|
@ -30,11 +30,11 @@ driver=gc2145
|
|||
media-driver=sun6i-csi
|
||||
capture-width=1280
|
||||
capture-height=960
|
||||
capture-rate=30
|
||||
capture-rate=60
|
||||
capture-fmt=BGGR8
|
||||
preview-width=1280
|
||||
preview-height=960
|
||||
preview-rate=30
|
||||
preview-rate=60
|
||||
preview-fmt=BGGR8
|
||||
rotate=90
|
||||
mirrored=true
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform sampler2D texture;
|
||||
|
||||
varying vec2 uv;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = vec4(texture2D(texture, uv).rgb, 1);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
attribute vec2 vert;
|
||||
attribute vec2 tex_coord;
|
||||
|
||||
uniform mat3 transform;
|
||||
|
||||
varying vec2 uv;
|
||||
|
||||
void main() {
|
||||
uv = tex_coord;
|
||||
|
||||
gl_Position = vec4(transform * vec3(vert, 1), 1);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
.errorbox {
|
||||
background: #dd0000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.controlbox {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.button-overlay {
|
||||
opacity: 0.9;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="GtkWindow" id="window">
|
||||
<property name="can-focus">0</property>
|
||||
<property name="default-width">360</property>
|
||||
<property name="default-height">640</property>
|
||||
<property name="decorated">0</property>
|
||||
<property name="child">
|
||||
<object class="GtkStack" id="main_stack">
|
||||
<property name="can-focus">0</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">main</property>
|
||||
<property name="child">
|
||||
<object class="GtkOverlay">
|
||||
<property name="can-focus">0</property>
|
||||
<child>
|
||||
<object class="GtkGLArea" id="preview">
|
||||
<property name="vexpand">1</property>
|
||||
<property name="can-focus">0</property>
|
||||
<property name="use-es">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkBox" id="top-box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="halign">fill</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="can-focus">0</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
|
||||
<property name="vexpand">1</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="can-focus">0</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="iso-controls-button">
|
||||
<property name="valign">start</property>
|
||||
<property name="label">ISO</property>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="shutter-controls-button">
|
||||
<property name="valign">start</property>
|
||||
<property name="icon-name">shutter-symbolic</property>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="visible">0</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Error</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">dialog-warning-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="button-overlay"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkBox" id="bottom-box">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="halign">fill</property>
|
||||
<property name="valign">end</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">app.open-settings</property>
|
||||
<property name="icon-name">settings-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">app.switch-camera</property>
|
||||
<property name="icon-name">switch-camera-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="action-name">app.capture</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="pixel-size">60</property>
|
||||
<property name="can-focus">0</property>
|
||||
<property name="icon-name">shutter-button-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation" bind-source="bottom-box" bind-property="orientation"/>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">app.open-last</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="open_last_stack">
|
||||
<property name="can-focus">0</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="thumb_last">
|
||||
<property name="width-request">24</property>
|
||||
<property name="height-request">24</property>
|
||||
<property name="can-focus">0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="process_spinner">
|
||||
<property name="width-request">24</property>
|
||||
<property name="height-request">24</property>
|
||||
<property name="can-focus">0</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">app.open-photos</property>
|
||||
<property name="icon-name">folder-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="button-overlay"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">settings</property>
|
||||
<property name="title" translatable="yes">page1</property>
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkViewport">
|
||||
<property name="can-focus">0</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">0</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">0</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Back</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="action-name">app.close-settings</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="can-focus">0</property>
|
||||
<property name="label" translatable="yes">Settings aren't functional yet</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="GtkPopover" id="controls">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title">
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkScale" id="scale">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="draw-value">0</property>
|
||||
<property name="width-request">150</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="value-label">
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="auto-button">
|
||||
<property name="valign">center</property>
|
||||
<property name="label">auto</property>
|
||||
<property name="margin-start">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -0,0 +1,34 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform sampler2D texture;
|
||||
uniform mat3 color_matrix;
|
||||
|
||||
varying vec2 top_left_uv;
|
||||
varying vec2 top_right_uv;
|
||||
varying vec2 bottom_left_uv;
|
||||
varying vec2 bottom_right_uv;
|
||||
|
||||
void main() {
|
||||
// Note the coordinates for texture samples need to be a varying, as the
|
||||
// Mali-400 has this as a fast path allowing 32-bit floats. Otherwise
|
||||
// they end up as 16-bit floats and that's not accurate enough.
|
||||
vec4 samples = vec4(
|
||||
texture2D(texture, top_left_uv).r,
|
||||
texture2D(texture, top_right_uv).r,
|
||||
texture2D(texture, bottom_left_uv).r,
|
||||
texture2D(texture, bottom_right_uv).r);
|
||||
|
||||
// Assume BGGR for now. Currently this just takes 3 of the four samples
|
||||
// for each pixel, there's room here to do some better debayering.
|
||||
vec3 color = vec3(samples.w, (samples.y + samples.w) / 2.0, samples.x);
|
||||
|
||||
// Fast SRGB estimate. See https://mimosa-pudica.net/fast-gamma/
|
||||
vec3 srgb_color = (vec3(1.138) * inversesqrt(color) - vec3(0.138)) * color;
|
||||
|
||||
// Slow SRGB estimate
|
||||
// vec3 srgb_color = pow(color, vec3(1.0 / 2.2));
|
||||
|
||||
gl_FragColor = vec4(color_matrix * srgb_color, 1);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
attribute vec2 vert;
|
||||
attribute vec2 tex_coord;
|
||||
|
||||
uniform mat3 transform;
|
||||
uniform vec2 pixel_size;
|
||||
|
||||
varying vec2 top_left_uv;
|
||||
varying vec2 top_right_uv;
|
||||
varying vec2 bottom_left_uv;
|
||||
varying vec2 bottom_right_uv;
|
||||
|
||||
void main() {
|
||||
top_left_uv = tex_coord - pixel_size / 2.0;
|
||||
bottom_right_uv = tex_coord + pixel_size / 2.0;
|
||||
top_right_uv = vec2(top_left_uv.x, bottom_right_uv.y);
|
||||
bottom_left_uv = vec2(bottom_right_uv.x, top_left_uv.y);
|
||||
|
||||
gl_Position = vec4(transform * vec3(vert, 1), 1);
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,15 @@
|
|||
resources = gnome.compile_resources('megapixels-resources',
|
||||
'org.postmarketos.Megapixels.gresource.xml')
|
||||
|
||||
install_data(['org.postmarketos.Megapixels.desktop'],
|
||||
install_dir: get_option('datadir') / 'applications')
|
||||
|
||||
install_data(['org.postmarketos.Megapixels.metainfo.xml'],
|
||||
install_dir: get_option('datadir') / 'metainfo')
|
||||
|
||||
install_data('org.postmarketos.Megapixels.svg',
|
||||
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps'))
|
||||
|
||||
install_data(['postprocess.sh'],
|
||||
install_dir: get_option('datadir') / 'megapixels/',
|
||||
install_mode: 'rwxr-xr-x')
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/postmarketos/Megapixels">
|
||||
<file preprocess="xml-stripblanks">camera.ui</file>
|
||||
<file preprocess="xml-stripblanks">controls-popover.ui</file>
|
||||
<file>camera.css</file>
|
||||
<file preprocess="xml-stripblanks">switch-camera-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">shutter-button-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">folder-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">settings-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">shutter-symbolic.svg</file>
|
||||
<file>blit.vert</file>
|
||||
<file>blit.frag</file>
|
||||
<file>solid.vert</file>
|
||||
<file>solid.frag</file>
|
||||
<file>debayer.vert</file>
|
||||
<file>debayer.frag</file>
|
||||
</gresource>
|
||||
</gresources>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 8.4666665 8.466667"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
style="fill:#bebebe;fill-opacity:1;stroke-width:1"
|
||||
d="M 16 0 A 16.000001 16.000001 0 0 0 8.5097656 1.8730469 L 11.611328 13.443359 L 19.189453 0.3203125 A 16.000001 16.000001 0 0 0 16 0 z M 20.699219 0.70703125 L 14.705078 11.087891 L 29.330078 7.1679688 A 16.000001 16.000001 0 0 0 20.699219 0.70703125 z M 7.1679688 2.6699219 A 16.000001 16.000001 0 0 0 0.70703125 11.300781 L 11.087891 17.294922 L 7.1679688 2.6699219 z M 30.126953 8.5117188 L 18.558594 11.611328 L 31.679688 19.189453 A 16.000001 16.000001 0 0 0 32 16 A 16.000001 16.000001 0 0 0 30.126953 8.5117188 z M 0.3203125 12.810547 A 16.000001 16.000001 0 0 0 0 16 A 16.000001 16.000001 0 0 0 1.8730469 23.490234 L 13.443359 20.388672 L 0.3203125 12.810547 z M 20.912109 14.705078 L 24.832031 29.330078 A 16.000001 16.000001 0 0 0 31.292969 20.699219 L 20.912109 14.705078 z M 20.388672 18.558594 L 12.8125 31.679688 A 16.000001 16.000001 0 0 0 16 32 A 16.000001 16.000001 0 0 0 23.490234 30.126953 L 20.388672 18.558594 z M 17.294922 20.912109 L 2.6699219 24.832031 A 16.000001 16.000001 0 0 0 11.300781 31.292969 L 17.294922 20.912109 z "
|
||||
transform="scale(0.26458334)"
|
||||
id="path4537" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,9 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
attribute vec2 vert;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vert, 0, 1);
|
||||
}
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
792
main.c
|
@ -1,792 +0,0 @@
|
|||
#include "main.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/media.h>
|
||||
#include <linux/v4l2-subdev.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <asm/errno.h>
|
||||
#include <wordexp.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <locale.h>
|
||||
#include <zbar.h>
|
||||
#include "camera_config.h"
|
||||
#include "quickpreview.h"
|
||||
#include "io_pipeline.h"
|
||||
|
||||
enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
|
||||
|
||||
static bool camera_is_initialized = false;
|
||||
static const struct mp_camera_config *camera = NULL;
|
||||
static MPCameraMode mode;
|
||||
|
||||
static int preview_width = -1;
|
||||
static int preview_height = -1;
|
||||
|
||||
static bool gain_is_manual = false;
|
||||
static int gain;
|
||||
static int gain_max;
|
||||
|
||||
static bool exposure_is_manual = false;
|
||||
static int exposure;
|
||||
|
||||
static bool has_auto_focus_continuous;
|
||||
static bool has_auto_focus_start;
|
||||
|
||||
static cairo_surface_t *surface = NULL;
|
||||
static cairo_surface_t *status_surface = NULL;
|
||||
static char last_path[260] = "";
|
||||
|
||||
static MPZBarScanResult *zbar_result = NULL;
|
||||
|
||||
static int burst_length = 3;
|
||||
|
||||
static enum user_control current_control;
|
||||
|
||||
// Widgets
|
||||
GtkWidget *preview;
|
||||
GtkWidget *error_box;
|
||||
GtkWidget *error_message;
|
||||
GtkWidget *main_stack;
|
||||
GtkWidget *open_last_stack;
|
||||
GtkWidget *thumb_last;
|
||||
GtkWidget *process_spinner;
|
||||
GtkWidget *control_box;
|
||||
GtkWidget *control_name;
|
||||
GtkAdjustment *control_slider;
|
||||
GtkWidget *control_auto;
|
||||
|
||||
int
|
||||
remap(int value, int input_min, int input_max, int output_min, int output_max)
|
||||
{
|
||||
const long long factor = 1000000000;
|
||||
long long output_spread = output_max - output_min;
|
||||
long long input_spread = input_max - input_min;
|
||||
|
||||
long long zero_value = value - input_min;
|
||||
zero_value *= factor;
|
||||
long long percentage = zero_value / input_spread;
|
||||
|
||||
long long zero_output = percentage * output_spread / factor;
|
||||
|
||||
long long result = output_min + zero_output;
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
static void
|
||||
update_io_pipeline()
|
||||
{
|
||||
struct mp_io_pipeline_state io_state = {
|
||||
.camera = camera,
|
||||
.burst_length = burst_length,
|
||||
.preview_width = preview_width,
|
||||
.preview_height = preview_height,
|
||||
.gain_is_manual = gain_is_manual,
|
||||
.gain = gain,
|
||||
.exposure_is_manual = exposure_is_manual,
|
||||
.exposure = exposure,
|
||||
};
|
||||
mp_io_pipeline_update_state(&io_state);
|
||||
}
|
||||
|
||||
static bool
|
||||
update_state(const struct mp_main_state *state)
|
||||
{
|
||||
if (!camera_is_initialized) {
|
||||
camera_is_initialized = true;
|
||||
}
|
||||
|
||||
if (camera == state->camera) {
|
||||
mode = state->mode;
|
||||
|
||||
if (!gain_is_manual) {
|
||||
gain = state->gain;
|
||||
}
|
||||
gain_max = state->gain_max;
|
||||
|
||||
if (!exposure_is_manual) {
|
||||
exposure = state->exposure;
|
||||
}
|
||||
|
||||
has_auto_focus_continuous = state->has_auto_focus_continuous;
|
||||
has_auto_focus_start = state->has_auto_focus_start;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_update_state(const struct mp_main_state *state)
|
||||
{
|
||||
struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
|
||||
*state_copy = *state;
|
||||
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)update_state, state_copy, free);
|
||||
}
|
||||
|
||||
static bool set_zbar_result(MPZBarScanResult *result)
|
||||
{
|
||||
if (zbar_result) {
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
free(zbar_result->codes[i].data);
|
||||
}
|
||||
|
||||
free(zbar_result);
|
||||
}
|
||||
|
||||
zbar_result = result;
|
||||
gtk_widget_queue_draw(preview);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mp_main_set_zbar_result(MPZBarScanResult *result)
|
||||
{
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)set_zbar_result, result, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
set_preview(cairo_surface_t *image)
|
||||
{
|
||||
if (surface) {
|
||||
cairo_surface_destroy(surface);
|
||||
}
|
||||
surface = image;
|
||||
gtk_widget_queue_draw(preview);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_set_preview(cairo_surface_t *image)
|
||||
{
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)set_preview, image, NULL);
|
||||
}
|
||||
|
||||
static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
|
||||
int src_width, int src_height)
|
||||
{
|
||||
cairo_translate(cr, dst_width / 2, dst_height / 2);
|
||||
|
||||
double scale = MIN(dst_width / (double)src_width, dst_height / (double)src_height);
|
||||
cairo_scale(cr, scale, scale);
|
||||
|
||||
cairo_translate(cr, -src_width / 2, -src_height / 2);
|
||||
}
|
||||
|
||||
void
|
||||
draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
|
||||
cairo_surface_t *surface)
|
||||
{
|
||||
cairo_save(cr);
|
||||
|
||||
int width = cairo_image_surface_get_width(surface);
|
||||
int height = cairo_image_surface_get_height(surface);
|
||||
transform_centered(cr, dst_width, dst_height, width, height);
|
||||
|
||||
cairo_set_source_surface(cr, surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_restore(cr);
|
||||
}
|
||||
|
||||
struct capture_completed_args {
|
||||
cairo_surface_t *thumb;
|
||||
char *fname;
|
||||
};
|
||||
|
||||
static bool
|
||||
capture_completed(struct capture_completed_args *args)
|
||||
{
|
||||
strncpy(last_path, args->fname, 259);
|
||||
|
||||
gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb);
|
||||
|
||||
gtk_spinner_stop(GTK_SPINNER(process_spinner));
|
||||
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
|
||||
|
||||
cairo_surface_destroy(args->thumb);
|
||||
g_free(args->fname);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_capture_completed(cairo_surface_t *thumb, const char *fname)
|
||||
{
|
||||
struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
|
||||
args->thumb = thumb;
|
||||
args->fname = g_strdup(fname);
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)capture_completed, args, free);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_controls()
|
||||
{
|
||||
cairo_t *cr;
|
||||
char iso[6];
|
||||
int temp;
|
||||
char shutterangle[6];
|
||||
|
||||
if (exposure_is_manual) {
|
||||
temp = (int)((float)exposure / (float)camera->capture_mode.height *
|
||||
360);
|
||||
sprintf(shutterangle, "%d\u00b0", temp);
|
||||
} else {
|
||||
sprintf(shutterangle, "auto");
|
||||
}
|
||||
|
||||
if (gain_is_manual) {
|
||||
temp = remap(gain - 1, 0, gain_max, camera->iso_min,
|
||||
camera->iso_max);
|
||||
sprintf(iso, "%d", temp);
|
||||
} else {
|
||||
sprintf(iso, "auto");
|
||||
}
|
||||
|
||||
if (status_surface)
|
||||
cairo_surface_destroy(status_surface);
|
||||
|
||||
// Make a service to show status of controls, 32px high
|
||||
if (gtk_widget_get_window(preview) == NULL) {
|
||||
return;
|
||||
}
|
||||
status_surface =
|
||||
gdk_window_create_similar_surface(gtk_widget_get_window(preview),
|
||||
CAIRO_CONTENT_COLOR_ALPHA,
|
||||
preview_width, 32);
|
||||
|
||||
cr = cairo_create(status_surface);
|
||||
cairo_set_source_rgba(cr, 0, 0, 0, 0.0);
|
||||
cairo_paint(cr);
|
||||
|
||||
// Draw the outlines for the headings
|
||||
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
|
||||
CAIRO_FONT_WEIGHT_BOLD);
|
||||
cairo_set_font_size(cr, 9);
|
||||
cairo_set_source_rgba(cr, 0, 0, 0, 1);
|
||||
|
||||
cairo_move_to(cr, 16, 16);
|
||||
cairo_text_path(cr, "ISO");
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_move_to(cr, 60, 16);
|
||||
cairo_text_path(cr, "Shutter");
|
||||
cairo_stroke(cr);
|
||||
|
||||
// Draw the fill for the headings
|
||||
cairo_set_source_rgba(cr, 1, 1, 1, 1);
|
||||
cairo_move_to(cr, 16, 16);
|
||||
cairo_show_text(cr, "ISO");
|
||||
cairo_move_to(cr, 60, 16);
|
||||
cairo_show_text(cr, "Shutter");
|
||||
|
||||
// Draw the outlines for the values
|
||||
cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL,
|
||||
CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, 11);
|
||||
cairo_set_source_rgba(cr, 0, 0, 0, 1);
|
||||
|
||||
cairo_move_to(cr, 16, 26);
|
||||
cairo_text_path(cr, iso);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_move_to(cr, 60, 26);
|
||||
cairo_text_path(cr, shutterangle);
|
||||
cairo_stroke(cr);
|
||||
|
||||
// Draw the fill for the values
|
||||
cairo_set_source_rgba(cr, 1, 1, 1, 1);
|
||||
cairo_move_to(cr, 16, 26);
|
||||
cairo_show_text(cr, iso);
|
||||
cairo_move_to(cr, 60, 26);
|
||||
cairo_show_text(cr, shutterangle);
|
||||
|
||||
cairo_destroy(cr);
|
||||
|
||||
gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preview_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
|
||||
{
|
||||
if (!camera_is_initialized) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Clear preview area with black
|
||||
cairo_paint(cr);
|
||||
|
||||
if (surface) {
|
||||
// Draw camera preview
|
||||
cairo_save(cr);
|
||||
|
||||
int width = cairo_image_surface_get_width(surface);
|
||||
int height = cairo_image_surface_get_height(surface);
|
||||
transform_centered(cr, preview_width, preview_height, width, height);
|
||||
|
||||
cairo_set_source_surface(cr, surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
|
||||
// Draw zbar image
|
||||
if (zbar_result) {
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
MPZBarCode *code = &zbar_result->codes[i];
|
||||
|
||||
cairo_set_line_width(cr, 3.0);
|
||||
cairo_set_source_rgba(cr, 0, 0.5, 1, 0.75);
|
||||
cairo_new_path(cr);
|
||||
cairo_move_to(cr, code->bounds_x[0], code->bounds_y[0]);
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
cairo_line_to(cr, code->bounds_x[i], code->bounds_y[i]);
|
||||
}
|
||||
cairo_close_path(cr);
|
||||
cairo_stroke(cr);
|
||||
|
||||
cairo_save(cr);
|
||||
cairo_translate(cr, code->bounds_x[0], code->bounds_y[0]);
|
||||
cairo_show_text(cr, code->data);
|
||||
cairo_restore(cr);
|
||||
}
|
||||
}
|
||||
|
||||
cairo_restore(cr);
|
||||
}
|
||||
|
||||
// Draw control overlay
|
||||
cairo_set_source_surface(cr, status_surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preview_configure(GtkWidget *widget, GdkEventConfigure *event)
|
||||
{
|
||||
int new_preview_width = gtk_widget_get_allocated_width(widget);
|
||||
int new_preview_height = gtk_widget_get_allocated_height(widget);
|
||||
|
||||
if (preview_width != new_preview_width ||
|
||||
preview_height != new_preview_height) {
|
||||
preview_width = new_preview_width;
|
||||
preview_height = new_preview_height;
|
||||
update_io_pipeline();
|
||||
}
|
||||
|
||||
draw_controls();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
on_open_last_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
char uri[275];
|
||||
GError *error = NULL;
|
||||
|
||||
if (strlen(last_path) == 0) {
|
||||
return;
|
||||
}
|
||||
sprintf(uri, "file://%s", last_path);
|
||||
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
|
||||
g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_open_directory_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
char uri[270];
|
||||
GError *error = NULL;
|
||||
sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
|
||||
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
|
||||
g_printerr("Could not launch image viewer: %s\n", error->message);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_shutter_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
gtk_spinner_start(GTK_SPINNER(process_spinner));
|
||||
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
|
||||
mp_io_pipeline_capture();
|
||||
}
|
||||
|
||||
void
|
||||
on_capture_shortcut(void)
|
||||
{
|
||||
on_shutter_clicked(NULL, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
|
||||
{
|
||||
bool right = false, left = false, top = false, bottom = false;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (x <= bounds_x[i])
|
||||
left = true;
|
||||
if (x >= bounds_x[i])
|
||||
right = true;
|
||||
if (y <= bounds_y[i])
|
||||
top = true;
|
||||
if (y >= bounds_y[i])
|
||||
bottom = true;
|
||||
}
|
||||
|
||||
return right && left && top && bottom;
|
||||
}
|
||||
|
||||
static void
|
||||
on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
|
||||
bool data_is_url = g_uri_is_valid(
|
||||
code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
|
||||
|
||||
char* data = strdup(code->data);
|
||||
|
||||
if (data_is_url) {
|
||||
dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(gtk_widget_get_toplevel(widget)),
|
||||
flags,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_NONE,
|
||||
"Found a URL '%s' encoded in a %s code.",
|
||||
code->data,
|
||||
code->type);
|
||||
gtk_dialog_add_buttons(
|
||||
GTK_DIALOG(dialog),
|
||||
"_Open URL",
|
||||
GTK_RESPONSE_YES,
|
||||
NULL);
|
||||
} else {
|
||||
dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(gtk_widget_get_toplevel(widget)),
|
||||
flags,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_NONE,
|
||||
"Found '%s' encoded in a %s code.",
|
||||
code->data,
|
||||
code->type);
|
||||
}
|
||||
gtk_dialog_add_buttons(
|
||||
GTK_DIALOG(dialog),
|
||||
"_Copy",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
NULL);
|
||||
|
||||
int result = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
|
||||
GError *error = NULL;
|
||||
switch (result) {
|
||||
case GTK_RESPONSE_YES:
|
||||
if (!g_app_info_launch_default_for_uri(data,
|
||||
NULL, &error)) {
|
||||
g_printerr("Could not launch application: %s\n",
|
||||
error->message);
|
||||
}
|
||||
case GTK_RESPONSE_ACCEPT:
|
||||
gtk_clipboard_set_text(
|
||||
gtk_clipboard_get(GDK_SELECTION_PRIMARY),
|
||||
data, -1);
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
break;
|
||||
default:
|
||||
g_printerr("Wrong dialog result: %d\n", result);
|
||||
}
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
|
||||
{
|
||||
if (event->type != GDK_BUTTON_PRESS)
|
||||
return;
|
||||
|
||||
// Handle taps on the controls
|
||||
if (event->y < 32) {
|
||||
if (gtk_widget_is_visible(control_box)) {
|
||||
gtk_widget_hide(control_box);
|
||||
return;
|
||||
} else {
|
||||
gtk_widget_show(control_box);
|
||||
}
|
||||
|
||||
if (event->x < 60) {
|
||||
// ISO
|
||||
current_control = USER_CONTROL_ISO;
|
||||
gtk_label_set_text(GTK_LABEL(control_name), "ISO");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
|
||||
!gain_is_manual);
|
||||
gtk_adjustment_set_lower(control_slider, 0.0);
|
||||
gtk_adjustment_set_upper(control_slider, (float)gain_max);
|
||||
gtk_adjustment_set_value(control_slider, (double)gain);
|
||||
|
||||
} else if (event->x > 60 && event->x < 120) {
|
||||
// Shutter angle
|
||||
current_control = USER_CONTROL_SHUTTER;
|
||||
gtk_label_set_text(GTK_LABEL(control_name), "Shutter");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto),
|
||||
!exposure_is_manual);
|
||||
gtk_adjustment_set_lower(control_slider, 1.0);
|
||||
gtk_adjustment_set_upper(control_slider, 360.0);
|
||||
gtk_adjustment_set_value(control_slider, (double)exposure);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Tapped zbar result
|
||||
if (zbar_result) {
|
||||
// Transform the event coordinates to the image
|
||||
int width = cairo_image_surface_get_width(surface);
|
||||
int height = cairo_image_surface_get_height(surface);
|
||||
double scale = MIN(preview_width / (double)width, preview_height / (double)height);
|
||||
int x = (event->x - preview_width / 2) / scale + width / 2;
|
||||
int y = (event->y - preview_height / 2) / scale + height / 2;
|
||||
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
MPZBarCode *code = &zbar_result->codes[i];
|
||||
|
||||
if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) {
|
||||
on_zbar_code_tapped(widget, code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tapped preview image itself, try focussing
|
||||
if (has_auto_focus_start) {
|
||||
mp_io_pipeline_focus();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_error_close_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
gtk_widget_hide(error_box);
|
||||
}
|
||||
|
||||
void
|
||||
on_camera_switch_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
size_t next_index = camera->index + 1;
|
||||
const struct mp_camera_config *next_camera =
|
||||
mp_get_camera_config(next_index);
|
||||
|
||||
if (!next_camera) {
|
||||
next_index = 0;
|
||||
next_camera = mp_get_camera_config(next_index);
|
||||
}
|
||||
|
||||
camera = next_camera;
|
||||
update_io_pipeline();
|
||||
}
|
||||
|
||||
void
|
||||
on_settings_btn_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
|
||||
}
|
||||
|
||||
void
|
||||
on_back_clicked(GtkWidget *widget, gpointer user_data)
|
||||
{
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
|
||||
}
|
||||
|
||||
void
|
||||
on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data)
|
||||
{
|
||||
bool is_manual = gtk_toggle_button_get_active(widget) ? false : true;
|
||||
bool has_changed;
|
||||
|
||||
switch (current_control) {
|
||||
case USER_CONTROL_ISO:
|
||||
if (gain_is_manual != is_manual) {
|
||||
gain_is_manual = is_manual;
|
||||
has_changed = true;
|
||||
}
|
||||
break;
|
||||
case USER_CONTROL_SHUTTER:
|
||||
if (exposure_is_manual != is_manual) {
|
||||
exposure_is_manual = is_manual;
|
||||
has_changed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_changed) {
|
||||
// The slider might have been moved while Auto mode is active. When entering
|
||||
// Manual mode, first read the slider value to sync with those changes.
|
||||
double value = gtk_adjustment_get_value(control_slider);
|
||||
switch (current_control) {
|
||||
case USER_CONTROL_ISO:
|
||||
if (value != gain) {
|
||||
gain = (int)value;
|
||||
}
|
||||
break;
|
||||
case USER_CONTROL_SHUTTER: {
|
||||
// So far all sensors use exposure time in number of sensor rows
|
||||
int new_exposure =
|
||||
(int)(value / 360.0 * camera->capture_mode.height);
|
||||
if (new_exposure != exposure) {
|
||||
exposure = new_exposure;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
update_io_pipeline();
|
||||
draw_controls();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_control_slider_changed(GtkAdjustment *widget, gpointer user_data)
|
||||
{
|
||||
double value = gtk_adjustment_get_value(widget);
|
||||
|
||||
bool has_changed = false;
|
||||
switch (current_control) {
|
||||
case USER_CONTROL_ISO:
|
||||
if (value != gain) {
|
||||
gain = (int)value;
|
||||
has_changed = true;
|
||||
}
|
||||
break;
|
||||
case USER_CONTROL_SHUTTER: {
|
||||
// So far all sensors use exposure time in number of sensor rows
|
||||
int new_exposure =
|
||||
(int)(value / 360.0 * camera->capture_mode.height);
|
||||
if (new_exposure != exposure) {
|
||||
exposure = new_exposure;
|
||||
has_changed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_changed) {
|
||||
update_io_pipeline();
|
||||
draw_controls();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
if (!mp_load_config())
|
||||
return 1;
|
||||
|
||||
setenv("LC_NUMERIC", "C", 1);
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
|
||||
TRUE, NULL);
|
||||
GtkBuilder *builder = gtk_builder_new_from_resource(
|
||||
"/org/postmarketos/Megapixels/camera.glade");
|
||||
|
||||
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
|
||||
GtkWidget *shutter = GTK_WIDGET(gtk_builder_get_object(builder, "shutter"));
|
||||
GtkWidget *switch_btn =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera"));
|
||||
GtkWidget *settings_btn =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "settings"));
|
||||
GtkWidget *settings_back =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "settings_back"));
|
||||
GtkWidget *error_close =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "error_close"));
|
||||
GtkWidget *open_last =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "open_last"));
|
||||
GtkWidget *open_directory =
|
||||
GTK_WIDGET(gtk_builder_get_object(builder, "open_directory"));
|
||||
preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
|
||||
error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box"));
|
||||
error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message"));
|
||||
main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
|
||||
open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
|
||||
thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
|
||||
process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
|
||||
control_box = GTK_WIDGET(gtk_builder_get_object(builder, "control_box"));
|
||||
control_name = GTK_WIDGET(gtk_builder_get_object(builder, "control_name"));
|
||||
control_slider =
|
||||
GTK_ADJUSTMENT(gtk_builder_get_object(builder, "control_adj"));
|
||||
control_auto = GTK_WIDGET(gtk_builder_get_object(builder, "control_auto"));
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
||||
g_signal_connect(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL);
|
||||
g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked),
|
||||
NULL);
|
||||
g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked),
|
||||
NULL);
|
||||
g_signal_connect(settings_btn, "clicked",
|
||||
G_CALLBACK(on_settings_btn_clicked), NULL);
|
||||
g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked),
|
||||
NULL);
|
||||
g_signal_connect(open_last, "clicked", G_CALLBACK(on_open_last_clicked),
|
||||
NULL);
|
||||
g_signal_connect(open_directory, "clicked",
|
||||
G_CALLBACK(on_open_directory_clicked), NULL);
|
||||
g_signal_connect(preview, "draw", G_CALLBACK(preview_draw), NULL);
|
||||
g_signal_connect(preview, "configure-event", G_CALLBACK(preview_configure),
|
||||
NULL);
|
||||
gtk_widget_set_events(preview, gtk_widget_get_events(preview) |
|
||||
GDK_BUTTON_PRESS_MASK |
|
||||
GDK_POINTER_MOTION_MASK);
|
||||
g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap),
|
||||
NULL);
|
||||
g_signal_connect(control_auto, "toggled",
|
||||
G_CALLBACK(on_control_auto_toggled), NULL);
|
||||
g_signal_connect(control_slider, "value-changed",
|
||||
G_CALLBACK(on_control_slider_changed), NULL);
|
||||
|
||||
GtkCssProvider *provider = gtk_css_provider_new();
|
||||
if (access("camera.css", F_OK) != -1) {
|
||||
gtk_css_provider_load_from_path(provider, "camera.css", NULL);
|
||||
} else {
|
||||
gtk_css_provider_load_from_resource(
|
||||
provider, "/org/postmarketos/Megapixels/camera.css");
|
||||
}
|
||||
GtkStyleContext *context = gtk_widget_get_style_context(error_box);
|
||||
gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
context = gtk_widget_get_style_context(control_box);
|
||||
gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
|
||||
GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0);
|
||||
|
||||
GtkAccelGroup* accel_group = gtk_accel_group_new();
|
||||
gtk_accel_group_connect(accel_group,
|
||||
GDK_KEY_space,
|
||||
0,
|
||||
0,
|
||||
capture_shortcut);
|
||||
|
||||
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
|
||||
|
||||
mp_io_pipeline_start();
|
||||
|
||||
camera = mp_get_camera_config(0);
|
||||
update_io_pipeline();
|
||||
|
||||
gtk_widget_show(window);
|
||||
gtk_main();
|
||||
|
||||
mp_io_pipeline_stop();
|
||||
|
||||
return 0;
|
||||
}
|
73
meson.build
|
@ -1,14 +1,17 @@
|
|||
project('megapixels', 'c')
|
||||
|
||||
gnome = import('gnome')
|
||||
gtkdep = dependency('gtk+-3.0')
|
||||
gtkdep = dependency('gtk4')
|
||||
tiff = dependency('libtiff-4')
|
||||
zbar = dependency('zbar')
|
||||
threads = dependency('threads')
|
||||
# gl = dependency('gl')
|
||||
epoxy = dependency('epoxy')
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
libm = cc.find_library('m', required: false)
|
||||
|
||||
resources = gnome.compile_resources('megapixels-resources', 'org.postmarketos.Megapixels.gresource.xml')
|
||||
subdir('data')
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
|
||||
|
@ -22,38 +25,33 @@ if get_option('buildtype') == 'debug'
|
|||
add_global_arguments('-DDEBUG', language: 'c')
|
||||
endif
|
||||
|
||||
# Workaround for libtiff having ABI changes but not changing the internal version number
|
||||
# Workaround for libtiff having ABI changes but not changing the internal
|
||||
# version number
|
||||
if get_option('tiffcfapattern')
|
||||
add_global_arguments('-DLIBTIFF_CFA_PATTERN', language: 'c')
|
||||
endif
|
||||
|
||||
executable('megapixels',
|
||||
'main.c',
|
||||
'ini.c',
|
||||
'quickpreview.c',
|
||||
'camera.c',
|
||||
'device.c',
|
||||
'pipeline.c',
|
||||
'camera_config.c',
|
||||
'io_pipeline.c',
|
||||
'process_pipeline.c',
|
||||
'zbar_pipeline.c',
|
||||
'matrix.c',
|
||||
'src/main.c',
|
||||
'src/ini.c',
|
||||
'src/gles2_debayer.c',
|
||||
'src/gl_util.c',
|
||||
'src/camera.c',
|
||||
'src/device.c',
|
||||
'src/pipeline.c',
|
||||
'src/camera_config.c',
|
||||
'src/io_pipeline.c',
|
||||
'src/process_pipeline.c',
|
||||
'src/zbar_pipeline.c',
|
||||
'src/matrix.c',
|
||||
resources,
|
||||
dependencies : [gtkdep, libm, tiff, zbar, threads],
|
||||
install : true)
|
||||
include_directories: 'src/',
|
||||
dependencies: [gtkdep, libm, tiff, zbar, threads, epoxy],
|
||||
install: true,
|
||||
link_args: '-Wl,-ldl')
|
||||
|
||||
install_data(['data/org.postmarketos.Megapixels.desktop'],
|
||||
install_dir : get_option('datadir') / 'applications')
|
||||
|
||||
install_data(['data/org.postmarketos.Megapixels.metainfo.xml'],
|
||||
install_dir : get_option('datadir') / 'metainfo')
|
||||
|
||||
install_data('data/org.postmarketos.Megapixels.svg',
|
||||
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')
|
||||
)
|
||||
|
||||
install_data([
|
||||
install_data(
|
||||
[
|
||||
'config/pine64,pinephone-1.0.ini',
|
||||
'config/pine64,pinephone-1.1.ini',
|
||||
'config/pine64,pinephone-1.2.ini',
|
||||
|
@ -61,13 +59,18 @@ install_data([
|
|||
],
|
||||
install_dir: get_option('datadir') / 'megapixels/config/')
|
||||
|
||||
install_data(['postprocess.sh'],
|
||||
install_dir : get_option('datadir') / 'megapixels/',
|
||||
install_mode: 'rwxr-xr-x')
|
||||
|
||||
# Tools
|
||||
executable('megapixels-list-devices', 'tools/list_devices.c', 'device.c', dependencies: [gtkdep], install: true)
|
||||
executable('megapixels-camera-test', 'tools/camera_test.c', 'camera.c', 'device.c', dependencies: [gtkdep], install: true)
|
||||
executable('megapixels-list-devices',
|
||||
'tools/list_devices.c',
|
||||
'src/device.c',
|
||||
include_directories: 'src/',
|
||||
dependencies: [gtkdep],
|
||||
install: true)
|
||||
|
||||
test_quickpreview = executable('test_quickpreview', 'tests/test_quickpreview.c', 'quickpreview.c', 'camera.c', dependencies: [gtkdep])
|
||||
test('quickpreview', test_quickpreview)
|
||||
executable('megapixels-camera-test',
|
||||
'tools/camera_test.c',
|
||||
'src/camera.c',
|
||||
'src/device.c',
|
||||
include_directories: 'src/',
|
||||
dependencies: [gtkdep],
|
||||
install: true)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/postmarketos/Megapixels">
|
||||
<file>camera.glade</file>
|
||||
<file>camera.css</file>
|
||||
<file>switch-camera.svg</file>
|
||||
<file>shutter-button.svg</file>
|
||||
<file>folder-symbolic.svg</file>
|
||||
<file>settings-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
351
quickpreview.c
|
@ -1,351 +0,0 @@
|
|||
/*
|
||||
* Fast but bad debayer method that scales and rotates by skipping source
|
||||
* pixels and doesn't interpolate any values at all
|
||||
*/
|
||||
|
||||
#include "quickpreview.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Linear -> sRGB lookup table */
|
||||
static const int srgb[] = {
|
||||
0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70,
|
||||
73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100,
|
||||
102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118,
|
||||
120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134,
|
||||
135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147,
|
||||
148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159,
|
||||
160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170,
|
||||
171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180,
|
||||
181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189,
|
||||
190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198,
|
||||
199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206,
|
||||
207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214,
|
||||
215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222,
|
||||
222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229,
|
||||
230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236,
|
||||
237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243,
|
||||
243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249,
|
||||
250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255
|
||||
};
|
||||
|
||||
static inline uint32_t
|
||||
pack_rgb(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
convert_yuv_to_srgb(uint8_t y, uint8_t u, uint8_t v)
|
||||
{
|
||||
uint32_t r = 1.164f * y + 1.596f * (v - 128);
|
||||
uint32_t g = 1.164f * y - 0.813f * (v - 128) - 0.391f * (u - 128);
|
||||
uint32_t b = 1.164f * y + 2.018f * (u - 128);
|
||||
return pack_rgb(r, g, b);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
apply_colormatrix(uint32_t color, const float *colormatrix)
|
||||
{
|
||||
if (!colormatrix) {
|
||||
return color;
|
||||
}
|
||||
|
||||
uint32_t r = (color >> 16) * colormatrix[0] +
|
||||
((color >> 8) & 0xFF) * colormatrix[1] +
|
||||
(color & 0xFF) * colormatrix[2];
|
||||
uint32_t g = (color >> 16) * colormatrix[3] +
|
||||
((color >> 8) & 0xFF) * colormatrix[4] +
|
||||
(color & 0xFF) * colormatrix[5];
|
||||
uint32_t b = (color >> 16) * colormatrix[6] +
|
||||
((color >> 8) & 0xFF) * colormatrix[7] +
|
||||
(color & 0xFF) * colormatrix[8];
|
||||
|
||||
// Clip colors
|
||||
if (r > 0xFF)
|
||||
r = 0xFF;
|
||||
if (g > 0xFF)
|
||||
g = 0xFF;
|
||||
if (b > 0xFF)
|
||||
b = 0xFF;
|
||||
return pack_rgb(r, g, b);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
coord_map(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int rotation,
|
||||
bool mirrored)
|
||||
{
|
||||
uint32_t x_r, y_r;
|
||||
if (rotation == 0) {
|
||||
x_r = x;
|
||||
y_r = y;
|
||||
} else if (rotation == 90) {
|
||||
x_r = y;
|
||||
y_r = height - x - 1;
|
||||
} else if (rotation == 270) {
|
||||
x_r = width - y - 1;
|
||||
y_r = x;
|
||||
} else {
|
||||
x_r = width - x - 1;
|
||||
y_r = height - y - 1;
|
||||
}
|
||||
|
||||
if (mirrored) {
|
||||
x_r = width - x_r - 1;
|
||||
}
|
||||
|
||||
uint32_t index = y_r * width + x_r;
|
||||
#ifdef DEBUG
|
||||
assert(index < width * height);
|
||||
#endif
|
||||
return index;
|
||||
}
|
||||
|
||||
static void
|
||||
quick_preview_rggb8(uint32_t *dst, const uint32_t dst_width,
|
||||
const uint32_t dst_height, const uint8_t *src,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const MPPixelFormat format, const uint32_t rotation,
|
||||
const bool mirrored, const float *colormatrix,
|
||||
const uint8_t blacklevel, const uint32_t skip)
|
||||
{
|
||||
uint32_t src_y = 0, dst_y = 0;
|
||||
while (src_y < src_height) {
|
||||
uint32_t src_x = 0, dst_x = 0;
|
||||
while (src_x < src_width) {
|
||||
uint32_t src_i = src_y * src_width + src_x;
|
||||
|
||||
uint8_t b0 = srgb[src[src_i] - blacklevel];
|
||||
uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
|
||||
uint8_t b2 = srgb[src[src_i + src_width + 1] - blacklevel];
|
||||
|
||||
uint32_t color;
|
||||
switch (format) {
|
||||
case MP_PIXEL_FMT_BGGR8:
|
||||
color = pack_rgb(b2, b1, b0);
|
||||
break;
|
||||
case MP_PIXEL_FMT_GBRG8:
|
||||
color = pack_rgb(b2, b0, b1);
|
||||
break;
|
||||
case MP_PIXEL_FMT_GRBG8:
|
||||
color = pack_rgb(b1, b0, b2);
|
||||
break;
|
||||
case MP_PIXEL_FMT_RGGB8:
|
||||
color = pack_rgb(b0, b1, b2);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
color = apply_colormatrix(color, colormatrix);
|
||||
|
||||
dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
|
||||
mirrored)] = color;
|
||||
|
||||
src_x += 2 + 2 * skip;
|
||||
++dst_x;
|
||||
}
|
||||
|
||||
src_y += 2 + 2 * skip;
|
||||
++dst_y;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
quick_preview_rggb10(uint32_t *dst, const uint32_t dst_width,
|
||||
const uint32_t dst_height, const uint8_t *src,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const MPPixelFormat format, const uint32_t rotation,
|
||||
const bool mirrored, const float *colormatrix,
|
||||
const uint8_t blacklevel, const uint32_t skip)
|
||||
{
|
||||
assert(src_width % 2 == 0);
|
||||
|
||||
uint32_t width_bytes = mp_pixel_format_width_to_bytes(format, src_width);
|
||||
|
||||
uint32_t src_y = 0, dst_y = 0;
|
||||
while (src_y < src_height) {
|
||||
uint32_t src_x = 0, dst_x = 0;
|
||||
while (src_x < width_bytes) {
|
||||
uint32_t src_i = src_y * width_bytes + src_x;
|
||||
|
||||
uint8_t b0 = srgb[src[src_i] - blacklevel];
|
||||
uint8_t b1 = srgb[src[src_i + 1] - blacklevel];
|
||||
uint8_t b2 = srgb[src[src_i + width_bytes + 1] - blacklevel];
|
||||
|
||||
uint32_t color;
|
||||
switch (format) {
|
||||
case MP_PIXEL_FMT_BGGR10P:
|
||||
color = pack_rgb(b2, b1, b0);
|
||||
break;
|
||||
case MP_PIXEL_FMT_GBRG10P:
|
||||
color = pack_rgb(b2, b0, b1);
|
||||
break;
|
||||
case MP_PIXEL_FMT_GRBG10P:
|
||||
color = pack_rgb(b1, b0, b2);
|
||||
break;
|
||||
case MP_PIXEL_FMT_RGGB10P:
|
||||
color = pack_rgb(b0, b1, b2);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
color = apply_colormatrix(color, colormatrix);
|
||||
|
||||
dst[coord_map(dst_x, dst_y, dst_width, dst_height, rotation,
|
||||
mirrored)] = color;
|
||||
|
||||
uint32_t advance = 1 + skip;
|
||||
if (src_x % 5 == 0) {
|
||||
src_x += 2 * (advance % 2) + 5 * (advance / 2);
|
||||
} else {
|
||||
src_x += 3 * (advance % 2) + 5 * (advance / 2);
|
||||
}
|
||||
++dst_x;
|
||||
}
|
||||
|
||||
src_y += 2 + 2 * skip;
|
||||
++dst_y;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
quick_preview_yuv(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
|
||||
const uint8_t *src, const uint32_t src_width,
|
||||
const uint32_t src_height, const MPPixelFormat format,
|
||||
const uint32_t rotation, const bool mirrored,
|
||||
const float *colormatrix, const uint32_t skip)
|
||||
{
|
||||
assert(src_width % 2 == 0);
|
||||
|
||||
uint32_t width_bytes = src_width * 2;
|
||||
|
||||
uint32_t unrot_dst_width = dst_width;
|
||||
if (rotation != 0 && rotation != 180) {
|
||||
unrot_dst_width = dst_height;
|
||||
}
|
||||
|
||||
uint32_t src_y = 0, dst_y = 0;
|
||||
while (src_y < src_height) {
|
||||
uint32_t src_x = 0, dst_x = 0;
|
||||
while (src_x < width_bytes) {
|
||||
uint32_t src_i = src_y * width_bytes + src_x;
|
||||
|
||||
uint8_t b0 = src[src_i];
|
||||
uint8_t b1 = src[src_i + 1];
|
||||
uint8_t b2 = src[src_i + 2];
|
||||
uint8_t b3 = src[src_i + 3];
|
||||
|
||||
uint32_t color1, color2;
|
||||
switch (format) {
|
||||
case MP_PIXEL_FMT_UYVY:
|
||||
color1 = convert_yuv_to_srgb(b1, b0, b2);
|
||||
color2 = convert_yuv_to_srgb(b3, b0, b2);
|
||||
break;
|
||||
case MP_PIXEL_FMT_YUYV:
|
||||
color1 = convert_yuv_to_srgb(b0, b1, b3);
|
||||
color2 = convert_yuv_to_srgb(b2, b1, b3);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
color1 = apply_colormatrix(color1, colormatrix);
|
||||
color2 = apply_colormatrix(color2, colormatrix);
|
||||
|
||||
uint32_t dst_i1 = coord_map(dst_x, dst_y, dst_width,
|
||||
dst_height, rotation, mirrored);
|
||||
dst[dst_i1] = color1;
|
||||
++dst_x;
|
||||
|
||||
// The last pixel needs to be skipped if we have an odd un-rotated width
|
||||
if (dst_x < unrot_dst_width) {
|
||||
uint32_t dst_i2 =
|
||||
coord_map(dst_x, dst_y, dst_width,
|
||||
dst_height, rotation, mirrored);
|
||||
dst[dst_i2] = color2;
|
||||
++dst_x;
|
||||
}
|
||||
|
||||
src_x += 4 + 4 * skip;
|
||||
}
|
||||
|
||||
src_y += 1 + skip;
|
||||
++dst_y;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
quick_preview(uint32_t *dst, const uint32_t dst_width, const uint32_t dst_height,
|
||||
const uint8_t *src, const uint32_t src_width,
|
||||
const uint32_t src_height, const MPPixelFormat format,
|
||||
const uint32_t rotation, const bool mirrored, const float *colormatrix,
|
||||
const uint8_t blacklevel, const uint32_t skip)
|
||||
{
|
||||
switch (format) {
|
||||
case MP_PIXEL_FMT_BGGR8:
|
||||
case MP_PIXEL_FMT_GBRG8:
|
||||
case MP_PIXEL_FMT_GRBG8:
|
||||
case MP_PIXEL_FMT_RGGB8:
|
||||
quick_preview_rggb8(dst, dst_width, dst_height, src, src_width,
|
||||
src_height, format, rotation, mirrored,
|
||||
colormatrix, blacklevel, skip);
|
||||
break;
|
||||
case MP_PIXEL_FMT_BGGR10P:
|
||||
case MP_PIXEL_FMT_GBRG10P:
|
||||
case MP_PIXEL_FMT_GRBG10P:
|
||||
case MP_PIXEL_FMT_RGGB10P:
|
||||
quick_preview_rggb10(dst, dst_width, dst_height, src, src_width,
|
||||
src_height, format, rotation, mirrored,
|
||||
colormatrix, blacklevel, skip);
|
||||
break;
|
||||
case MP_PIXEL_FMT_UYVY:
|
||||
case MP_PIXEL_FMT_YUYV:
|
||||
quick_preview_yuv(dst, dst_width, dst_height, src, src_width,
|
||||
src_height, format, rotation, mirrored,
|
||||
colormatrix, skip);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
div_ceil(uint32_t x, uint32_t y)
|
||||
{
|
||||
return x / y + !!(x % y);
|
||||
}
|
||||
|
||||
void
|
||||
quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
|
||||
const uint32_t preview_width, const uint32_t preview_height,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const MPPixelFormat format, const int rotation)
|
||||
{
|
||||
uint32_t colors_x = mp_pixel_format_width_to_colors(format, src_width);
|
||||
uint32_t colors_y = mp_pixel_format_height_to_colors(format, src_height);
|
||||
|
||||
if (rotation != 0 && rotation != 180) {
|
||||
uint32_t tmp = colors_x;
|
||||
colors_x = colors_y;
|
||||
colors_y = tmp;
|
||||
}
|
||||
|
||||
uint32_t scale_x = colors_x / preview_width;
|
||||
uint32_t scale_y = colors_y / preview_height;
|
||||
|
||||
if (scale_x > 0)
|
||||
--scale_x;
|
||||
if (scale_y > 0)
|
||||
--scale_y;
|
||||
*skip = scale_x > scale_y ? scale_x : scale_y;
|
||||
|
||||
*dst_width = div_ceil(colors_x, (1 + *skip));
|
||||
if (*dst_width <= 0)
|
||||
*dst_width = 1;
|
||||
|
||||
*dst_height = div_ceil(colors_y, (1 + *skip));
|
||||
if (*dst_height <= 0)
|
||||
*dst_height = 1;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
#include "camera.h"
|
||||
#include <stdint.h>
|
||||
|
||||
void quick_preview(uint32_t *dst, const uint32_t dst_width,
|
||||
const uint32_t dst_height, const uint8_t *src,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const MPPixelFormat format, const uint32_t rotation,
|
||||
const bool mirrored, const float *colormatrix,
|
||||
const uint8_t blacklevel, const uint32_t skip);
|
||||
|
||||
void quick_preview_size(uint32_t *dst_width, uint32_t *dst_height, uint32_t *skip,
|
||||
const uint32_t preview_width, const uint32_t preview_height,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const MPPixelFormat format, const int rotation);
|
|
@ -6,6 +6,7 @@
|
|||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_VIDEO_BUFFERS 20
|
||||
|
||||
|
@ -205,6 +206,7 @@ xioctl(int fd, int request, void *arg)
|
|||
struct video_buffer {
|
||||
uint32_t length;
|
||||
uint8_t *data;
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct _MPCamera {
|
||||
|
@ -465,6 +467,17 @@ mp_camera_start_capture(MPCamera *camera)
|
|||
break;
|
||||
}
|
||||
|
||||
struct v4l2_exportbuffer expbuf = {
|
||||
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||
.index = i,
|
||||
};
|
||||
if (xioctl(camera->video_fd, VIDIOC_EXPBUF, &expbuf) == -1) {
|
||||
errno_printerr("VIDIOC_EXPBUF");
|
||||
break;
|
||||
}
|
||||
|
||||
camera->buffers[i].fd = expbuf.fd;
|
||||
|
||||
++camera->num_buffers;
|
||||
}
|
||||
|
||||
|
@ -510,6 +523,10 @@ error:
|
|||
-1) {
|
||||
errno_printerr("munmap");
|
||||
}
|
||||
|
||||
if (close(camera->buffers[i].fd) == -1) {
|
||||
errno_printerr("close");
|
||||
}
|
||||
}
|
||||
|
||||
// Reset allocated buffers
|
||||
|
@ -543,6 +560,10 @@ mp_camera_stop_capture(MPCamera *camera)
|
|||
-1) {
|
||||
errno_printerr("munmap");
|
||||
}
|
||||
|
||||
if (close(camera->buffers[i].fd) == -1) {
|
||||
errno_printerr("close");
|
||||
}
|
||||
}
|
||||
|
||||
camera->num_buffers = 0;
|
||||
|
@ -565,8 +586,7 @@ mp_camera_is_capturing(MPCamera *camera)
|
|||
}
|
||||
|
||||
bool
|
||||
mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
|
||||
void *user_data)
|
||||
mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer)
|
||||
{
|
||||
struct v4l2_buffer buf = {};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
@ -587,6 +607,7 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
|
|||
/* fallthrough */
|
||||
default:
|
||||
errno_printerr("VIDIOC_DQBUF");
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -606,24 +627,23 @@ mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
|
|||
mp_pixel_format_width_to_bytes(pixel_format, width) * height);
|
||||
assert(bytesused == camera->buffers[buf.index].length);
|
||||
|
||||
MPImage image = {
|
||||
.pixel_format = pixel_format,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = camera->buffers[buf.index].data,
|
||||
};
|
||||
buffer->index = buf.index;
|
||||
buffer->data = camera->buffers[buf.index].data;
|
||||
buffer->fd = camera->buffers[buf.index].fd;
|
||||
|
||||
callback(image, user_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The callback may have stopped the capture, only queue the buffer if we're
|
||||
// still capturing.
|
||||
if (mp_camera_is_capturing(camera)) {
|
||||
bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index)
|
||||
{
|
||||
struct v4l2_buffer buf = {};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = buffer_index;
|
||||
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
errno_printerr("VIDIOC_QBUF");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -45,11 +45,11 @@ typedef struct {
|
|||
bool mp_camera_mode_is_equivalent(const MPCameraMode *m1, const MPCameraMode *m2);
|
||||
|
||||
typedef struct {
|
||||
uint32_t pixel_format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t index;
|
||||
|
||||
uint8_t *data;
|
||||
} MPImage;
|
||||
int fd;
|
||||
} MPBuffer;
|
||||
|
||||
typedef struct _MPCamera MPCamera;
|
||||
|
||||
|
@ -67,8 +67,8 @@ bool mp_camera_set_mode(MPCamera *camera, MPCameraMode *mode);
|
|||
bool mp_camera_start_capture(MPCamera *camera);
|
||||
bool mp_camera_stop_capture(MPCamera *camera);
|
||||
bool mp_camera_is_capturing(MPCamera *camera);
|
||||
bool mp_camera_capture_image(MPCamera *camera, void (*callback)(MPImage, void *),
|
||||
void *user_data);
|
||||
bool mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer);
|
||||
bool mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index);
|
||||
|
||||
typedef struct _MPCameraModeList MPCameraModeList;
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
#include "ini.h"
|
||||
#include "config.h"
|
||||
#include "matrix.h"
|
||||
#include <wordexp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -21,18 +20,8 @@ static bool
|
|||
find_config(char *conffile)
|
||||
{
|
||||
char buf[512];
|
||||
char *xdg_config_home;
|
||||
wordexp_t exp_result;
|
||||
FILE *fp;
|
||||
|
||||
// Resolve XDG stuff
|
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
|
||||
xdg_config_home = "~/.config";
|
||||
}
|
||||
wordexp(xdg_config_home, &exp_result, 0);
|
||||
xdg_config_home = strdup(exp_result.we_wordv[0]);
|
||||
wordfree(&exp_result);
|
||||
|
||||
if (access("/proc/device-tree/compatible", F_OK) != -1) {
|
||||
// Reads to compatible string of the current device tree, looks like:
|
||||
// pine64,pinephone-1.2\0allwinner,sun50i-a64\0
|
||||
|
@ -48,7 +37,7 @@ find_config(char *conffile)
|
|||
}
|
||||
|
||||
// Check for a config file in XDG_CONFIG_HOME
|
||||
sprintf(conffile, "%s/megapixels/config/%s.ini", xdg_config_home,
|
||||
sprintf(conffile, "%s/megapixels/config/%s.ini", g_get_user_config_dir (),
|
||||
buf);
|
||||
if (access(conffile, F_OK) != -1) {
|
||||
printf("Found config file at %s\n", conffile);
|
|
@ -0,0 +1,220 @@
|
|||
#include "gl_util.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gmodule.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
void gl_util_check_error(const char *file, int line)
|
||||
{
|
||||
GLenum error = glGetError();
|
||||
|
||||
const char *name;
|
||||
switch (error) {
|
||||
case GL_NO_ERROR:
|
||||
return; // no error
|
||||
case GL_INVALID_ENUM:
|
||||
name = "GL_INVALID_ENUM";
|
||||
break;
|
||||
case GL_INVALID_VALUE:
|
||||
name = "GL_INVALID_VALUE";
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
name = "GL_INVALID_OPERATION";
|
||||
break;
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
name = "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
break;
|
||||
case GL_OUT_OF_MEMORY:
|
||||
name = "GL_OUT_OF_MEMORY";
|
||||
break;
|
||||
default:
|
||||
name = "UNKNOWN ERROR!";
|
||||
break;
|
||||
}
|
||||
|
||||
printf("GL error at %s:%d - %s\n", file, line, name);
|
||||
|
||||
// raise(SIGTRAP);
|
||||
}
|
||||
|
||||
GLuint
|
||||
gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra)
|
||||
{
|
||||
GdkGLContext *context = gdk_gl_context_get_current();
|
||||
assert(context);
|
||||
|
||||
GLuint shader = glCreateShader(type);
|
||||
if (shader == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GBytes *bytes = g_resources_lookup_data(resource, 0, NULL);
|
||||
if (!bytes) {
|
||||
printf("Failed to load shader resource %s\n", resource);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build #define for OpenGL context information
|
||||
gboolean is_es = gdk_gl_context_get_use_es(context);
|
||||
int major, minor;
|
||||
gdk_gl_context_get_version(context, &major, &minor);
|
||||
char context_info_buf[128];
|
||||
snprintf(context_info_buf, 128, "#define %s\n#define GL_%d\n#define GL_%d_%d\n", is_es ? "GL_ES" : "GL_NO_ES", major, major, minor);
|
||||
|
||||
gsize glib_size = 0;
|
||||
const GLchar *source = g_bytes_get_data(bytes, &glib_size);
|
||||
if (glib_size == 0 || glib_size > INT_MAX) {
|
||||
printf("Invalid size for resource\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const GLchar **sources = malloc((num_extra + 1) * sizeof(GLchar *));
|
||||
GLint *sizes = malloc((num_extra + 1) * sizeof(GLint));
|
||||
|
||||
for (size_t i = 0; i < num_extra; ++i) {
|
||||
sources[i] = extra_sources[i];
|
||||
sizes[i] = -1;
|
||||
}
|
||||
sources[num_extra] = source;
|
||||
sizes[num_extra] = glib_size;
|
||||
|
||||
glShaderSource(shader, num_extra + 1, sources, sizes);
|
||||
glCompileShader(shader);
|
||||
check_gl();
|
||||
|
||||
free(sources);
|
||||
free(sizes);
|
||||
|
||||
g_bytes_unref(bytes);
|
||||
|
||||
// Check compile status
|
||||
GLint success;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (success == GL_FALSE) {
|
||||
printf("Shader compilation failed for %s\n", resource);
|
||||
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
char *log = malloc(sizeof(char) * log_length);
|
||||
glGetShaderInfoLog(shader, log_length - 1, &log_length, log);
|
||||
|
||||
printf("Shader %s log: %s\n", resource, log);
|
||||
free(log);
|
||||
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint
|
||||
gl_util_link_program(GLuint *shaders, size_t num_shaders)
|
||||
{
|
||||
GLuint program = glCreateProgram();
|
||||
|
||||
for (size_t i = 0; i < num_shaders; ++i) {
|
||||
glAttachShader(program, shaders[i]);
|
||||
}
|
||||
|
||||
glLinkProgram(program);
|
||||
check_gl();
|
||||
|
||||
GLint success;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &success);
|
||||
if (success == GL_FALSE) {
|
||||
printf("Program linking failed\n");
|
||||
}
|
||||
|
||||
GLint log_length;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
char *log = malloc(sizeof(char) * log_length);
|
||||
glGetProgramInfoLog(program, log_length - 1, &log_length, log);
|
||||
|
||||
printf("Program log: %s\n", log);
|
||||
free(log);
|
||||
}
|
||||
check_gl();
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
static const GLfloat quad_data[] = {
|
||||
// Vertices
|
||||
-1, -1,
|
||||
1, -1,
|
||||
-1, 1,
|
||||
1, 1,
|
||||
// Texcoords
|
||||
0, 0,
|
||||
1, 0,
|
||||
0, 1,
|
||||
1, 1,
|
||||
};
|
||||
|
||||
GLuint gl_util_new_quad()
|
||||
{
|
||||
GdkGLContext *context = gdk_gl_context_get_current();
|
||||
assert(context);
|
||||
|
||||
if (gdk_gl_context_get_use_es(context)) {
|
||||
return 0;
|
||||
} else {
|
||||
GLuint buffer;
|
||||
glGenBuffers(1, &buffer);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
|
||||
check_gl();
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
check_gl();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
void gl_util_bind_quad(GLuint buffer)
|
||||
{
|
||||
GdkGLContext *context = gdk_gl_context_get_current();
|
||||
assert(context);
|
||||
|
||||
if (gdk_gl_context_get_use_es(context)) {
|
||||
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data);
|
||||
check_gl();
|
||||
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
|
||||
check_gl();
|
||||
|
||||
glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, 0, 0, quad_data + 8);
|
||||
check_gl();
|
||||
glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
|
||||
check_gl();
|
||||
} else {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
check_gl();
|
||||
|
||||
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
|
||||
check_gl();
|
||||
|
||||
glVertexAttribPointer(GL_UTIL_TEX_COORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, (void*) (8 * sizeof(float)));
|
||||
glEnableVertexAttribArray(GL_UTIL_TEX_COORD_ATTRIBUTE);
|
||||
check_gl();
|
||||
}
|
||||
}
|
||||
|
||||
void gl_util_draw_quad(GLuint buffer)
|
||||
{
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
check_gl();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <epoxy/gl.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define GL_UTIL_VERTEX_ATTRIBUTE 0
|
||||
#define GL_UTIL_TEX_COORD_ATTRIBUTE 1
|
||||
|
||||
#define check_gl() gl_util_check_error(__FILE__, __LINE__)
|
||||
void gl_util_check_error(const char *file, int line);
|
||||
|
||||
GLuint gl_util_load_shader(const char *resource, GLenum type, const char **extra_sources, size_t num_extra);
|
||||
GLuint gl_util_link_program(GLuint *shaders, size_t num_shaders);
|
||||
|
||||
GLuint gl_util_new_quad();
|
||||
void gl_util_bind_quad(GLuint buffer);
|
||||
void gl_util_draw_quad(GLuint buffer);
|
|
@ -0,0 +1,141 @@
|
|||
#include "gles2_debayer.h"
|
||||
|
||||
#include "camera.h"
|
||||
#include "gl_util.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define VERTEX_ATTRIBUTE 0
|
||||
#define TEX_COORD_ATTRIBUTE 1
|
||||
|
||||
struct _GLES2Debayer {
|
||||
GLuint frame_buffer;
|
||||
GLuint program;
|
||||
GLuint uniform_transform;
|
||||
GLuint uniform_pixel_size;
|
||||
GLuint uniform_texture;
|
||||
GLuint uniform_color_matrix;
|
||||
|
||||
GLuint quad;
|
||||
};
|
||||
|
||||
GLES2Debayer *
|
||||
gles2_debayer_new(MPPixelFormat format)
|
||||
{
|
||||
if (format != MP_PIXEL_FMT_BGGR8) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GLuint frame_buffer;
|
||||
glGenFramebuffers(1, &frame_buffer);
|
||||
check_gl();
|
||||
|
||||
GLuint shaders[] = {
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/debayer.vert", GL_VERTEX_SHADER, NULL, 0),
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/debayer.frag", GL_FRAGMENT_SHADER, NULL, 0),
|
||||
};
|
||||
|
||||
GLuint program = gl_util_link_program(shaders, 2);
|
||||
glBindAttribLocation(program, VERTEX_ATTRIBUTE, "vert");
|
||||
glBindAttribLocation(program, TEX_COORD_ATTRIBUTE, "tex_coord");
|
||||
check_gl();
|
||||
|
||||
GLES2Debayer *self = malloc(sizeof(GLES2Debayer));
|
||||
self->frame_buffer = frame_buffer;
|
||||
self->program = program;
|
||||
|
||||
self->uniform_transform = glGetUniformLocation(self->program, "transform");
|
||||
self->uniform_pixel_size = glGetUniformLocation(self->program, "pixel_size");
|
||||
self->uniform_texture = glGetUniformLocation(self->program, "texture");
|
||||
self->uniform_color_matrix = glGetUniformLocation(self->program, "color_matrix");
|
||||
check_gl();
|
||||
|
||||
self->quad = gl_util_new_quad();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
gles2_debayer_free(GLES2Debayer *self)
|
||||
{
|
||||
glDeleteFramebuffers(1, &self->frame_buffer);
|
||||
|
||||
glDeleteProgram(self->program);
|
||||
|
||||
free(self);
|
||||
}
|
||||
|
||||
void
|
||||
gles2_debayer_use(GLES2Debayer *self)
|
||||
{
|
||||
glUseProgram(self->program);
|
||||
check_gl();
|
||||
|
||||
gl_util_bind_quad(self->quad);
|
||||
}
|
||||
|
||||
void
|
||||
gles2_debayer_configure(GLES2Debayer *self,
|
||||
const uint32_t dst_width, const uint32_t dst_height,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const uint32_t rotation,
|
||||
const bool mirrored,
|
||||
const float *colormatrix,
|
||||
const uint8_t blacklevel)
|
||||
{
|
||||
glViewport(0, 0, dst_width, dst_height);
|
||||
check_gl();
|
||||
|
||||
GLfloat rotation_list[4] = { 0, -1, 0, 1 };
|
||||
int rotation_index = 4 - rotation / 90;
|
||||
|
||||
GLfloat sin_rot = rotation_list[rotation_index];
|
||||
GLfloat cos_rot = rotation_list[(rotation_index + 1) % 4];
|
||||
GLfloat scale_x = mirrored ? 1 : -1;
|
||||
GLfloat matrix[9] = {
|
||||
cos_rot * scale_x, sin_rot, 0,
|
||||
-sin_rot * scale_x, cos_rot, 0,
|
||||
0, 0, 1,
|
||||
};
|
||||
glUniformMatrix3fv(self->uniform_transform, 1, GL_FALSE, matrix);
|
||||
check_gl();
|
||||
|
||||
GLfloat pixel_size_x = 1.0f / src_width;
|
||||
GLfloat pixel_size_y = 1.0f / src_height;
|
||||
glUniform2f(self->uniform_pixel_size, pixel_size_x, pixel_size_y);
|
||||
check_gl();
|
||||
|
||||
if (colormatrix) {
|
||||
GLfloat transposed[9];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
transposed[i + j * 3] = colormatrix[j + i * 3];
|
||||
|
||||
glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, transposed);
|
||||
} else {
|
||||
static const GLfloat identity[9] = {
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1,
|
||||
};
|
||||
glUniformMatrix3fv(self->uniform_color_matrix, 1, GL_FALSE, identity);
|
||||
}
|
||||
check_gl();
|
||||
}
|
||||
|
||||
void
|
||||
gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_buffer);
|
||||
glBindTexture(GL_TEXTURE_2D, dst_id);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_id, 0);
|
||||
check_gl();
|
||||
|
||||
assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, source_id);
|
||||
glUniform1i(self->uniform_texture, 0);
|
||||
check_gl();
|
||||
|
||||
gl_util_draw_quad(self->quad);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#include "camera.h"
|
||||
#include "gl_util.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct _GLES2Debayer GLES2Debayer;
|
||||
|
||||
GLES2Debayer* gles2_debayer_new(MPPixelFormat format);
|
||||
void gles2_debayer_free(GLES2Debayer *self);
|
||||
|
||||
void gles2_debayer_use(GLES2Debayer *self);
|
||||
|
||||
void gles2_debayer_configure(GLES2Debayer *self,
|
||||
const uint32_t dst_width, const uint32_t dst_height,
|
||||
const uint32_t src_width, const uint32_t src_height,
|
||||
const uint32_t rotation,
|
||||
const bool mirrored,
|
||||
const float *colormatrix,
|
||||
const uint8_t blacklevel);
|
||||
|
||||
void gles2_debayer_process(GLES2Debayer *self, GLuint dst_id, GLuint source_id);
|
|
@ -75,6 +75,8 @@ static int captures_remaining = 0;
|
|||
static int preview_width;
|
||||
static int preview_height;
|
||||
|
||||
static int device_rotation;
|
||||
|
||||
struct control_state {
|
||||
bool gain_is_manual;
|
||||
int gain;
|
||||
|
@ -185,6 +187,12 @@ setup_camera(MPDeviceList **device_list, const struct mp_camera_config *config)
|
|||
|
||||
info->camera = mp_camera_new(dev_info->video_fd, info->fd);
|
||||
|
||||
// Start with the capture format, this works around a bug with
|
||||
// the ov5640 driver where it won't allow setting the preview
|
||||
// format initially.
|
||||
MPCameraMode mode = config->capture_mode;
|
||||
mp_camera_set_mode(info->camera, &mode);
|
||||
|
||||
// Trigger continuous auto focus if the sensor supports it
|
||||
if (mp_camera_query_control(info->camera, V4L2_CID_FOCUS_AUTO,
|
||||
NULL)) {
|
||||
|
@ -269,6 +277,7 @@ update_process_pipeline()
|
|||
.burst_length = burst_length,
|
||||
.preview_width = preview_width,
|
||||
.preview_height = preview_height,
|
||||
.device_rotation = device_rotation,
|
||||
.gain_is_manual = current_controls.gain_is_manual,
|
||||
.gain = current_controls.gain,
|
||||
.gain_max = info->gain_max,
|
||||
|
@ -305,6 +314,7 @@ capture(MPPipeline *pipeline, const void *data)
|
|||
V4L2_EXPOSURE_MANUAL);
|
||||
|
||||
// Change camera mode for capturing
|
||||
mp_process_pipeline_sync();
|
||||
mp_camera_stop_capture(info->camera);
|
||||
|
||||
mode = camera->capture_mode;
|
||||
|
@ -324,6 +334,19 @@ mp_io_pipeline_capture()
|
|||
mp_pipeline_invoke(pipeline, capture, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
release_buffer(MPPipeline *pipeline, const uint32_t *buffer_index)
|
||||
{
|
||||
struct camera_info *info = &cameras[camera->index];
|
||||
|
||||
mp_camera_release_buffer(info->camera, *buffer_index);
|
||||
}
|
||||
|
||||
void mp_io_pipeline_release_buffer(uint32_t buffer_index)
|
||||
{
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback) release_buffer, &buffer_index, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static void
|
||||
update_controls()
|
||||
{
|
||||
|
@ -375,7 +398,7 @@ update_controls()
|
|||
}
|
||||
|
||||
static void
|
||||
on_frame(MPImage image, void *data)
|
||||
on_frame(MPBuffer buffer, void * _data)
|
||||
{
|
||||
// Only update controls right after a frame was captured
|
||||
update_controls();
|
||||
|
@ -384,13 +407,13 @@ on_frame(MPImage image, void *data)
|
|||
// presumably from buffers made ready during the switch. Ignore these.
|
||||
if (just_switched_mode) {
|
||||
if (blank_frame_count < 20) {
|
||||
// Only check a 50x50 area
|
||||
// Only check a 10x10 area
|
||||
size_t test_size =
|
||||
MIN(50, image.width) * MIN(50, image.height);
|
||||
MIN(10, mode.width) * MIN(10, mode.height);
|
||||
|
||||
bool image_is_blank = true;
|
||||
for (size_t i = 0; i < test_size; ++i) {
|
||||
if (image.data[i] != 0) {
|
||||
if (buffer.data[i] != 0) {
|
||||
image_is_blank = false;
|
||||
}
|
||||
}
|
||||
|
@ -407,17 +430,8 @@ on_frame(MPImage image, void *data)
|
|||
blank_frame_count = 0;
|
||||
}
|
||||
|
||||
// Copy from the camera buffer
|
||||
size_t size =
|
||||
mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
|
||||
image.height;
|
||||
uint8_t *buffer = malloc(size);
|
||||
memcpy(buffer, image.data, size);
|
||||
|
||||
image.data = buffer;
|
||||
|
||||
// Send the image off for processing
|
||||
mp_process_pipeline_process_image(image);
|
||||
mp_process_pipeline_process_image(buffer);
|
||||
|
||||
if (captures_remaining > 0) {
|
||||
--captures_remaining;
|
||||
|
@ -438,6 +452,7 @@ on_frame(MPImage image, void *data)
|
|||
}
|
||||
|
||||
// Go back to preview mode
|
||||
mp_process_pipeline_sync();
|
||||
mp_camera_stop_capture(info->camera);
|
||||
|
||||
mode = camera->preview_mode;
|
||||
|
@ -465,6 +480,7 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
|
|||
struct camera_info *info = &cameras[camera->index];
|
||||
struct device_info *dev_info = &devices[info->device_index];
|
||||
|
||||
mp_process_pipeline_sync();
|
||||
mp_camera_stop_capture(info->camera);
|
||||
mp_device_setup_link(dev_info->device, info->pad_id,
|
||||
dev_info->interface_pad_id, false);
|
||||
|
@ -492,15 +508,15 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
|
|||
pipeline, info->camera, on_frame, NULL);
|
||||
|
||||
current_controls.gain_is_manual =
|
||||
mp_camera_control_get_int32(
|
||||
info->camera, V4L2_CID_EXPOSURE_AUTO) ==
|
||||
V4L2_EXPOSURE_MANUAL;
|
||||
mp_camera_control_get_bool(info->camera,
|
||||
V4L2_CID_AUTOGAIN) == 0;
|
||||
current_controls.gain = mp_camera_control_get_int32(
|
||||
info->camera, info->gain_ctrl);
|
||||
|
||||
current_controls.exposure_is_manual =
|
||||
mp_camera_control_get_bool(info->camera,
|
||||
V4L2_CID_AUTOGAIN) == 0;
|
||||
mp_camera_control_get_int32(
|
||||
info->camera, V4L2_CID_EXPOSURE_AUTO) ==
|
||||
V4L2_EXPOSURE_MANUAL;
|
||||
current_controls.exposure = mp_camera_control_get_int32(
|
||||
info->camera, V4L2_CID_EXPOSURE);
|
||||
}
|
||||
|
@ -508,11 +524,13 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state)
|
|||
|
||||
has_changed = has_changed || burst_length != state->burst_length ||
|
||||
preview_width != state->preview_width ||
|
||||
preview_height != state->preview_height;
|
||||
preview_height != state->preview_height ||
|
||||
device_rotation != state->device_rotation;
|
||||
|
||||
burst_length = state->burst_length;
|
||||
preview_width = state->preview_width;
|
||||
preview_height = state->preview_height;
|
||||
device_rotation = state->device_rotation;
|
||||
|
||||
if (camera) {
|
||||
struct control_state previous_desired = desired_controls;
|
|
@ -10,6 +10,8 @@ struct mp_io_pipeline_state {
|
|||
int preview_width;
|
||||
int preview_height;
|
||||
|
||||
int device_rotation;
|
||||
|
||||
bool gain_is_manual;
|
||||
int gain;
|
||||
|
||||
|
@ -23,4 +25,6 @@ void mp_io_pipeline_stop();
|
|||
void mp_io_pipeline_focus();
|
||||
void mp_io_pipeline_capture();
|
||||
|
||||
void mp_io_pipeline_release_buffer(uint32_t buffer_index);
|
||||
|
||||
void mp_io_pipeline_update_state(const struct mp_io_pipeline_state *state);
|
|
@ -0,0 +1,963 @@
|
|||
#include "main.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/media.h>
|
||||
#include <linux/v4l2-subdev.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <asm/errno.h>
|
||||
#include <wordexp.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <locale.h>
|
||||
#include <zbar.h>
|
||||
#include "gl_util.h"
|
||||
#include "camera_config.h"
|
||||
#include "io_pipeline.h"
|
||||
#include "process_pipeline.h"
|
||||
|
||||
#define RENDERDOC
|
||||
|
||||
#ifdef RENDERDOC
|
||||
#include <dlfcn.h>
|
||||
#include <renderdoc/app.h>
|
||||
RENDERDOC_API_1_1_2 *rdoc_api = NULL;
|
||||
#endif
|
||||
|
||||
enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
|
||||
|
||||
static bool camera_is_initialized = false;
|
||||
static const struct mp_camera_config *camera = NULL;
|
||||
static MPCameraMode mode;
|
||||
|
||||
static int preview_width = -1;
|
||||
static int preview_height = -1;
|
||||
|
||||
static int device_rotation = 0;
|
||||
|
||||
static bool gain_is_manual = false;
|
||||
static int gain;
|
||||
static int gain_max;
|
||||
|
||||
static bool exposure_is_manual = false;
|
||||
static int exposure;
|
||||
|
||||
static bool has_auto_focus_continuous;
|
||||
static bool has_auto_focus_start;
|
||||
|
||||
static MPProcessPipelineBuffer *current_preview_buffer = NULL;
|
||||
static int preview_buffer_width = -1;
|
||||
static int preview_buffer_height = -1;
|
||||
|
||||
static char last_path[260] = "";
|
||||
|
||||
static MPZBarScanResult *zbar_result = NULL;
|
||||
|
||||
static int burst_length = 3;
|
||||
|
||||
// Widgets
|
||||
GtkWidget *preview;
|
||||
GtkWidget *main_stack;
|
||||
GtkWidget *open_last_stack;
|
||||
GtkWidget *thumb_last;
|
||||
GtkWidget *process_spinner;
|
||||
GtkWidget *scanned_codes;
|
||||
GtkWidget *preview_top_box;
|
||||
GtkWidget *preview_bottom_box;
|
||||
|
||||
int
|
||||
remap(int value, int input_min, int input_max, int output_min, int output_max)
|
||||
{
|
||||
const long long factor = 1000000000;
|
||||
long long output_spread = output_max - output_min;
|
||||
long long input_spread = input_max - input_min;
|
||||
|
||||
long long zero_value = value - input_min;
|
||||
zero_value *= factor;
|
||||
long long percentage = zero_value / input_spread;
|
||||
|
||||
long long zero_output = percentage * output_spread / factor;
|
||||
|
||||
long long result = output_min + zero_output;
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
static void
|
||||
update_io_pipeline()
|
||||
{
|
||||
struct mp_io_pipeline_state io_state = {
|
||||
.camera = camera,
|
||||
.burst_length = burst_length,
|
||||
.preview_width = preview_width,
|
||||
.preview_height = preview_height,
|
||||
.device_rotation = device_rotation,
|
||||
.gain_is_manual = gain_is_manual,
|
||||
.gain = gain,
|
||||
.exposure_is_manual = exposure_is_manual,
|
||||
.exposure = exposure,
|
||||
};
|
||||
mp_io_pipeline_update_state(&io_state);
|
||||
}
|
||||
|
||||
static bool
|
||||
update_state(const struct mp_main_state *state)
|
||||
{
|
||||
if (!camera_is_initialized) {
|
||||
camera_is_initialized = true;
|
||||
}
|
||||
|
||||
if (camera == state->camera) {
|
||||
mode = state->mode;
|
||||
|
||||
if (!gain_is_manual) {
|
||||
gain = state->gain;
|
||||
}
|
||||
gain_max = state->gain_max;
|
||||
|
||||
if (!exposure_is_manual) {
|
||||
exposure = state->exposure;
|
||||
}
|
||||
|
||||
has_auto_focus_continuous = state->has_auto_focus_continuous;
|
||||
has_auto_focus_start = state->has_auto_focus_start;
|
||||
}
|
||||
|
||||
preview_buffer_width = state->image_width;
|
||||
preview_buffer_height = state->image_height;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_update_state(const struct mp_main_state *state)
|
||||
{
|
||||
struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
|
||||
*state_copy = *state;
|
||||
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)update_state, state_copy, free);
|
||||
}
|
||||
|
||||
static bool set_zbar_result(MPZBarScanResult *result)
|
||||
{
|
||||
if (zbar_result) {
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
free(zbar_result->codes[i].data);
|
||||
}
|
||||
|
||||
free(zbar_result);
|
||||
}
|
||||
|
||||
zbar_result = result;
|
||||
gtk_widget_queue_draw(preview);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mp_main_set_zbar_result(MPZBarScanResult *result)
|
||||
{
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)set_zbar_result, result, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
set_preview(MPProcessPipelineBuffer *buffer)
|
||||
{
|
||||
if (current_preview_buffer) {
|
||||
mp_process_pipeline_buffer_unref(current_preview_buffer);
|
||||
}
|
||||
current_preview_buffer = buffer;
|
||||
gtk_widget_queue_draw(preview);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_set_preview(MPProcessPipelineBuffer *buffer)
|
||||
{
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)set_preview, buffer, NULL);
|
||||
}
|
||||
|
||||
struct capture_completed_args {
|
||||
GdkTexture *thumb;
|
||||
char *fname;
|
||||
};
|
||||
|
||||
static bool
|
||||
capture_completed(struct capture_completed_args *args)
|
||||
{
|
||||
strncpy(last_path, args->fname, 259);
|
||||
|
||||
gtk_image_set_from_paintable(GTK_IMAGE(thumb_last),
|
||||
GDK_PAINTABLE(args->thumb));
|
||||
|
||||
gtk_spinner_stop(GTK_SPINNER(process_spinner));
|
||||
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
|
||||
|
||||
g_object_unref(args->thumb);
|
||||
g_free(args->fname);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_main_capture_completed(GdkTexture *thumb, const char *fname)
|
||||
{
|
||||
struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
|
||||
args->thumb = thumb;
|
||||
args->fname = g_strdup(fname);
|
||||
g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
|
||||
(GSourceFunc)capture_completed, args, free);
|
||||
}
|
||||
|
||||
static GLuint blit_program;
|
||||
static GLuint blit_uniform_transform;
|
||||
static GLuint blit_uniform_texture;
|
||||
static GLuint solid_program;
|
||||
static GLuint solid_uniform_color;
|
||||
static GLuint quad;
|
||||
|
||||
static void
|
||||
preview_realize(GtkGLArea *area)
|
||||
{
|
||||
gtk_gl_area_make_current(area);
|
||||
|
||||
if (gtk_gl_area_get_error(area) != NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a VAO for OpenGL
|
||||
if (!gtk_gl_area_get_use_es(area)) {
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
check_gl();
|
||||
}
|
||||
|
||||
GLuint blit_shaders[] = {
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert", GL_VERTEX_SHADER, NULL, 0),
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag", GL_FRAGMENT_SHADER, NULL, 0),
|
||||
};
|
||||
|
||||
blit_program = gl_util_link_program(blit_shaders, 2);
|
||||
glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
|
||||
glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord");
|
||||
check_gl();
|
||||
|
||||
blit_uniform_transform = glGetUniformLocation(blit_program, "transform");
|
||||
blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
|
||||
|
||||
GLuint solid_shaders[] = {
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert", GL_VERTEX_SHADER, NULL, 0),
|
||||
gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag", GL_FRAGMENT_SHADER, NULL, 0),
|
||||
};
|
||||
|
||||
solid_program = gl_util_link_program(solid_shaders, 2);
|
||||
glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
|
||||
check_gl();
|
||||
|
||||
solid_uniform_color = glGetUniformLocation(solid_program, "color");
|
||||
|
||||
quad = gl_util_new_quad();
|
||||
}
|
||||
|
||||
static void
|
||||
position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y)
|
||||
{
|
||||
int buffer_width, buffer_height;
|
||||
if (device_rotation == 0 || device_rotation == 180) {
|
||||
buffer_width = preview_buffer_width;
|
||||
buffer_height = preview_buffer_height;
|
||||
} else {
|
||||
buffer_width = preview_buffer_height;
|
||||
buffer_height = preview_buffer_width;
|
||||
}
|
||||
|
||||
int scale_factor = gtk_widget_get_scale_factor(preview);
|
||||
int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale_factor;
|
||||
int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale_factor;
|
||||
int inner_height = preview_height - top_height - bottom_height;
|
||||
|
||||
double scale = MIN(preview_width / (float) buffer_width, preview_height / (float) buffer_height);
|
||||
|
||||
*size_x = scale * buffer_width;
|
||||
*size_y = scale * buffer_height;
|
||||
|
||||
*offset_x = (preview_width - *size_x) / 2.0;
|
||||
|
||||
if (*size_y > inner_height) {
|
||||
*offset_y = (preview_height - *size_y) / 2.0;
|
||||
} else {
|
||||
*offset_y = top_height + (inner_height - *size_y) / 2.0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
|
||||
{
|
||||
if (gtk_gl_area_get_error(area) != NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!camera_is_initialized) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef RENDERDOC
|
||||
if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
|
||||
#endif
|
||||
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
float offset_x, offset_y, size_x, size_y;
|
||||
position_preview(&offset_x, &offset_y, &size_x, &size_y);
|
||||
glViewport(offset_x,
|
||||
preview_height - size_y - offset_y,
|
||||
size_x,
|
||||
size_y);
|
||||
|
||||
if (current_preview_buffer) {
|
||||
glUseProgram(blit_program);
|
||||
|
||||
GLfloat rotation_list[4] = { 0, -1, 0, 1 };
|
||||
int rotation_index = device_rotation / 90;
|
||||
|
||||
GLfloat sin_rot = rotation_list[rotation_index];
|
||||
GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4];
|
||||
GLfloat matrix[9] = {
|
||||
cos_rot, sin_rot, 0,
|
||||
-sin_rot, cos_rot, 0,
|
||||
0, 0, 1,
|
||||
};
|
||||
glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix);
|
||||
check_gl();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer));
|
||||
glUniform1i(blit_uniform_texture, 0);
|
||||
check_gl();
|
||||
|
||||
gl_util_bind_quad(quad);
|
||||
gl_util_draw_quad(quad);
|
||||
}
|
||||
|
||||
if (zbar_result) {
|
||||
GLuint buffer;
|
||||
if (!gtk_gl_area_get_use_es(area)) {
|
||||
glGenBuffers(1, &buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
check_gl();
|
||||
}
|
||||
|
||||
glUseProgram(solid_program);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glUniform4f(solid_uniform_color, 1, 0, 0, 0.5);
|
||||
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
MPZBarCode *code = &zbar_result->codes[i];
|
||||
|
||||
GLfloat vertices[] = {
|
||||
code->bounds_x[0], code->bounds_y[0],
|
||||
code->bounds_x[1], code->bounds_y[1],
|
||||
code->bounds_x[3], code->bounds_y[3],
|
||||
code->bounds_x[2], code->bounds_y[2],
|
||||
};
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vertices[i * 2] = 2 * vertices[i * 2] / preview_buffer_width - 1.0;
|
||||
vertices[i * 2 + 1] = 1.0 - 2 * vertices[i * 2 + 1] / preview_buffer_height;
|
||||
}
|
||||
|
||||
if (gtk_gl_area_get_use_es(area)) {
|
||||
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, vertices);
|
||||
check_gl();
|
||||
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
|
||||
check_gl();
|
||||
} else {
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
|
||||
check_gl();
|
||||
|
||||
glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
|
||||
check_gl();
|
||||
}
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
check_gl();
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
glFlush();
|
||||
|
||||
#ifdef RENDERDOC
|
||||
if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
|
||||
#endif
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preview_resize(GtkWidget *widget, int width, int height, gpointer data)
|
||||
{
|
||||
if (preview_width != width || preview_height != height) {
|
||||
preview_width = width;
|
||||
preview_height = height;
|
||||
update_io_pipeline();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
char uri[275];
|
||||
GError *error = NULL;
|
||||
|
||||
if (strlen(last_path) == 0) {
|
||||
return;
|
||||
}
|
||||
sprintf(uri, "file://%s", last_path);
|
||||
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
|
||||
g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
char uri[270];
|
||||
GError *error = NULL;
|
||||
sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
|
||||
if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
|
||||
g_printerr("Could not launch image viewer: %s\n", error->message);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
gtk_spinner_start(GTK_SPINNER(process_spinner));
|
||||
gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
|
||||
mp_io_pipeline_capture();
|
||||
}
|
||||
|
||||
void
|
||||
run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app)
|
||||
{
|
||||
g_application_quit(app);
|
||||
}
|
||||
|
||||
static bool
|
||||
check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
|
||||
{
|
||||
bool right = false, left = false, top = false, bottom = false;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (x <= bounds_x[i])
|
||||
left = true;
|
||||
if (x >= bounds_x[i])
|
||||
right = true;
|
||||
if (y <= bounds_y[i])
|
||||
top = true;
|
||||
if (y >= bounds_y[i])
|
||||
bottom = true;
|
||||
}
|
||||
|
||||
return right && left && top && bottom;
|
||||
}
|
||||
|
||||
static void
|
||||
on_zbar_dialog_response(GtkDialog *dialog, int response, char *data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
switch (response) {
|
||||
case GTK_RESPONSE_YES:
|
||||
if (!g_app_info_launch_default_for_uri(data,
|
||||
NULL, &error)) {
|
||||
g_printerr("Could not launch application: %s\n",
|
||||
error->message);
|
||||
}
|
||||
case GTK_RESPONSE_ACCEPT:
|
||||
{
|
||||
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog));
|
||||
gdk_clipboard_set_text(
|
||||
gdk_display_get_clipboard(display),
|
||||
data);
|
||||
}
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
break;
|
||||
default:
|
||||
g_printerr("Wrong dialog response: %d\n", response);
|
||||
}
|
||||
|
||||
g_free(data);
|
||||
gtk_window_destroy(GTK_WINDOW(dialog));
|
||||
}
|
||||
|
||||
static void
|
||||
on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
|
||||
bool data_is_url = g_uri_is_valid(
|
||||
code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
|
||||
|
||||
char* data = strdup(code->data);
|
||||
|
||||
if (data_is_url) {
|
||||
dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(gtk_widget_get_root(widget)),
|
||||
flags,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_NONE,
|
||||
"Found a URL '%s' encoded in a %s code.",
|
||||
code->data,
|
||||
code->type);
|
||||
gtk_dialog_add_buttons(
|
||||
GTK_DIALOG(dialog),
|
||||
"_Open URL",
|
||||
GTK_RESPONSE_YES,
|
||||
NULL);
|
||||
} else {
|
||||
dialog = gtk_message_dialog_new(
|
||||
GTK_WINDOW(gtk_widget_get_root(widget)),
|
||||
flags,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_NONE,
|
||||
"Found '%s' encoded in a %s code.",
|
||||
code->data,
|
||||
code->type);
|
||||
}
|
||||
gtk_dialog_add_buttons(
|
||||
GTK_DIALOG(dialog),
|
||||
"_Copy",
|
||||
GTK_RESPONSE_ACCEPT,
|
||||
"_Cancel",
|
||||
GTK_RESPONSE_CANCEL,
|
||||
NULL);
|
||||
|
||||
g_signal_connect(dialog, "response", G_CALLBACK(on_zbar_dialog_response), data);
|
||||
|
||||
gtk_widget_show(GTK_WIDGET(dialog));
|
||||
}
|
||||
|
||||
static void
|
||||
preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y)
|
||||
{
|
||||
GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
|
||||
int scale_factor = gtk_widget_get_scale_factor(widget);
|
||||
|
||||
// Tapped zbar result
|
||||
if (zbar_result) {
|
||||
// Transform the event coordinates to the image
|
||||
float offset_x, offset_y, size_x, size_y;
|
||||
position_preview(&offset_x, &offset_y, &size_x, &size_y);
|
||||
|
||||
int zbar_x = (x - offset_x) * scale_factor / size_x * preview_buffer_width;
|
||||
int zbar_y = (y - offset_y) * scale_factor / size_y * preview_buffer_height;
|
||||
|
||||
for (uint8_t i = 0; i < zbar_result->size; ++i) {
|
||||
MPZBarCode *code = &zbar_result->codes[i];
|
||||
|
||||
if (check_point_inside_bounds(zbar_x, zbar_y, code->bounds_x, code->bounds_y)) {
|
||||
on_zbar_code_tapped(widget, code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tapped preview image itself, try focussing
|
||||
if (has_auto_focus_start) {
|
||||
mp_io_pipeline_focus();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
size_t next_index = camera->index + 1;
|
||||
const struct mp_camera_config *next_camera =
|
||||
mp_get_camera_config(next_index);
|
||||
|
||||
if (!next_camera) {
|
||||
next_index = 0;
|
||||
next_camera = mp_get_camera_config(next_index);
|
||||
}
|
||||
|
||||
camera = next_camera;
|
||||
update_io_pipeline();
|
||||
}
|
||||
|
||||
static void
|
||||
run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
|
||||
}
|
||||
|
||||
static void
|
||||
run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||
{
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
|
||||
}
|
||||
|
||||
static void
|
||||
on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double))
|
||||
{
|
||||
set_fn(gtk_adjustment_get_value(adjustment));
|
||||
}
|
||||
|
||||
static void
|
||||
update_value(GtkAdjustment *adjustment, GtkLabel *label)
|
||||
{
|
||||
char buf[12];
|
||||
snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment));
|
||||
gtk_label_set_label(label, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool))
|
||||
{
|
||||
set_auto_fn(gtk_toggle_button_get_active(button));
|
||||
}
|
||||
|
||||
static void
|
||||
update_scale(GtkToggleButton *button, GtkScale *scale)
|
||||
{
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(scale), !gtk_toggle_button_get_active(button));
|
||||
}
|
||||
|
||||
static void
|
||||
open_controls(GtkWidget *parent, const char *title_name,
|
||||
double min_value, double max_value, double current,
|
||||
bool auto_enabled,
|
||||
void (*set_fn)(double),
|
||||
void (*set_auto_fn)(bool))
|
||||
{
|
||||
GtkBuilder *builder = gtk_builder_new_from_resource(
|
||||
"/org/postmarketos/Megapixels/controls-popover.ui");
|
||||
GtkPopover *popover = GTK_POPOVER(gtk_builder_get_object(builder, "controls"));
|
||||
GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale"));
|
||||
GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title"));
|
||||
GtkLabel *value_label = GTK_LABEL(gtk_builder_get_object(builder, "value-label"));
|
||||
GtkToggleButton *auto_button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button"));
|
||||
|
||||
gtk_label_set_label(title, title_name);
|
||||
|
||||
GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale));
|
||||
gtk_adjustment_set_lower(adjustment, min_value);
|
||||
gtk_adjustment_set_upper(adjustment, max_value);
|
||||
gtk_adjustment_set_value(adjustment, current);
|
||||
update_value(adjustment, value_label);
|
||||
|
||||
gtk_toggle_button_set_active(auto_button, auto_enabled);
|
||||
update_scale(auto_button, scale);
|
||||
|
||||
g_signal_connect(adjustment, "value-changed", G_CALLBACK(on_controls_scale_changed), set_fn);
|
||||
g_signal_connect(adjustment, "value-changed", G_CALLBACK(update_value), value_label);
|
||||
g_signal_connect(auto_button, "toggled", G_CALLBACK(on_auto_controls_toggled), set_auto_fn);
|
||||
g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale);
|
||||
|
||||
gtk_widget_set_parent(GTK_WIDGET(popover), parent);
|
||||
gtk_popover_popup(popover);
|
||||
// g_object_unref(popover);
|
||||
}
|
||||
|
||||
static void
|
||||
set_gain(double value)
|
||||
{
|
||||
if (gain != (int)value) {
|
||||
gain = value;
|
||||
update_io_pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_gain_auto(bool is_auto)
|
||||
{
|
||||
if (gain_is_manual != !is_auto) {
|
||||
gain_is_manual = !is_auto;
|
||||
update_io_pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
open_iso_controls(GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
open_controls(button, "ISO", 0, gain_max, gain, !gain_is_manual, set_gain, set_gain_auto);
|
||||
}
|
||||
|
||||
static void
|
||||
set_shutter(double value)
|
||||
{
|
||||
int new_exposure =
|
||||
(int)(value / 360.0 * camera->capture_mode.height);
|
||||
if (new_exposure != exposure) {
|
||||
exposure = new_exposure;
|
||||
update_io_pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_shutter_auto(bool is_auto)
|
||||
{
|
||||
if (exposure_is_manual != !is_auto) {
|
||||
exposure_is_manual = !is_auto;
|
||||
update_io_pipeline();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
open_shutter_controls(GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto);
|
||||
}
|
||||
|
||||
static void
|
||||
on_realize(GtkWidget *window, gpointer *data)
|
||||
{
|
||||
GtkNative *native = gtk_widget_get_native(window);
|
||||
mp_process_pipeline_init_gl(gtk_native_get_surface(native));
|
||||
|
||||
camera = mp_get_camera_config(0);
|
||||
update_io_pipeline();
|
||||
}
|
||||
|
||||
static GSimpleAction *
|
||||
create_simple_action(GtkApplication *app, const char *name, GCallback callback)
|
||||
{
|
||||
GSimpleAction *action = g_simple_action_new(name, NULL);
|
||||
g_signal_connect(action, "activate", callback, app);
|
||||
g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action));
|
||||
return action;
|
||||
}
|
||||
|
||||
static void update_ui_rotation()
|
||||
{
|
||||
if (device_rotation == 0 || device_rotation == 180) {
|
||||
// Portrait
|
||||
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL);
|
||||
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_VERTICAL);
|
||||
|
||||
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL);
|
||||
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_HORIZONTAL);
|
||||
|
||||
if (device_rotation == 0) {
|
||||
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END);
|
||||
} else {
|
||||
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END);
|
||||
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START);
|
||||
}
|
||||
} else {
|
||||
// Landscape
|
||||
gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL);
|
||||
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_HORIZONTAL);
|
||||
|
||||
gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL);
|
||||
gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_VERTICAL);
|
||||
|
||||
if (device_rotation == 90) {
|
||||
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END);
|
||||
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START);
|
||||
} else {
|
||||
gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START);
|
||||
gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GVariant *result = g_dbus_connection_call_finish(conn, res, &error);
|
||||
|
||||
if (!result) {
|
||||
printf("Failed to get display configuration: %s\n", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
GVariant *configs = g_variant_get_child_value(result, 1);
|
||||
if (g_variant_n_children(configs) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GVariant *config = g_variant_get_child_value(configs, 0);
|
||||
GVariant *rot_config = g_variant_get_child_value(config, 7);
|
||||
uint32_t rotation_index = g_variant_get_uint32(rot_config);
|
||||
|
||||
assert(rotation_index < 4);
|
||||
int new_rotation = rotation_index * 90;
|
||||
|
||||
if (new_rotation != device_rotation) {
|
||||
device_rotation = new_rotation;
|
||||
update_io_pipeline();
|
||||
update_ui_rotation();
|
||||
}
|
||||
}
|
||||
|
||||
static void update_screen_rotation(GDBusConnection *conn)
|
||||
{
|
||||
g_dbus_connection_call(conn,
|
||||
"org.gnome.Mutter.DisplayConfig",
|
||||
"/org/gnome/Mutter/DisplayConfig",
|
||||
"org.gnome.Mutter.DisplayConfig",
|
||||
"GetResources",
|
||||
NULL,
|
||||
NULL,
|
||||
G_DBUS_CALL_FLAGS_NO_AUTO_START,
|
||||
-1,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)display_config_received,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void on_screen_rotate(
|
||||
GDBusConnection *conn,
|
||||
const gchar *sender_name,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *signal_name,
|
||||
GVariant *parameters,
|
||||
gpointer user_data)
|
||||
{
|
||||
update_screen_rotation(conn);
|
||||
}
|
||||
|
||||
static void
|
||||
activate(GtkApplication *app, gpointer data)
|
||||
{
|
||||
g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
|
||||
TRUE, NULL);
|
||||
|
||||
GdkDisplay *display = gdk_display_get_default();
|
||||
GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display);
|
||||
gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels");
|
||||
|
||||
GtkCssProvider *provider = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_resource(
|
||||
provider, "/org/postmarketos/Megapixels/camera.css");
|
||||
gtk_style_context_add_provider_for_display(
|
||||
display, GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
GtkBuilder *builder = gtk_builder_new_from_resource(
|
||||
"/org/postmarketos/Megapixels/camera.ui");
|
||||
|
||||
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
|
||||
GtkWidget *iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button"));
|
||||
GtkWidget *shutter_button = GTK_WIDGET(gtk_builder_get_object(builder, "shutter-controls-button"));
|
||||
preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
|
||||
main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
|
||||
open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
|
||||
thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
|
||||
process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
|
||||
scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes"));
|
||||
preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
|
||||
preview_bottom_box = GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
|
||||
|
||||
g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
|
||||
|
||||
g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
|
||||
g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
|
||||
g_signal_connect(preview, "resize", G_CALLBACK(preview_resize), NULL);
|
||||
GtkGesture *click = gtk_gesture_click_new();
|
||||
g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL);
|
||||
gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click));
|
||||
|
||||
g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
|
||||
g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
|
||||
|
||||
// Setup actions
|
||||
create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
|
||||
create_simple_action(app, "switch-camera", G_CALLBACK(run_camera_switch_action));
|
||||
create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action));
|
||||
create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action));
|
||||
create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action));
|
||||
create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action));
|
||||
create_simple_action(app, "quit", G_CALLBACK(run_quit_action));
|
||||
|
||||
// Setup shortcuts
|
||||
const char *capture_accels[] = { "space", NULL };
|
||||
gtk_application_set_accels_for_action(app, "app.capture", capture_accels);
|
||||
|
||||
const char *quit_accels[] = { "<Ctrl>q", "<Ctrl>w", NULL };
|
||||
gtk_application_set_accels_for_action(app, "app.quit", quit_accels);
|
||||
|
||||
// Listen for phosh rotation
|
||||
GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app));
|
||||
g_dbus_connection_signal_subscribe(
|
||||
conn,
|
||||
NULL,
|
||||
"org.gnome.Mutter.DisplayConfig",
|
||||
"MonitorsChanged",
|
||||
"/org/gnome/Mutter/DisplayConfig",
|
||||
NULL,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
&on_screen_rotate,
|
||||
NULL,
|
||||
NULL);
|
||||
update_screen_rotation(conn);
|
||||
|
||||
mp_io_pipeline_start();
|
||||
|
||||
gtk_application_add_window(app, GTK_WINDOW(window));
|
||||
gtk_widget_show(window);
|
||||
}
|
||||
|
||||
static void
|
||||
shutdown(GApplication *app, gpointer data)
|
||||
{
|
||||
// Only do cleanup in development, let the OS clean up otherwise
|
||||
#ifdef DEBUG
|
||||
mp_io_pipeline_stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef RENDERDOC
|
||||
{
|
||||
void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
|
||||
if (mod)
|
||||
{
|
||||
pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
|
||||
int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
|
||||
assert(ret == 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Renderdoc not found\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mp_load_config())
|
||||
return 1;
|
||||
|
||||
setenv("LC_NUMERIC", "C", 1);
|
||||
|
||||
GtkApplication *app = gtk_application_new("org.postmarketos.Megapixels", 0);
|
||||
|
||||
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
|
||||
g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL);
|
||||
|
||||
g_application_run(G_APPLICATION(app), argc, argv);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
#include "camera_config.h"
|
||||
#include "zbar_pipeline.h"
|
||||
#include "process_pipeline.h"
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
#define MP_MAIN_THUMB_SIZE 24
|
||||
|
||||
struct mp_main_state {
|
||||
const struct mp_camera_config *camera;
|
||||
MPCameraMode mode;
|
||||
|
||||
int image_width;
|
||||
int image_height;
|
||||
|
||||
bool gain_is_manual;
|
||||
int gain;
|
||||
int gain_max;
|
||||
|
@ -23,12 +25,9 @@ struct mp_main_state {
|
|||
|
||||
void mp_main_update_state(const struct mp_main_state *state);
|
||||
|
||||
void mp_main_set_preview(cairo_surface_t *image);
|
||||
void mp_main_capture_completed(cairo_surface_t *thumb, const char *fname);
|
||||
void mp_main_set_preview(MPProcessPipelineBuffer *buffer);
|
||||
void mp_main_capture_completed(GdkTexture *thumb, const char *fname);
|
||||
|
||||
void mp_main_set_zbar_result(MPZBarScanResult *result);
|
||||
|
||||
int remap(int value, int input_min, int input_max, int output_min, int output_max);
|
||||
|
||||
void draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height,
|
||||
cairo_surface_t *surface);
|
|
@ -65,6 +65,27 @@ mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
|
|||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
unlock_mutex(GMutex *mutex)
|
||||
{
|
||||
g_mutex_unlock(mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
mp_pipeline_sync(MPPipeline *pipeline)
|
||||
{
|
||||
GMutex mutex;
|
||||
g_mutex_init(&mutex);
|
||||
g_mutex_lock(&mutex);
|
||||
|
||||
g_main_context_invoke_full(pipeline->main_context, G_PRIORITY_LOW, (GSourceFunc)unlock_mutex, &mutex, NULL);
|
||||
g_mutex_lock(&mutex);
|
||||
g_mutex_unlock(&mutex);
|
||||
|
||||
g_mutex_clear(&mutex);
|
||||
}
|
||||
|
||||
void
|
||||
mp_pipeline_free(MPPipeline *pipeline)
|
||||
{
|
||||
|
@ -80,21 +101,24 @@ mp_pipeline_free(MPPipeline *pipeline)
|
|||
|
||||
struct capture_source_args {
|
||||
MPCamera *camera;
|
||||
void (*callback)(MPImage, void *);
|
||||
void (*callback)(MPBuffer, void *);
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
static bool
|
||||
on_capture(int fd, GIOCondition condition, struct capture_source_args *args)
|
||||
{
|
||||
mp_camera_capture_image(args->camera, args->callback, args->user_data);
|
||||
MPBuffer buffer;
|
||||
if (mp_camera_capture_buffer(args->camera, &buffer)) {
|
||||
args->callback(buffer, args->user_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not thread safe
|
||||
GSource *
|
||||
mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
|
||||
void (*callback)(MPImage, void *), void *user_data)
|
||||
void (*callback)(MPBuffer, void *), void *user_data)
|
||||
{
|
||||
int video_fd = mp_camera_get_video_fd(camera);
|
||||
GSource *video_source = g_unix_fd_source_new(video_fd, G_IO_IN);
|
|
@ -11,8 +11,10 @@ typedef void (*MPPipelineCallback)(MPPipeline *, const void *);
|
|||
MPPipeline *mp_pipeline_new();
|
||||
void mp_pipeline_invoke(MPPipeline *pipeline, MPPipelineCallback callback,
|
||||
const void *data, size_t size);
|
||||
// Wait until all pending tasks have completed
|
||||
void mp_pipeline_sync(MPPipeline *pipeline);
|
||||
void mp_pipeline_free(MPPipeline *pipeline);
|
||||
|
||||
GSource *mp_pipeline_add_capture_source(MPPipeline *pipeline, MPCamera *camera,
|
||||
void (*callback)(MPImage, void *),
|
||||
void (*callback)(MPBuffer, void *),
|
||||
void *user_data);
|
|
@ -2,15 +2,18 @@
|
|||
|
||||
#include "pipeline.h"
|
||||
#include "zbar_pipeline.h"
|
||||
#include "io_pipeline.h"
|
||||
#include "main.h"
|
||||
#include "config.h"
|
||||
#include "quickpreview.h"
|
||||
#include "gles2_debayer.h"
|
||||
#include <tiffio.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <wordexp.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gl_util.h"
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define TIFFTAG_FORWARDMATRIX1 50964
|
||||
|
||||
static const float colormatrix_srgb[] = { 3.2409, -1.5373, -0.4986, -0.9692, 1.8759,
|
||||
|
@ -26,6 +29,7 @@ static volatile int frames_processed = 0;
|
|||
static volatile int frames_received = 0;
|
||||
|
||||
static const struct mp_camera_config *camera;
|
||||
static int camera_rotation;
|
||||
|
||||
static MPCameraMode mode;
|
||||
|
||||
|
@ -35,6 +39,11 @@ static int captures_remaining = 0;
|
|||
static int preview_width;
|
||||
static int preview_height;
|
||||
|
||||
static int device_rotation;
|
||||
|
||||
static int output_buffer_width = -1;
|
||||
static int output_buffer_height = -1;
|
||||
|
||||
// static bool gain_is_manual;
|
||||
static int gain;
|
||||
static int gain_max;
|
||||
|
@ -60,28 +69,18 @@ register_custom_tiff_tags(TIFF *tif)
|
|||
static bool
|
||||
find_processor(char *script)
|
||||
{
|
||||
char *xdg_config_home;
|
||||
char filename[] = "postprocess.sh";
|
||||
wordexp_t exp_result;
|
||||
|
||||
// Resolve XDG stuff
|
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
|
||||
xdg_config_home = "~/.config";
|
||||
}
|
||||
wordexp(xdg_config_home, &exp_result, 0);
|
||||
xdg_config_home = strdup(exp_result.we_wordv[0]);
|
||||
wordfree(&exp_result);
|
||||
|
||||
// Check postprocess.h in the current working directory
|
||||
sprintf(script, "%s", filename);
|
||||
// Check postprocess.sh in the current working directory
|
||||
sprintf(script, "./data/%s", filename);
|
||||
if (access(script, F_OK) != -1) {
|
||||
sprintf(script, "./%s", filename);
|
||||
sprintf(script, "./data/%s", filename);
|
||||
printf("Found postprocessor script at %s\n", script);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for a script in XDG_CONFIG_HOME
|
||||
sprintf(script, "%s/megapixels/%s", xdg_config_home, filename);
|
||||
sprintf(script, "%s/megapixels/%s", g_get_user_config_dir(), filename);
|
||||
if (access(script, F_OK) != -1) {
|
||||
printf("Found postprocessor script at %s\n", script);
|
||||
return true;
|
||||
|
@ -134,47 +133,208 @@ mp_process_pipeline_stop()
|
|||
mp_zbar_pipeline_stop();
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
process_image_for_preview(const MPImage *image)
|
||||
void
|
||||
mp_process_pipeline_sync()
|
||||
{
|
||||
uint32_t surface_width, surface_height, skip;
|
||||
quick_preview_size(&surface_width, &surface_height, &skip, preview_width,
|
||||
preview_height, image->width, image->height,
|
||||
image->pixel_format, camera->rotate);
|
||||
|
||||
cairo_surface_t *surface = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_RGB24, surface_width, surface_height);
|
||||
|
||||
uint8_t *pixels = cairo_image_surface_get_data(surface);
|
||||
|
||||
quick_preview((uint32_t *)pixels, surface_width, surface_height, image->data,
|
||||
image->width, image->height, image->pixel_format,
|
||||
camera->rotate, camera->mirrored,
|
||||
camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
|
||||
camera->blacklevel, skip);
|
||||
|
||||
// Create a thumbnail from the preview for the last capture
|
||||
cairo_surface_t *thumb = NULL;
|
||||
if (captures_remaining == 1) {
|
||||
printf("Making thumbnail\n");
|
||||
thumb = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE);
|
||||
|
||||
cairo_t *cr = cairo_create(thumb);
|
||||
draw_surface_scaled_centered(
|
||||
cr, MP_MAIN_THUMB_SIZE, MP_MAIN_THUMB_SIZE, surface);
|
||||
cairo_destroy(cr);
|
||||
mp_pipeline_sync(pipeline);
|
||||
}
|
||||
|
||||
// Pass processed preview to main and zbar
|
||||
mp_zbar_pipeline_process_image(cairo_surface_reference(surface));
|
||||
mp_main_set_preview(surface);
|
||||
#define NUM_BUFFERS 4
|
||||
|
||||
struct _MPProcessPipelineBuffer {
|
||||
GLuint texture_id;
|
||||
|
||||
_Atomic(int) refcount;
|
||||
};
|
||||
static MPProcessPipelineBuffer output_buffers[NUM_BUFFERS];
|
||||
|
||||
void
|
||||
mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf)
|
||||
{
|
||||
++buf->refcount;
|
||||
}
|
||||
|
||||
void
|
||||
mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf)
|
||||
{
|
||||
--buf->refcount;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf)
|
||||
{
|
||||
return buf->texture_id;
|
||||
}
|
||||
|
||||
static GLES2Debayer *gles2_debayer = NULL;
|
||||
|
||||
static GdkGLContext *context;
|
||||
|
||||
#define RENDERDOC
|
||||
|
||||
#ifdef RENDERDOC
|
||||
#include <renderdoc/app.h>
|
||||
extern RENDERDOC_API_1_1_2 *rdoc_api;
|
||||
#endif
|
||||
|
||||
static void
|
||||
init_gl(MPPipeline *pipeline, GdkSurface **surface)
|
||||
{
|
||||
GError *error = NULL;
|
||||
context = gdk_surface_create_gl_context(*surface, &error);
|
||||
if (context == NULL) {
|
||||
printf("Failed to initialize OpenGL context: %s\n", error->message);
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
gdk_gl_context_set_use_es(context, true);
|
||||
gdk_gl_context_set_required_version(context, 2, 0);
|
||||
gdk_gl_context_set_forward_compatible(context, false);
|
||||
#ifdef DEBUG
|
||||
gdk_gl_context_set_debug_enabled(context, true);
|
||||
#else
|
||||
gdk_gl_context_set_debug_enabled(context, false);
|
||||
#endif
|
||||
|
||||
gdk_gl_context_realize(context, &error);
|
||||
if (error != NULL) {
|
||||
printf("Failed to create OpenGL context: %s\n", error->message);
|
||||
g_clear_object(&context);
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
gdk_gl_context_make_current(context);
|
||||
check_gl();
|
||||
|
||||
// Make a VAO for OpenGL
|
||||
if (!gdk_gl_context_get_use_es(context)) {
|
||||
GLuint vao;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
check_gl();
|
||||
}
|
||||
|
||||
gles2_debayer = gles2_debayer_new(MP_PIXEL_FMT_BGGR8);
|
||||
check_gl();
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
check_gl();
|
||||
|
||||
gles2_debayer_use(gles2_debayer);
|
||||
|
||||
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
|
||||
glGenTextures(1, &output_buffers[i].texture_id);
|
||||
glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
gboolean is_es = gdk_gl_context_get_use_es(context);
|
||||
int major, minor;
|
||||
gdk_gl_context_get_version(context, &major, &minor);
|
||||
|
||||
printf("Initialized %s %d.%d\n", is_es ? "OpenGL ES" : "OpenGL", major, minor);
|
||||
}
|
||||
|
||||
void
|
||||
mp_process_pipeline_init_gl(GdkSurface *surface)
|
||||
{
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback) init_gl, &surface, sizeof(GdkSurface *));
|
||||
}
|
||||
|
||||
static GdkTexture *
|
||||
process_image_for_preview(const uint8_t *image)
|
||||
{
|
||||
#ifdef PROFILE_DEBAYER
|
||||
clock_t t1 = clock();
|
||||
#endif
|
||||
|
||||
// Pick an available buffer
|
||||
MPProcessPipelineBuffer *output_buffer = NULL;
|
||||
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
|
||||
if (output_buffers[i].refcount == 0) {
|
||||
output_buffer = &output_buffers[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (output_buffer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
assert(output_buffer != NULL);
|
||||
|
||||
#ifdef RENDERDOC
|
||||
if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
|
||||
#endif
|
||||
|
||||
// Copy image to a GL texture. TODO: This can be avoided
|
||||
GLuint input_texture;
|
||||
glGenTextures(1, &input_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, input_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, mode.width, mode.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
|
||||
check_gl();
|
||||
|
||||
gles2_debayer_process(
|
||||
gles2_debayer, output_buffer->texture_id, input_texture);
|
||||
check_gl();
|
||||
|
||||
glFinish();
|
||||
|
||||
glDeleteTextures(1, &input_texture);
|
||||
|
||||
#ifdef PROFILE_DEBAYER
|
||||
clock_t t2 = clock();
|
||||
printf("process_image_for_preview %fms\n", (float)(t2 - t1) / CLOCKS_PER_SEC * 1000);
|
||||
#endif
|
||||
|
||||
#ifdef RENDERDOC
|
||||
if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
|
||||
#endif
|
||||
|
||||
mp_process_pipeline_buffer_ref(output_buffer);
|
||||
mp_main_set_preview(output_buffer);
|
||||
|
||||
// Create a thumbnail from the preview for the last capture
|
||||
GdkTexture *thumb = NULL;
|
||||
if (captures_remaining == 1) {
|
||||
printf("Making thumbnail\n");
|
||||
|
||||
size_t size = output_buffer_width * output_buffer_height * sizeof(uint32_t);
|
||||
|
||||
uint32_t *data = g_malloc_n(size, 1);
|
||||
|
||||
glReadPixels(0, 0, output_buffer_width, output_buffer_height, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
check_gl();
|
||||
|
||||
// Flip vertically
|
||||
for (size_t y = 0; y < output_buffer_height / 2; ++y) {
|
||||
for (size_t x = 0; x < output_buffer_width; ++x) {
|
||||
uint32_t tmp = data[(output_buffer_height - y - 1) * output_buffer_width + x];
|
||||
data[(output_buffer_height - y - 1) * output_buffer_width + x] = data[y * output_buffer_width + x];
|
||||
data[y * output_buffer_width + x] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
thumb = gdk_memory_texture_new(
|
||||
output_buffer_width,
|
||||
output_buffer_height,
|
||||
GDK_MEMORY_R8G8B8A8,
|
||||
g_bytes_new_take(data, size),
|
||||
output_buffer_width * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
static void
|
||||
process_image_for_capture(const MPImage *image, int count)
|
||||
process_image_for_capture(const uint8_t *image, int count)
|
||||
{
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
@ -193,21 +353,21 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
|
||||
// Define TIFF thumbnail
|
||||
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 1);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width >> 4);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height >> 4);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width >> 4);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height >> 4);
|
||||
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
|
||||
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
|
||||
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
|
||||
TIFFSetField(tif, TIFFTAG_MAKE, mp_get_device_make());
|
||||
TIFFSetField(tif, TIFFTAG_MODEL, mp_get_device_model());
|
||||
uint16_t orientation;
|
||||
if (camera->rotate == 0) {
|
||||
if (camera_rotation == 0) {
|
||||
orientation = camera->mirrored ? ORIENTATION_TOPRIGHT :
|
||||
ORIENTATION_TOPLEFT;
|
||||
} else if (camera->rotate == 90) {
|
||||
} else if (camera_rotation == 90) {
|
||||
orientation = camera->mirrored ? ORIENTATION_RIGHTBOT :
|
||||
ORIENTATION_LEFTBOT;
|
||||
} else if (camera->rotate == 180) {
|
||||
} else if (camera_rotation == 180) {
|
||||
orientation = camera->mirrored ? ORIENTATION_BOTLEFT :
|
||||
ORIENTATION_BOTRIGHT;
|
||||
} else {
|
||||
|
@ -241,8 +401,8 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
// Write black thumbnail, only windows uses this
|
||||
{
|
||||
unsigned char *buf =
|
||||
(unsigned char *)calloc(1, (int)image->width >> 4);
|
||||
for (int row = 0; row < (image->height >> 4); row++) {
|
||||
(unsigned char *)calloc(1, (int)mode.width >> 4);
|
||||
for (int row = 0; row < (mode.height >> 4); row++) {
|
||||
TIFFWriteScanline(tif, buf, row, 0);
|
||||
}
|
||||
free(buf);
|
||||
|
@ -251,8 +411,8 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
|
||||
// Define main photo
|
||||
TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, image->width);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, image->height);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, mode.width);
|
||||
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, mode.height);
|
||||
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
|
||||
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);
|
||||
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
|
||||
|
@ -274,9 +434,9 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
TIFFCheckpointDirectory(tif);
|
||||
printf("Writing frame to %s\n", fname);
|
||||
|
||||
unsigned char *pLine = (unsigned char *)malloc(image->width);
|
||||
for (int row = 0; row < image->height; row++) {
|
||||
TIFFWriteScanline(tif, image->data + (row * image->width), row, 0);
|
||||
unsigned char *pLine = (unsigned char *)malloc(mode.width);
|
||||
for (int row = 0; row < mode.height; row++) {
|
||||
TIFFWriteScanline(tif, (void *) image + (row * mode.width), row, 0);
|
||||
}
|
||||
free(pLine);
|
||||
TIFFWriteDirectory(tif);
|
||||
|
@ -293,7 +453,7 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
TIFFSetField(tif, EXIFTAG_EXPOSURETIME,
|
||||
(mode.frame_interval.numerator /
|
||||
(float)mode.frame_interval.denominator) /
|
||||
((float)image->height / (float)exposure));
|
||||
((float)mode.height / (float)exposure));
|
||||
uint16_t isospeed[1];
|
||||
isospeed[0] = (uint16_t)remap(gain - 1, 0, gain_max, camera->iso_min,
|
||||
camera->iso_max);
|
||||
|
@ -325,7 +485,7 @@ process_image_for_capture(const MPImage *image, int count)
|
|||
}
|
||||
|
||||
static void
|
||||
post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thumb)
|
||||
post_process_finished(GSubprocess *proc, GAsyncResult *res, GdkTexture *thumb)
|
||||
{
|
||||
char *stdout;
|
||||
g_subprocess_communicate_utf8_finish(proc, res, &stdout, NULL, NULL);
|
||||
|
@ -348,7 +508,7 @@ post_process_finished(GSubprocess *proc, GAsyncResult *res, cairo_surface_t *thu
|
|||
}
|
||||
|
||||
static void
|
||||
process_capture_burst(cairo_surface_t *thumb)
|
||||
process_capture_burst(GdkTexture *thumb)
|
||||
{
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
|
@ -401,11 +561,27 @@ process_capture_burst(cairo_surface_t *thumb)
|
|||
}
|
||||
|
||||
static void
|
||||
process_image(MPPipeline *pipeline, const MPImage *image)
|
||||
process_image(MPPipeline *pipeline, const MPBuffer *buffer)
|
||||
{
|
||||
assert(image->width == mode.width && image->height == mode.height);
|
||||
#ifdef PROFILE_PROCESS
|
||||
clock_t t1 = clock();
|
||||
#endif
|
||||
|
||||
cairo_surface_t *thumb = process_image_for_preview(image);
|
||||
size_t size =
|
||||
mp_pixel_format_width_to_bytes(mode.pixel_format, mode.width) *
|
||||
mode.height;
|
||||
uint8_t *image = malloc(size);
|
||||
memcpy(image, buffer->data, size);
|
||||
mp_io_pipeline_release_buffer(buffer->index);
|
||||
|
||||
MPZBarImage *zbar_image = mp_zbar_image_new(image, mode.pixel_format, mode.width, mode.height, camera_rotation, camera->mirrored);
|
||||
mp_zbar_pipeline_process_image(mp_zbar_image_ref(zbar_image));
|
||||
|
||||
#ifdef PROFILE_PROCESS
|
||||
clock_t t2 = clock();
|
||||
#endif
|
||||
|
||||
GdkTexture *thumb = process_image_for_preview(image);
|
||||
|
||||
if (captures_remaining > 0) {
|
||||
int count = burst_length - captures_remaining;
|
||||
|
@ -423,28 +599,35 @@ process_image(MPPipeline *pipeline, const MPImage *image)
|
|||
assert(!thumb);
|
||||
}
|
||||
|
||||
free(image->data);
|
||||
mp_zbar_image_unref(zbar_image);
|
||||
|
||||
++frames_processed;
|
||||
if (captures_remaining == 0) {
|
||||
is_capturing = false;
|
||||
}
|
||||
|
||||
#ifdef PROFILE_PROCESS
|
||||
clock_t t3 = clock();
|
||||
printf("process_image %fms, step 1:%fms, step 2:%fms\n",
|
||||
(float)(t3 - t1) / CLOCKS_PER_SEC * 1000,
|
||||
(float)(t2 - t1) / CLOCKS_PER_SEC * 1000,
|
||||
(float)(t3 - t2) / CLOCKS_PER_SEC * 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
mp_process_pipeline_process_image(MPImage image)
|
||||
mp_process_pipeline_process_image(MPBuffer buffer)
|
||||
{
|
||||
// If we haven't processed the previous frame yet, drop this one
|
||||
if (frames_received != frames_processed && !is_capturing) {
|
||||
printf("Dropped frame at capture\n");
|
||||
free(image.data);
|
||||
mp_io_pipeline_release_buffer(buffer.index);
|
||||
return;
|
||||
}
|
||||
|
||||
++frames_received;
|
||||
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
|
||||
sizeof(MPImage));
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &buffer,
|
||||
sizeof(MPBuffer));
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -472,17 +655,60 @@ mp_process_pipeline_capture()
|
|||
mp_pipeline_invoke(pipeline, capture, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
on_output_changed()
|
||||
{
|
||||
output_buffer_width = mode.width / 2;
|
||||
output_buffer_height = mode.height / 2;
|
||||
|
||||
if (camera->rotate != 0 || camera->rotate != 180) {
|
||||
int tmp = output_buffer_width;
|
||||
output_buffer_width = output_buffer_height;
|
||||
output_buffer_height = tmp;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < NUM_BUFFERS; ++i) {
|
||||
glBindTexture(GL_TEXTURE_2D, output_buffers[i].texture_id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, output_buffer_width, output_buffer_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
gles2_debayer_configure(
|
||||
gles2_debayer,
|
||||
output_buffer_width, output_buffer_height,
|
||||
mode.width, mode.height,
|
||||
camera->rotate, camera->mirrored,
|
||||
camera->previewmatrix[0] == 0 ? NULL : camera->previewmatrix,
|
||||
camera->blacklevel);
|
||||
}
|
||||
|
||||
static int
|
||||
mod(int a, int b)
|
||||
{
|
||||
int r = a % b;
|
||||
return r < 0 ? r + b : r;
|
||||
}
|
||||
|
||||
static void
|
||||
update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state)
|
||||
{
|
||||
const bool output_changed =
|
||||
!mp_camera_mode_is_equivalent(&mode, &state->mode)
|
||||
|| preview_width != state->preview_width
|
||||
|| preview_height != state->preview_height
|
||||
|| device_rotation != state->device_rotation;
|
||||
|
||||
camera = state->camera;
|
||||
mode = state->mode;
|
||||
|
||||
burst_length = state->burst_length;
|
||||
|
||||
preview_width = state->preview_width;
|
||||
preview_height = state->preview_height;
|
||||
|
||||
device_rotation = state->device_rotation;
|
||||
|
||||
burst_length = state->burst_length;
|
||||
|
||||
// gain_is_manual = state->gain_is_manual;
|
||||
gain = state->gain;
|
||||
gain_max = state->gain_max;
|
||||
|
@ -490,9 +716,17 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state
|
|||
exposure_is_manual = state->exposure_is_manual;
|
||||
exposure = state->exposure;
|
||||
|
||||
if (output_changed) {
|
||||
camera_rotation = mod(camera->rotate - device_rotation, 360);
|
||||
|
||||
on_output_changed();
|
||||
}
|
||||
|
||||
struct mp_main_state main_state = {
|
||||
.camera = camera,
|
||||
.mode = mode,
|
||||
.image_width = output_buffer_width,
|
||||
.image_height = output_buffer_height,
|
||||
.gain_is_manual = state->gain_is_manual,
|
||||
.gain = gain,
|
||||
.gain_max = gain_max,
|
||||
|
@ -510,3 +744,6 @@ mp_process_pipeline_update_state(const struct mp_process_pipeline_state *new_sta
|
|||
mp_pipeline_invoke(pipeline, (MPPipelineCallback)update_state, new_state,
|
||||
sizeof(struct mp_process_pipeline_state));
|
||||
}
|
||||
|
||||
// GTK4 seems to require this
|
||||
void pango_fc_font_get_languages() {}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "camera_config.h"
|
||||
|
||||
typedef struct _GdkSurface GdkSurface;
|
||||
|
||||
struct mp_process_pipeline_state {
|
||||
const struct mp_camera_config *camera;
|
||||
MPCameraMode mode;
|
||||
|
@ -11,6 +13,8 @@ struct mp_process_pipeline_state {
|
|||
int preview_width;
|
||||
int preview_height;
|
||||
|
||||
int device_rotation;
|
||||
|
||||
bool gain_is_manual;
|
||||
int gain;
|
||||
int gain_max;
|
||||
|
@ -24,7 +28,16 @@ struct mp_process_pipeline_state {
|
|||
|
||||
void mp_process_pipeline_start();
|
||||
void mp_process_pipeline_stop();
|
||||
void mp_process_pipeline_sync();
|
||||
|
||||
void mp_process_pipeline_process_image(MPImage image);
|
||||
void mp_process_pipeline_init_gl(GdkSurface *window);
|
||||
|
||||
void mp_process_pipeline_process_image(MPBuffer buffer);
|
||||
void mp_process_pipeline_capture();
|
||||
void mp_process_pipeline_update_state(const struct mp_process_pipeline_state *state);
|
||||
|
||||
typedef struct _MPProcessPipelineBuffer MPProcessPipelineBuffer;
|
||||
|
||||
void mp_process_pipeline_buffer_ref(MPProcessPipelineBuffer *buf);
|
||||
void mp_process_pipeline_buffer_unref(MPProcessPipelineBuffer *buf);
|
||||
uint32_t mp_process_pipeline_buffer_get_texture_id(MPProcessPipelineBuffer *buf);
|
|
@ -0,0 +1,688 @@
|
|||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019-2021 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||
//
|
||||
|
||||
#if !defined(RENDERDOC_NO_STDINT)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||
#define RENDERDOC_CC __cdecl
|
||||
#elif defined(__linux__)
|
||||
#define RENDERDOC_CC
|
||||
#elif defined(__APPLE__)
|
||||
#define RENDERDOC_CC
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants not used directly in below API
|
||||
|
||||
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||
// information can be found to match up with a stripped shader.
|
||||
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||
// RENDERDOC_ShaderDebugMagicValue_value
|
||||
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||
{ \
|
||||
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||
{ \
|
||||
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc capture options
|
||||
//
|
||||
|
||||
typedef enum RENDERDOC_CaptureOption {
|
||||
// Allow the application to enable vsync
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable vsync at will
|
||||
// 0 - vsync is force disabled
|
||||
eRENDERDOC_Option_AllowVSync = 0,
|
||||
|
||||
// Allow the application to enable fullscreen
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable fullscreen at will
|
||||
// 0 - fullscreen is force disabled
|
||||
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||
|
||||
// Record API debugging events and messages
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enable built-in API debugging features and records the results into
|
||||
// the capture, which is matched up with events on replay
|
||||
// 0 - no API debugging is forcibly enabled
|
||||
eRENDERDOC_Option_APIValidation = 2,
|
||||
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||
|
||||
// Capture CPU callstacks for API events
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enables capturing of callstacks
|
||||
// 0 - no callstacks are captured
|
||||
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||
|
||||
// When capturing CPU callstacks, only capture them from drawcalls.
|
||||
// This option does nothing without the above option being enabled
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Only captures callstacks for drawcall type API events.
|
||||
// Ignored if CaptureCallstacks is disabled
|
||||
// 0 - Callstacks, if enabled, are captured for every event.
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||
|
||||
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||
// creating or injecting into a process, before continuing to allow it to run.
|
||||
//
|
||||
// 0 indicates no delay, and the process will run immediately after injection
|
||||
//
|
||||
// Default - 0 seconds
|
||||
//
|
||||
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||
|
||||
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||
// to a marker value to catch use of uninitialised memory.
|
||||
//
|
||||
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Verify buffer access
|
||||
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||
// RenderDoc.
|
||||
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||
|
||||
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||
// previously always enabled
|
||||
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||
|
||||
// Hooks any system API calls that create child processes, and injects
|
||||
// RenderDoc into them recursively with the same options.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Hooks into spawned child processes
|
||||
// 0 - Child processes are not hooked by RenderDoc
|
||||
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||
|
||||
// By default RenderDoc only includes resources in the final capture necessary
|
||||
// for that frame, this allows you to override that behaviour.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - all live resources at the time of capture are included in the capture
|
||||
// and available for inspection
|
||||
// 0 - only the resources referenced by the captured frame are included
|
||||
eRENDERDOC_Option_RefAllResources = 8,
|
||||
|
||||
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
// getting it will be ignored, to allow compatibility with older versions.
|
||||
// In v1.1 the option acts as if it's always enabled.
|
||||
//
|
||||
// By default RenderDoc skips saving initial states for resources where the
|
||||
// previous contents don't appear to be used, assuming that writes before
|
||||
// reads indicate previous contents aren't used.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||
// they are later overwritten or cleared before being used.
|
||||
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||
// appear as black or empty data.
|
||||
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||
|
||||
// In APIs that allow for the recording of command lists to be replayed later,
|
||||
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
// triggered, to reduce overheads. This means any command lists recorded once
|
||||
// and replayed many times will not be available and may cause a failure to
|
||||
// capture.
|
||||
//
|
||||
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
// and always capture all command lists since the API is heavily oriented
|
||||
// around it and the overheads have been reduced by API design.
|
||||
//
|
||||
// 1 - All command lists are captured from the start of the application
|
||||
// 0 - Command lists are only captured if their recording begins during
|
||||
// the period when a frame capture is in progress.
|
||||
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||
|
||||
// Mute API debugging output when the API validation mode option is enabled
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - Mute any API debug messages from being displayed or passed through
|
||||
// 0 - API debugging is displayed as normal
|
||||
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||
|
||||
// Option to allow vendor extensions to be used even when they may be
|
||||
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
//
|
||||
// Default - inactive
|
||||
//
|
||||
// No values are documented, this option should only be used when absolutely
|
||||
// necessary as directed by a RenderDoc developer.
|
||||
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||
|
||||
} RENDERDOC_CaptureOption;
|
||||
|
||||
// Sets an option that controls how RenderDoc behaves on capture.
|
||||
//
|
||||
// Returns 1 if the option and value are valid
|
||||
// Returns 0 if either is invalid and the option is unchanged
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||
|
||||
// Gets the current value of an option as a uint32_t
|
||||
//
|
||||
// If the option is invalid, 0xffffffff is returned
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
// Gets the current value of an option as a float
|
||||
//
|
||||
// If the option is invalid, -FLT_MAX is returned
|
||||
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
typedef enum RENDERDOC_InputButton {
|
||||
// '0' - '9' matches ASCII values
|
||||
eRENDERDOC_Key_0 = 0x30,
|
||||
eRENDERDOC_Key_1 = 0x31,
|
||||
eRENDERDOC_Key_2 = 0x32,
|
||||
eRENDERDOC_Key_3 = 0x33,
|
||||
eRENDERDOC_Key_4 = 0x34,
|
||||
eRENDERDOC_Key_5 = 0x35,
|
||||
eRENDERDOC_Key_6 = 0x36,
|
||||
eRENDERDOC_Key_7 = 0x37,
|
||||
eRENDERDOC_Key_8 = 0x38,
|
||||
eRENDERDOC_Key_9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
eRENDERDOC_Key_A = 0x41,
|
||||
eRENDERDOC_Key_B = 0x42,
|
||||
eRENDERDOC_Key_C = 0x43,
|
||||
eRENDERDOC_Key_D = 0x44,
|
||||
eRENDERDOC_Key_E = 0x45,
|
||||
eRENDERDOC_Key_F = 0x46,
|
||||
eRENDERDOC_Key_G = 0x47,
|
||||
eRENDERDOC_Key_H = 0x48,
|
||||
eRENDERDOC_Key_I = 0x49,
|
||||
eRENDERDOC_Key_J = 0x4A,
|
||||
eRENDERDOC_Key_K = 0x4B,
|
||||
eRENDERDOC_Key_L = 0x4C,
|
||||
eRENDERDOC_Key_M = 0x4D,
|
||||
eRENDERDOC_Key_N = 0x4E,
|
||||
eRENDERDOC_Key_O = 0x4F,
|
||||
eRENDERDOC_Key_P = 0x50,
|
||||
eRENDERDOC_Key_Q = 0x51,
|
||||
eRENDERDOC_Key_R = 0x52,
|
||||
eRENDERDOC_Key_S = 0x53,
|
||||
eRENDERDOC_Key_T = 0x54,
|
||||
eRENDERDOC_Key_U = 0x55,
|
||||
eRENDERDOC_Key_V = 0x56,
|
||||
eRENDERDOC_Key_W = 0x57,
|
||||
eRENDERDOC_Key_X = 0x58,
|
||||
eRENDERDOC_Key_Y = 0x59,
|
||||
eRENDERDOC_Key_Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||
|
||||
eRENDERDOC_Key_Divide,
|
||||
eRENDERDOC_Key_Multiply,
|
||||
eRENDERDOC_Key_Subtract,
|
||||
eRENDERDOC_Key_Plus,
|
||||
|
||||
eRENDERDOC_Key_F1,
|
||||
eRENDERDOC_Key_F2,
|
||||
eRENDERDOC_Key_F3,
|
||||
eRENDERDOC_Key_F4,
|
||||
eRENDERDOC_Key_F5,
|
||||
eRENDERDOC_Key_F6,
|
||||
eRENDERDOC_Key_F7,
|
||||
eRENDERDOC_Key_F8,
|
||||
eRENDERDOC_Key_F9,
|
||||
eRENDERDOC_Key_F10,
|
||||
eRENDERDOC_Key_F11,
|
||||
eRENDERDOC_Key_F12,
|
||||
|
||||
eRENDERDOC_Key_Home,
|
||||
eRENDERDOC_Key_End,
|
||||
eRENDERDOC_Key_Insert,
|
||||
eRENDERDOC_Key_Delete,
|
||||
eRENDERDOC_Key_PageUp,
|
||||
eRENDERDOC_Key_PageDn,
|
||||
|
||||
eRENDERDOC_Key_Backspace,
|
||||
eRENDERDOC_Key_Tab,
|
||||
eRENDERDOC_Key_PrtScrn,
|
||||
eRENDERDOC_Key_Pause,
|
||||
|
||||
eRENDERDOC_Key_Max,
|
||||
} RENDERDOC_InputButton;
|
||||
|
||||
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||
//
|
||||
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
// Sets which key or keys can be used to capture the next frame
|
||||
//
|
||||
// If keys is NULL or num is 0, captures keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
typedef enum RENDERDOC_OverlayBits {
|
||||
// This single bit controls whether the overlay is enabled or disabled globally
|
||||
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||
|
||||
// Show the average framerate over several seconds as well as min/max
|
||||
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||
|
||||
// Show the current frame number
|
||||
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||
|
||||
// Show a list of recent captures, and how many captures have been made
|
||||
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||
|
||||
// Default values for the overlay mask
|
||||
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||
|
||||
// Enable all bits
|
||||
eRENDERDOC_Overlay_All = ~0U,
|
||||
|
||||
// Disable all bits
|
||||
eRENDERDOC_Overlay_None = 0,
|
||||
} RENDERDOC_OverlayBits;
|
||||
|
||||
// returns the overlay bits that have been set
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
|
||||
// sets the overlay bits with an and & or mask
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||
|
||||
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||
//
|
||||
// Note: that this can only work correctly if done immediately after
|
||||
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||
// after any API functions have been called, and there is still no guarantee of
|
||||
// success.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||
|
||||
// This function will unload RenderDoc's crash handler.
|
||||
//
|
||||
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||
// intercede, you can call this function to unload it and any unhandled
|
||||
// exceptions will pass to the next handler.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
|
||||
|
||||
// Sets the capture file path template
|
||||
//
|
||||
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||
// and where they will be saved.
|
||||
//
|
||||
// Any extension is stripped off the path, and captures are saved in the directory
|
||||
// specified, and named with the filename and the frame number appended. If the
|
||||
// directory does not exist it will be created, including any parent directories.
|
||||
//
|
||||
// If pathtemplate is NULL, the template will remain unchanged
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// SetCaptureFilePathTemplate("my_captures/example");
|
||||
//
|
||||
// Capture #1 -> my_captures/example_frame123.rdc
|
||||
// Capture #2 -> my_captures/example_frame456.rdc
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||
|
||||
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||
|
||||
// returns the number of captures that have been made
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
|
||||
|
||||
// This function returns the details of a capture, by index. New captures are added
|
||||
// to the end of the list.
|
||||
//
|
||||
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||
// pathlength will be written with the length in bytes of the filename string
|
||||
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||
//
|
||||
// Any of the parameters can be NULL and they'll be skipped.
|
||||
//
|
||||
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||
// If the index is invalid, the values will be unchanged
|
||||
//
|
||||
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||
// capture path may not exist anymore.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||
uint32_t *pathlength, uint64_t *timestamp);
|
||||
|
||||
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||
// UI program when opening.
|
||||
//
|
||||
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||
// the most recent capture file created made will be used instead.
|
||||
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||
//
|
||||
// Any existing comments will be overwritten.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||
const char *comments);
|
||||
|
||||
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||
// as the same function pointer type.
|
||||
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||
|
||||
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||
// into the running application.
|
||||
//
|
||||
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||
// to connect to this application
|
||||
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||
// if cmdline is NULL, the command line will be empty.
|
||||
//
|
||||
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||
const char *cmdline);
|
||||
|
||||
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||
// ignored and the others will be filled out.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Capturing functions
|
||||
//
|
||||
|
||||
// A device pointer is a pointer to the API's root handle.
|
||||
//
|
||||
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||
typedef void *RENDERDOC_DevicePointer;
|
||||
|
||||
// A window handle is the OS's native window handle
|
||||
//
|
||||
// This would be an HWND, GLXDrawable, etc
|
||||
typedef void *RENDERDOC_WindowHandle;
|
||||
|
||||
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||
//
|
||||
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||
//
|
||||
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||
// indirect once.
|
||||
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||
|
||||
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||
// respond to keypresses. Neither parameter can be NULL
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// capture the next frame on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
|
||||
|
||||
// capture the next N frames on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||
|
||||
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||
//
|
||||
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||
// is undefined which one will be captured.
|
||||
//
|
||||
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||
// a device pointer or leave it NULL as above.
|
||||
|
||||
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||
//
|
||||
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||
// this will do nothing.
|
||||
//
|
||||
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||
// even on separate devices and/oror windows.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||
//
|
||||
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
|
||||
|
||||
// Ends capturing immediately.
|
||||
//
|
||||
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||
//
|
||||
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||
// was in progress
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API versions
|
||||
//
|
||||
|
||||
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||
//
|
||||
// MAJOR version is incremented when incompatible API changes happen.
|
||||
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||
//
|
||||
// Note that this means the API returned can be higher than the one you might have requested.
|
||||
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||
typedef enum RENDERDOC_Version {
|
||||
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||
} RENDERDOC_Version;
|
||||
|
||||
// API version changelog:
|
||||
//
|
||||
// 1.0.0 - initial release
|
||||
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||
// function pointer is added to the end of the struct, the original layout is identical
|
||||
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||
// replay/remote server concept in replay UI)
|
||||
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||
// branch.
|
||||
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||
// displayed in the UI program on load.
|
||||
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||
// Should be used at the user's own risk.
|
||||
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||
// 0xdddddddd of uninitialised buffer contents.
|
||||
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||
// capturing without saving anything to disk.
|
||||
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||
|
||||
typedef struct RENDERDOC_API_1_4_1
|
||||
{
|
||||
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||
|
||||
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||
|
||||
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||
|
||||
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
pRENDERDOC_Shutdown Shutdown;
|
||||
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||
};
|
||||
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||
|
||||
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||
};
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||
};
|
||||
|
||||
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||
pRENDERDOC_GetCapture GetCapture;
|
||||
|
||||
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||
|
||||
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||
// This union allows old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||
// current name
|
||||
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||
};
|
||||
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||
|
||||
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||
|
||||
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||
|
||||
// new function in 1.1.0
|
||||
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||
|
||||
// new function in 1.2.0
|
||||
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||
|
||||
// new function in 1.4.0
|
||||
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||
} RENDERDOC_API_1_4_1;
|
||||
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0;
|
||||
typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API entry point
|
||||
//
|
||||
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||
//
|
||||
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||
//
|
||||
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||
// any API work, since some configuration functionality etc has to be done also before
|
||||
// initialising any APIs.
|
||||
//
|
||||
// Parameters:
|
||||
// version is a single value from the RENDERDOC_Version above.
|
||||
//
|
||||
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||
// pointers.
|
||||
//
|
||||
// Returns:
|
||||
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||
//
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
|
@ -6,6 +6,17 @@
|
|||
#include <zbar.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct _MPZBarImage {
|
||||
uint8_t *data;
|
||||
MPPixelFormat pixel_format;
|
||||
int width;
|
||||
int height;
|
||||
int rotation;
|
||||
bool mirrored;
|
||||
|
||||
_Atomic int ref_count;
|
||||
};
|
||||
|
||||
static MPPipeline *pipeline;
|
||||
|
||||
static volatile int frames_processed = 0;
|
||||
|
@ -33,7 +44,8 @@ mp_zbar_pipeline_stop()
|
|||
mp_pipeline_free(pipeline);
|
||||
}
|
||||
|
||||
static bool is_3d_code(zbar_symbol_type_t type)
|
||||
static bool
|
||||
is_3d_code(zbar_symbol_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case ZBAR_EAN2:
|
||||
|
@ -62,9 +74,41 @@ static bool is_3d_code(zbar_symbol_type_t type)
|
|||
}
|
||||
}
|
||||
|
||||
static MPZBarCode
|
||||
process_symbol(const zbar_symbol_t *symbol)
|
||||
static inline void
|
||||
map_coords(int *x, int *y, int width, int height, int rotation, bool mirrored)
|
||||
{
|
||||
int x_r, y_r;
|
||||
if (rotation == 0) {
|
||||
x_r = *x;
|
||||
y_r = *y;
|
||||
} else if (rotation == 90) {
|
||||
x_r = *y;
|
||||
y_r = height - *x - 1;
|
||||
} else if (rotation == 270) {
|
||||
x_r = width - *y - 1;
|
||||
y_r = *x;
|
||||
} else {
|
||||
x_r = width - *x - 1;
|
||||
y_r = height - *y - 1;
|
||||
}
|
||||
|
||||
if (mirrored) {
|
||||
x_r = width - x_r - 1;
|
||||
}
|
||||
|
||||
*x = x_r;
|
||||
*y = y_r;
|
||||
}
|
||||
|
||||
static MPZBarCode
|
||||
process_symbol(const MPZBarImage *image, int width, int height, const zbar_symbol_t *symbol)
|
||||
{
|
||||
if (image->rotation == 90 || image->rotation == 270) {
|
||||
int tmp = width;
|
||||
width = height;
|
||||
height = tmp;
|
||||
}
|
||||
|
||||
MPZBarCode code;
|
||||
|
||||
unsigned loc_size = zbar_symbol_get_loc_size(symbol);
|
||||
|
@ -100,6 +144,10 @@ process_symbol(const zbar_symbol_t *symbol)
|
|||
code.bounds_y[3] = max_y;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
map_coords(&code.bounds_x[i], &code.bounds_y[i], width, height, image->rotation, image->mirrored);
|
||||
}
|
||||
|
||||
const char *data = zbar_symbol_get_data(symbol);
|
||||
unsigned int data_size = zbar_symbol_get_data_length(symbol);
|
||||
code.type = zbar_get_symbol_name(type);
|
||||
|
@ -110,25 +158,33 @@ process_symbol(const zbar_symbol_t *symbol)
|
|||
}
|
||||
|
||||
static void
|
||||
process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
|
||||
process_image(MPPipeline *pipeline, MPZBarImage **_image)
|
||||
{
|
||||
cairo_surface_t *surface = *_surface;
|
||||
MPZBarImage *image = *_image;
|
||||
|
||||
int width = cairo_image_surface_get_width(surface);
|
||||
int height = cairo_image_surface_get_height(surface);
|
||||
const uint32_t *surface_data = (const uint32_t *)cairo_image_surface_get_data(surface);
|
||||
assert(image->pixel_format == MP_PIXEL_FMT_BGGR8
|
||||
|| image->pixel_format == MP_PIXEL_FMT_GBRG8
|
||||
|| image->pixel_format == MP_PIXEL_FMT_GRBG8
|
||||
|| image->pixel_format == MP_PIXEL_FMT_RGGB8);
|
||||
|
||||
// Create a grayscale image for scanning from the current preview.
|
||||
// Rotate/mirror correctly.
|
||||
int width = image->width / 2;
|
||||
int height = image->height / 2;
|
||||
|
||||
// Create a grayscale image for scanning from the current preview
|
||||
uint8_t *data = malloc(width * height * sizeof(uint8_t));
|
||||
for (size_t i = 0; i < width * height; ++i) {
|
||||
data[i] = (surface_data[i] >> 16) & 0xff;
|
||||
size_t i = 0;
|
||||
for (int y = 0; y < image->height; y += 2) {
|
||||
for (int x = 0; x < image->width; x += 2) {
|
||||
data[++i] = image->data[x + image->width * y];
|
||||
}
|
||||
}
|
||||
|
||||
// Create image for zbar
|
||||
zbar_image_t *zbar_image = zbar_image_create();
|
||||
zbar_image_set_format(zbar_image, zbar_fourcc('Y', '8', '0', '0'));
|
||||
zbar_image_set_size(zbar_image, width, height);
|
||||
zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), zbar_image_free_data);
|
||||
zbar_image_set_data(zbar_image, data, width * height * sizeof(uint8_t), NULL);
|
||||
|
||||
int res = zbar_scan_image(scanner, zbar_image);
|
||||
assert(res >= 0);
|
||||
|
@ -140,7 +196,7 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
|
|||
const zbar_symbol_t *symbol = zbar_image_first_symbol(zbar_image);
|
||||
for (int i = 0; i < MIN(res, 8); ++i) {
|
||||
assert(symbol != NULL);
|
||||
result->codes[i] = process_symbol(symbol);
|
||||
result->codes[i] = process_symbol(image, width, height, symbol);
|
||||
symbol = zbar_symbol_next(symbol);
|
||||
}
|
||||
|
||||
|
@ -150,22 +206,57 @@ process_surface(MPPipeline *pipeline, cairo_surface_t **_surface)
|
|||
}
|
||||
|
||||
zbar_image_destroy(zbar_image);
|
||||
cairo_surface_destroy(surface);
|
||||
mp_zbar_image_unref(image);
|
||||
|
||||
++frames_processed;
|
||||
}
|
||||
|
||||
void
|
||||
mp_zbar_pipeline_process_image(cairo_surface_t *surface)
|
||||
mp_zbar_pipeline_process_image(MPZBarImage *image)
|
||||
{
|
||||
// If we haven't processed the previous frame yet, drop this one
|
||||
if (frames_received != frames_processed) {
|
||||
cairo_surface_destroy(surface);
|
||||
mp_zbar_image_unref(image);
|
||||
return;
|
||||
}
|
||||
|
||||
++frames_received;
|
||||
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_surface, &surface,
|
||||
sizeof(cairo_surface_t *));
|
||||
mp_pipeline_invoke(pipeline, (MPPipelineCallback)process_image, &image,
|
||||
sizeof(MPZBarImage *));
|
||||
}
|
||||
|
||||
MPZBarImage *
|
||||
mp_zbar_image_new(uint8_t *data,
|
||||
MPPixelFormat pixel_format,
|
||||
int width,
|
||||
int height,
|
||||
int rotation,
|
||||
bool mirrored)
|
||||
{
|
||||
MPZBarImage *image = malloc(sizeof(MPZBarImage));
|
||||
image->data = data;
|
||||
image->pixel_format = pixel_format;
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
image->rotation = rotation;
|
||||
image->mirrored = mirrored;
|
||||
image->ref_count = 1;
|
||||
return image;
|
||||
}
|
||||
|
||||
MPZBarImage *
|
||||
mp_zbar_image_ref(MPZBarImage *image)
|
||||
{
|
||||
++image->ref_count;
|
||||
return image;
|
||||
}
|
||||
|
||||
void
|
||||
mp_zbar_image_unref(MPZBarImage *image)
|
||||
{
|
||||
if (--image->ref_count == 0) {
|
||||
free(image->data);
|
||||
free(image);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include "camera_config.h"
|
||||
|
||||
typedef struct _MPZBarImage MPZBarImage;
|
||||
|
||||
typedef struct {
|
||||
int bounds_x[4];
|
||||
int bounds_y[4];
|
||||
char *data;
|
||||
const char *type;
|
||||
} MPZBarCode;
|
||||
|
||||
typedef struct {
|
||||
MPZBarCode codes[8];
|
||||
uint8_t size;
|
||||
} MPZBarScanResult;
|
||||
|
||||
void mp_zbar_pipeline_start();
|
||||
void mp_zbar_pipeline_stop();
|
||||
|
||||
void mp_zbar_pipeline_process_image(MPZBarImage *image);
|
||||
|
||||
MPZBarImage *mp_zbar_image_new(uint8_t *data,
|
||||
MPPixelFormat pixel_format,
|
||||
int width,
|
||||
int height,
|
||||
int rotation,
|
||||
bool mirrored);
|
||||
MPZBarImage *mp_zbar_image_ref(MPZBarImage *image);
|
||||
void mp_zbar_image_unref(MPZBarImage *image);
|
|
@ -1,120 +0,0 @@
|
|||
#include "quickpreview.h"
|
||||
#include <assert.h>
|
||||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void
|
||||
test_quick_preview_fuzz()
|
||||
{
|
||||
for (size_t i = 0; i < 10000; ++i) {
|
||||
uint32_t width = rand() % 127 + 1;
|
||||
uint32_t height = rand() % 127 + 1;
|
||||
uint32_t format = rand() % (MP_PIXEL_FMT_MAX - 1) + 1;
|
||||
|
||||
assert(width > 0 && height > 0);
|
||||
|
||||
switch (format) {
|
||||
case MP_PIXEL_FMT_BGGR8:
|
||||
case MP_PIXEL_FMT_GBRG8:
|
||||
case MP_PIXEL_FMT_GRBG8:
|
||||
case MP_PIXEL_FMT_RGGB8:
|
||||
width += width % 2;
|
||||
height += height % 2;
|
||||
break;
|
||||
case MP_PIXEL_FMT_BGGR10P:
|
||||
case MP_PIXEL_FMT_GBRG10P:
|
||||
case MP_PIXEL_FMT_GRBG10P:
|
||||
case MP_PIXEL_FMT_RGGB10P:
|
||||
width += width % 2;
|
||||
height += height % 2;
|
||||
break;
|
||||
case MP_PIXEL_FMT_UYVY:
|
||||
case MP_PIXEL_FMT_YUYV:
|
||||
width += width % 4;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
int rotation;
|
||||
switch (rand() % 3) {
|
||||
case 0:
|
||||
rotation = 0;
|
||||
break;
|
||||
case 1:
|
||||
rotation = 90;
|
||||
break;
|
||||
case 2:
|
||||
rotation = 180;
|
||||
break;
|
||||
default:
|
||||
rotation = 270;
|
||||
break;
|
||||
}
|
||||
|
||||
bool mirrored = rand() % 2;
|
||||
|
||||
float matbuf[9];
|
||||
float *colormatrix = NULL;
|
||||
if (rand() % 2) {
|
||||
for (int j = 0; j < 9; ++j) {
|
||||
matbuf[j] = (double)rand() / RAND_MAX * 2.0;
|
||||
}
|
||||
colormatrix = matbuf;
|
||||
}
|
||||
|
||||
uint8_t blacklevel = rand() % 3;
|
||||
|
||||
size_t src_size = mp_pixel_format_width_to_bytes(format, width) * height;
|
||||
uint8_t *src = malloc(src_size);
|
||||
for (int j = 0; j < src_size; ++j) {
|
||||
src[j] = rand();
|
||||
}
|
||||
|
||||
uint32_t preview_width = mp_pixel_format_width_to_colors(format, width);
|
||||
uint32_t preview_height = mp_pixel_format_height_to_colors(format, height);
|
||||
if (preview_width > 32 && preview_height > 32) {
|
||||
preview_width /= (1 + rand() % 2);
|
||||
preview_height /= (1 + rand() % 2);
|
||||
}
|
||||
|
||||
uint32_t dst_width, dst_height, skip;
|
||||
quick_preview_size(
|
||||
&dst_width,
|
||||
&dst_height,
|
||||
&skip,
|
||||
preview_width,
|
||||
preview_height,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
rotation);
|
||||
|
||||
uint32_t *dst = malloc(dst_width * dst_height * sizeof(uint32_t));
|
||||
|
||||
quick_preview(
|
||||
dst,
|
||||
dst_width,
|
||||
dst_height,
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
rotation,
|
||||
mirrored,
|
||||
colormatrix,
|
||||
blacklevel,
|
||||
skip);
|
||||
|
||||
free(dst);
|
||||
free(src);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/quick_preview/fuzz", test_quick_preview_fuzz);
|
||||
return g_test_run();
|
||||
}
|
|
@ -15,20 +15,6 @@ get_time()
|
|||
return t.tv_sec + t.tv_usec * 1e-6;
|
||||
}
|
||||
|
||||
void
|
||||
on_capture(MPImage image, void *user_data)
|
||||
{
|
||||
size_t num_bytes =
|
||||
mp_pixel_format_width_to_bytes(image.pixel_format, image.width) *
|
||||
image.height;
|
||||
uint8_t *data = malloc(num_bytes);
|
||||
memcpy(data, image.data, num_bytes);
|
||||
|
||||
printf(" first byte: %d.", data[0]);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -184,7 +170,22 @@ main(int argc, char *argv[])
|
|||
(last - start_capture) * 1000);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
mp_camera_capture_image(camera, on_capture, NULL);
|
||||
MPBuffer buffer;
|
||||
if (!mp_camera_capture_buffer(camera, &buffer)) {
|
||||
printf(" Failed to capture buffer\n");
|
||||
}
|
||||
|
||||
size_t num_bytes =
|
||||
mp_pixel_format_width_to_bytes(m->pixel_format, m->width) *
|
||||
m->height;
|
||||
uint8_t *data = malloc(num_bytes);
|
||||
memcpy(data, buffer.data, num_bytes);
|
||||
|
||||
printf(" first byte: %d.", data[0]);
|
||||
|
||||
free(data);
|
||||
|
||||
mp_camera_release_buffer(camera, buffer.index);
|
||||
|
||||
double now = get_time();
|
||||
printf(" capture took %fms\n", (now - last) * 1000);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "camera_config.h"
|
||||
|
||||
typedef struct _cairo_surface cairo_surface_t;
|
||||
|
||||
typedef struct {
|
||||
int bounds_x[4];
|
||||
int bounds_y[4];
|
||||
char *data;
|
||||
const char *type;
|
||||
} MPZBarCode;
|
||||
|
||||
typedef struct {
|
||||
MPZBarCode codes[8];
|
||||
uint8_t size;
|
||||
} MPZBarScanResult;
|
||||
|
||||
void mp_zbar_pipeline_start();
|
||||
void mp_zbar_pipeline_stop();
|
||||
|
||||
void mp_zbar_pipeline_process_image(cairo_surface_t *surface);
|